groupe-meditation/frontend/src/pages/Rapports.jsx
Daniel Allaire 69309acd13 Intégrer rencontres et assemblées pilotées par tuiles
Refond les pages Rencontres et Assemblées autour de tuiles ouvrables avec actions locales, PDF serveur, ventes, collectes, présences, décisions, postes et contributions.

Ajoute les invitations publiques d'assemblée en lecture seule avec lien expirant, l'adoption formelle du rapport du trésorier avec proposeur/secondeur, et bloque les contributions tant que ce rapport n'est pas adopté.

Uniformise la navigation et les entêtes, retire les modules redondants des contextes, ajoute la création de comptes membres par exécutif et les données démo reliées aux nouveaux processus.

Ce commit capture un lot fonctionnel important; une refactorisation ciblée du module Assemblées, puis Trésorerie/Gouvernance, est maintenant pertinente pour isoler les règles métier et faciliter les tests.
2026-05-31 09:02:02 -04:00

81 lines
3.1 KiB
JavaScript

import React, { useState, useEffect } from 'react';
import { api } from '../stores/api';
import PageHeader from '../components/PageHeader';
export default function Rapports() {
const [anneesResolutions, setAnneesResolutions] = useState([]);
const [loading, setLoading] = useState(true);
const [generating, setGenerating] = useState(null);
useEffect(() => {
api.get('/rapports/resolutions/annees').then((annees) => {
setAnneesResolutions(annees);
setLoading(false);
}).catch(() => setLoading(false));
}, []);
const telecharger = async (path, key, filename) => {
setGenerating(key);
try {
const token = api.getToken();
const res = await fetch(`/api/rapports/${path}`, {
headers: { Authorization: `Bearer ${token}` },
});
if (!res.ok) throw new Error('Erreur de génération');
const blob = await res.blob();
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = filename;
a.click();
URL.revokeObjectURL(a.href);
} catch (e) {
alert(`Erreur: ${e.message}`);
}
setGenerating(null);
};
const vide = anneesResolutions.length === 0;
return (
<div className="min-h-screen bg-gray-100">
<PageHeader titre="Rapports PDF" sousTitre="C1 — Finances, réunions, résolutions" />
<div className="p-4 space-y-4">
{loading ? (
<p className="text-center text-gray-400 py-8">Chargement...</p>
) : vide ? (
<div className="text-center py-12">
<p className="text-4xl mb-2">📄</p>
<p className="text-gray-500">Aucun rapport disponible</p>
<p className="text-gray-400 text-sm mt-1">Les rapports apparaîtront après les premières saisies</p>
</div>
) : (
<>
{/* Registre des résolutions */}
{anneesResolutions.length > 0 && (
<div className="bg-white rounded-2xl p-4 shadow-sm">
<h3 className="font-bold text-gray-800 mb-3">📜 Registre des résolutions</h3>
<div className="grid gap-2">
{anneesResolutions.map((a) => (
<button key={a.annee}
onClick={() => telecharger(`resolutions/${a.annee}`, `res-${a.annee}`, `registre_resolutions_${a.annee}.pdf`)}
disabled={generating === `res-${a.annee}`}
className="flex items-center gap-3 p-3 bg-orange-50 rounded-xl text-left active:scale-[0.98] transition border border-orange-200"
>
<span className="text-xl">📜</span>
<div className="flex-1">
<p className="font-bold text-orange-800 text-sm">Résolutions {a.annee}</p>
<p className="text-xs text-orange-600">Décisions prises en Groupe</p>
</div>
<span className="text-orange-400">{generating === `res-${a.annee}` ? '⏳' : '↓'}</span>
</button>
))}
</div>
</div>
)}
</>
)}
</div>
</div>
);
}