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.
|
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
|
## 14. Modifications simultanées
|
||||||
|
|
||||||
Il peut arriver que deux membres exécutifs travaillent sur la même information en même temps.
|
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 = [
|
const DOMAINES = [
|
||||||
['', 'Tous les domaines'],
|
['', 'Tous les domaines'],
|
||||||
|
['membres', 'Membres'],
|
||||||
['propositions', 'Gouvernance'],
|
['propositions', 'Gouvernance'],
|
||||||
['pv_reunions', 'Procès-verbaux'],
|
['pv_reunions', 'Procès-verbaux'],
|
||||||
['collectes', 'Collectes'],
|
['collectes', 'Collectes'],
|
||||||
|
|
@ -62,9 +63,11 @@ export default function JournalActions() {
|
||||||
dateStyle: 'medium',
|
dateStyle: 'medium',
|
||||||
timeStyle: 'short',
|
timeStyle: 'short',
|
||||||
});
|
});
|
||||||
const formatDateCompacte = (value) => new Date(value).toLocaleString('fr-CA', {
|
const formatJour = (value) => new Date(value).toLocaleDateString('fr-CA', {
|
||||||
month: 'short',
|
month: 'short',
|
||||||
day: 'numeric',
|
day: 'numeric',
|
||||||
|
});
|
||||||
|
const formatHeure = (value) => new Date(value).toLocaleTimeString('fr-CA', {
|
||||||
hour: '2-digit',
|
hour: '2-digit',
|
||||||
minute: '2-digit',
|
minute: '2-digit',
|
||||||
});
|
});
|
||||||
|
|
@ -81,17 +84,6 @@ export default function JournalActions() {
|
||||||
.map((cle) => [cle, avant?.[cle], apres?.[cle]])
|
.map((cle) => [cle, avant?.[cle], apres?.[cle]])
|
||||||
.filter(([, a, b]) => JSON.stringify(a) !== JSON.stringify(b));
|
.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 domaineLabel = (value) => DOMAINES.find(([id]) => id === value)?.[1] || value || '—';
|
||||||
const updateFiltre = (key, value) => setFiltres((f) => ({ ...f, [key]: value }));
|
const updateFiltre = (key, value) => setFiltres((f) => ({ ...f, [key]: value }));
|
||||||
const resetFiltres = () => {
|
const resetFiltres = () => {
|
||||||
|
|
@ -154,7 +146,7 @@ export default function JournalActions() {
|
||||||
{actionsDisponibles.map((action) => <option key={action} value={action}>{action}</option>)}
|
{actionsDisponibles.map((action) => <option key={action} value={action}>{action}</option>)}
|
||||||
</select>
|
</select>
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<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>
|
<button onClick={resetFiltres} className="p-3 bg-gray-100 text-gray-600 rounded-xl text-sm font-bold">Réinitialiser</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -177,17 +169,16 @@ export default function JournalActions() {
|
||||||
onClick={() => setOuvertHistorique(ouvert ? null : item.id)}
|
onClick={() => setOuvertHistorique(ouvert ? null : item.id)}
|
||||||
className="w-full p-3 text-left flex items-center gap-3 active:bg-gray-50"
|
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="w-[70px] shrink-0 text-xs font-bold text-gray-500">{formatJour(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-[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>
|
<span className="text-gray-300 text-lg">{ouvert ? '⌃' : '⌄'}</span>
|
||||||
</button>
|
</button>
|
||||||
{ouvert && (
|
{ouvert && (
|
||||||
<div className="px-4 pb-4 space-y-3 border-t border-gray-100">
|
<div className="px-4 pb-4 space-y-3 border-t border-gray-100">
|
||||||
<div className="pt-3">
|
<div className="pt-3">
|
||||||
<p className="font-bold text-gray-800">{item.raison}</p>
|
<p className="font-bold text-gray-800">{item.raison}</p>
|
||||||
<p className="text-xs text-gray-400 mt-1">
|
<p className="text-xs text-gray-400 mt-1">Par {item.membre_prenom || item.membre_id || 'membre inconnu'}</p>
|
||||||
{formatDate(item.cree_le)} · {item.membre_prenom || item.membre_id || 'Membre inconnu'}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
{lignes.length > 0 && (
|
{lignes.length > 0 && (
|
||||||
<div className="rounded-xl bg-gray-50 p-3 text-xs text-gray-600 space-y-2">
|
<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>
|
||||||
)}
|
)}
|
||||||
<div className="grid grid-cols-2 gap-2 text-xs text-gray-500">
|
<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><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>
|
<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>}
|
{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)}
|
onClick={() => setOuvertTechnique(ouvert ? null : action.id)}
|
||||||
className="w-full p-3 text-left flex items-center gap-3 active:bg-gray-50"
|
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="w-[70px] shrink-0 text-xs font-bold text-gray-500">{formatJour(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-[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>
|
<span className="text-xs font-bold rounded-full bg-gray-100 text-gray-600 px-2 py-1">{action.statut_http}</span>
|
||||||
</button>
|
</button>
|
||||||
{ouvert && (
|
{ouvert && (
|
||||||
<div className="px-4 pb-4 pt-3 border-t border-gray-100 text-xs text-gray-500 space-y-1">
|
<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>
|
<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.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>}
|
{action.adresse_ip && <p><span className="font-bold text-gray-600">IP</span>: {action.adresse_ip}</p>}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue