import React, { useEffect, useState } from 'react'; import { useStore } from '../stores/store'; import { api } from '../stores/api'; const DOMAINES = [ ['', 'Tous les domaines'], ['propositions', 'Gouvernance'], ['pv_reunions', 'Procès-verbaux'], ['collectes', 'Collectes'], ['depenses', 'Dépenses'], ['envois_contributions', 'Contributions'], ['reserves', 'Réserves'], ['operations_bancaires', 'Opérations bancaires'], ['mouvements_reserves', 'Virements réserves'], ['litterature', 'Littérature'], ['reunions', 'Rencontres'], ['destinataires_contributions', 'Destinataires'], ]; export default function JournalActions() { const { setPage } = useStore(); const [actions, setActions] = useState([]); const [historique, setHistorique] = useState([]); const [membres, setMembres] = useState([]); const [tab, setTab] = useState('historique'); const [filtres, setFiltres] = useState({ date_debut: '', date_fin: '', membre_id: '', table_cible: '', action: '', q: '' }); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); const [ouvertHistorique, setOuvertHistorique] = useState(null); const [ouvertTechnique, setOuvertTechnique] = useState(null); const historiqueQuery = (source = filtres) => { const params = new URLSearchParams({ limite: '100' }); Object.entries(source).forEach(([key, value]) => { if (value) params.set(key, value); }); return `/journal/historique?${params.toString()}`; }; const charger = async (sourceFiltres = filtres) => { setLoading(true); setError(''); try { const [hist, acts, mbrs] = await Promise.all([ api.get(historiqueQuery(sourceFiltres)), api.get('/journal/actions?limite=100'), api.get('/membres/actifs'), ]); setHistorique(hist); setActions(acts); setMembres(mbrs); } catch (e) { setError(e.message); } finally { setLoading(false); } }; useEffect(() => { charger(); }, []); const formatDate = (value) => new Date(value).toLocaleString('fr-CA', { dateStyle: 'medium', timeStyle: 'short', }); const formatDateCompacte = (value) => new Date(value).toLocaleString('fr-CA', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', }); const formatValeur = (value) => { if (value === null || value === undefined || value === '') return '—'; if (typeof value === 'boolean') return value ? 'oui' : 'non'; if (Array.isArray(value)) return `${value.length} élément(s)`; if (typeof value === 'object') return JSON.stringify(value); return String(value); }; const changements = (avant = {}, apres = {}) => { const cles = [...new Set([...Object.keys(avant || {}), ...Object.keys(apres || {})])]; return cles .map((cle) => [cle, avant?.[cle], apres?.[cle]]) .filter(([, a, b]) => JSON.stringify(a) !== JSON.stringify(b)); }; const resumeChangements = (item) => { const lignes = changements(item.avant, item.apres); if (lignes.length === 0 && item.avant && !item.apres) { return Object.keys(item.avant).slice(0, 2).join(', ') || item.raison; } if (lignes.length === 0) return item.raison; const resume = lignes.slice(0, 2) .map(([cle, avant, apres]) => `${cle}: ${formatValeur(avant)} → ${formatValeur(apres)}`) .join(' · '); return lignes.length > 2 ? `${resume} · +${lignes.length - 2}` : resume; }; const domaineLabel = (value) => DOMAINES.find(([id]) => id === value)?.[1] || value || '—'; const updateFiltre = (key, value) => setFiltres((f) => ({ ...f, [key]: value })); const resetFiltres = () => { const vides = { date_debut: '', date_fin: '', membre_id: '', table_cible: '', action: '', q: '' }; setFiltres(vides); charger(vides); }; const actionsDisponibles = [...new Set(historique.map((item) => item.action))].sort(); return (

Journal

Changements enregistrés

{tab === 'historique' && (
updateFiltre('q', e.target.value)} placeholder="Rechercher dans les raisons" className="w-full p-3 bg-gray-50 rounded-xl border-0 focus:ring-2 focus:ring-aa-blue text-sm" />
updateFiltre('date_debut', e.target.value)} className="w-full p-3 bg-gray-50 rounded-xl border-0 focus:ring-2 focus:ring-aa-blue text-sm" /> updateFiltre('date_fin', e.target.value)} className="w-full p-3 bg-gray-50 rounded-xl border-0 focus:ring-2 focus:ring-aa-blue text-sm" />
)} {loading &&

Chargement...

} {error &&
{error}
} {!loading && tab === 'historique' && historique.length === 0 && (

Aucun changement journalisé.

)} {!loading && tab === 'technique' && actions.length === 0 && (

Aucune action technique journalisée.

)} {tab === 'historique' && historique.map((item) => { const ouvert = ouvertHistorique === item.id; const lignes = changements(item.avant, item.apres); return (
{ouvert && (

{item.raison}

{formatDate(item.cree_le)} · {item.membre_prenom || item.membre_id || 'Membre inconnu'}

{lignes.length > 0 && (
{lignes.map(([cle, avant, apres]) => (
{cle} {formatValeur(avant)} → {formatValeur(apres)}
))}
)} {item.avant && !item.apres && (
{Object.entries(item.avant).map(([cle, valeur]) => (

{cle}: {formatValeur(valeur)}

))}
)}

Domaine
{domaineLabel(item.table_cible)}

Action
{item.action}

Objet
{item.objet_id}

{item.chemin_api &&

API
{item.chemin_api}

}
)}
); })} {tab === 'technique' && actions.map((action) => { const ouvert = ouvertTechnique === action.id; return (
{ouvert && (

Date: {formatDate(action.cree_le)}

Membre: {action.membre_id}

{action.admin_id &&

Admin: {action.admin_id}

} {action.adresse_ip &&

IP: {action.adresse_ip}

} {action.details?.impersonification && (

Action faite en impersonification

)}
)}
); })}
); }