Corriger filtres et lecture du journal
This commit is contained in:
parent
57e489e118
commit
9b45a3110f
2 changed files with 15 additions and 22 deletions
|
|
@ -576,6 +576,8 @@ Les consultations simples ne sont pas journalisées comme actions modificatrices
|
|||
|
||||
Le journal doit servir à comprendre les changements et protéger l'historique, pas à surveiller les membres.
|
||||
|
||||
Tous les modules ne sont pas équivalents en journalisation. Les actions techniques sont enregistrées largement par l'API. L'historique métier détaillé, avec raison et valeurs avant/après, existe pour les processus sensibles déjà instrumentés : trésorerie, réserves, collectes, dépenses, gouvernance, procès-verbaux, rencontres, contributions, littérature et gestion des membres.
|
||||
|
||||
## 14. Modifications simultanées
|
||||
|
||||
Il peut arriver que deux membres exécutifs travaillent sur la même information en même temps.
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { api } from '../stores/api';
|
|||
|
||||
const DOMAINES = [
|
||||
['', 'Tous les domaines'],
|
||||
['membres', 'Membres'],
|
||||
['propositions', 'Gouvernance'],
|
||||
['pv_reunions', 'Procès-verbaux'],
|
||||
['collectes', 'Collectes'],
|
||||
|
|
@ -62,9 +63,11 @@ export default function JournalActions() {
|
|||
dateStyle: 'medium',
|
||||
timeStyle: 'short',
|
||||
});
|
||||
const formatDateCompacte = (value) => new Date(value).toLocaleString('fr-CA', {
|
||||
const formatJour = (value) => new Date(value).toLocaleDateString('fr-CA', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
});
|
||||
const formatHeure = (value) => new Date(value).toLocaleTimeString('fr-CA', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
});
|
||||
|
|
@ -81,17 +84,6 @@ export default function JournalActions() {
|
|||
.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 = () => {
|
||||
|
|
@ -154,7 +146,7 @@ export default function JournalActions() {
|
|||
{actionsDisponibles.map((action) => <option key={action} value={action}>{action}</option>)}
|
||||
</select>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<button onClick={charger} className="p-3 bg-aa-blue text-white rounded-xl text-sm font-bold">Filtrer</button>
|
||||
<button onClick={() => charger()} className="p-3 bg-aa-blue text-white rounded-xl text-sm font-bold">Filtrer</button>
|
||||
<button onClick={resetFiltres} className="p-3 bg-gray-100 text-gray-600 rounded-xl text-sm font-bold">Réinitialiser</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -177,17 +169,16 @@ export default function JournalActions() {
|
|||
onClick={() => setOuvertHistorique(ouvert ? null : item.id)}
|
||||
className="w-full p-3 text-left flex items-center gap-3 active:bg-gray-50"
|
||||
>
|
||||
<span className="w-[84px] shrink-0 text-xs font-bold text-gray-500">{formatDateCompacte(item.cree_le)}</span>
|
||||
<span className="flex-1 min-w-0 text-sm font-medium text-gray-800 truncate">{resumeChangements(item)}</span>
|
||||
<span className="w-[70px] shrink-0 text-xs font-bold text-gray-500">{formatJour(item.cree_le)}</span>
|
||||
<span className="w-[50px] shrink-0 text-xs font-bold text-gray-500">{formatHeure(item.cree_le)}</span>
|
||||
<span className="flex-1 min-w-0 text-sm font-bold text-gray-800 truncate">{domaineLabel(item.table_cible)}</span>
|
||||
<span className="text-gray-300 text-lg">{ouvert ? '⌃' : '⌄'}</span>
|
||||
</button>
|
||||
{ouvert && (
|
||||
<div className="px-4 pb-4 space-y-3 border-t border-gray-100">
|
||||
<div className="pt-3">
|
||||
<p className="font-bold text-gray-800">{item.raison}</p>
|
||||
<p className="text-xs text-gray-400 mt-1">
|
||||
{formatDate(item.cree_le)} · {item.membre_prenom || item.membre_id || 'Membre inconnu'}
|
||||
</p>
|
||||
<p className="text-xs text-gray-400 mt-1">Par {item.membre_prenom || item.membre_id || 'membre inconnu'}</p>
|
||||
</div>
|
||||
{lignes.length > 0 && (
|
||||
<div className="rounded-xl bg-gray-50 p-3 text-xs text-gray-600 space-y-2">
|
||||
|
|
@ -211,7 +202,6 @@ export default function JournalActions() {
|
|||
</div>
|
||||
)}
|
||||
<div className="grid grid-cols-2 gap-2 text-xs text-gray-500">
|
||||
<p><span className="font-bold text-gray-600">Domaine</span><br />{domaineLabel(item.table_cible)}</p>
|
||||
<p><span className="font-bold text-gray-600">Action</span><br />{item.action}</p>
|
||||
<p className="col-span-2 break-all"><span className="font-bold text-gray-600">Référence technique</span><br />{item.objet_id}</p>
|
||||
{item.chemin_api && <p className="col-span-2 break-all"><span className="font-bold text-gray-600">API</span><br />{item.chemin_api}</p>}
|
||||
|
|
@ -230,13 +220,14 @@ export default function JournalActions() {
|
|||
onClick={() => setOuvertTechnique(ouvert ? null : action.id)}
|
||||
className="w-full p-3 text-left flex items-center gap-3 active:bg-gray-50"
|
||||
>
|
||||
<span className="w-[84px] shrink-0 text-xs font-bold text-gray-500">{formatDateCompacte(action.cree_le)}</span>
|
||||
<span className="flex-1 min-w-0 text-sm font-medium text-gray-800 truncate">{action.methode} {action.chemin}</span>
|
||||
<span className="w-[70px] shrink-0 text-xs font-bold text-gray-500">{formatJour(action.cree_le)}</span>
|
||||
<span className="w-[50px] shrink-0 text-xs font-bold text-gray-500">{formatHeure(action.cree_le)}</span>
|
||||
<span className="flex-1 min-w-0 text-sm font-bold text-gray-800 truncate">Technique</span>
|
||||
<span className="text-xs font-bold rounded-full bg-gray-100 text-gray-600 px-2 py-1">{action.statut_http}</span>
|
||||
</button>
|
||||
{ouvert && (
|
||||
<div className="px-4 pb-4 pt-3 border-t border-gray-100 text-xs text-gray-500 space-y-1">
|
||||
<p><span className="font-bold text-gray-600">Date</span>: {formatDate(action.cree_le)}</p>
|
||||
<p className="break-all"><span className="font-bold text-gray-600">Action</span>: {action.methode} {action.chemin}</p>
|
||||
<p className="break-all"><span className="font-bold text-gray-600">Membre</span>: {action.membre_id}</p>
|
||||
{action.admin_id && <p className="break-all"><span className="font-bold text-gray-600">Admin</span>: {action.admin_id}</p>}
|
||||
{action.adresse_ip && <p><span className="font-bold text-gray-600">IP</span>: {action.adresse_ip}</p>}
|
||||
|
|
|
|||
Loading…
Reference in a new issue