92 lines
3.7 KiB
React
92 lines
3.7 KiB
React
import { useState, useEffect } from 'react'
|
|
import { useAuth } from '../context/AuthContext'
|
|
|
|
const ACTION_LABELS = {
|
|
connexion: 'Connexion',
|
|
changement_mot_de_passe: 'Changement mot de passe',
|
|
recommandation_ajout: 'Recommandation ajoutée',
|
|
recommandation_modification: 'Recommandation modifiée',
|
|
recommandation_suppression: 'Recommandation supprimée',
|
|
invite_ajout: 'Invité ajouté',
|
|
invite_modification: 'Invité modifié',
|
|
invite_suppression: 'Invité supprimé',
|
|
invite_email_envoye: 'Email invitation envoyé',
|
|
candidature_demarrage: 'Candidature démarrée',
|
|
candidature_refus: 'Candidature refusée',
|
|
absence_ajout: 'Absence ajoutée',
|
|
absence_suppression: 'Absence supprimée',
|
|
}
|
|
|
|
export default function AdminLogs() {
|
|
const { authFetch } = useAuth()
|
|
const [logs, setLogs] = useState([])
|
|
const [page, setPage] = useState(1)
|
|
const [pages, setPages] = useState(1)
|
|
const [loading, setLoading] = useState(true)
|
|
|
|
const load = async (p) => {
|
|
setLoading(true)
|
|
setPage(p)
|
|
try {
|
|
const r = await authFetch(`/api/activity-logs?page=${p}&limit=50`)
|
|
if (r.ok) { const d = await r.json(); setLogs(d.logs); setPages(d.pages) }
|
|
} catch {} finally { setLoading(false) }
|
|
}
|
|
|
|
useEffect(() => { load(1) }, [])
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
<h2 className="text-xl font-bold text-gray-800">Journal d'activité</h2>
|
|
|
|
{loading ? (
|
|
<p className="text-center text-gray-400 py-8">Chargement...</p>
|
|
) : logs.length === 0 ? (
|
|
<p className="text-center text-gray-400 py-8">Aucune activité enregistrée</p>
|
|
) : (
|
|
<div className="bg-white rounded-xl shadow-sm overflow-x-auto">
|
|
<table className="w-full text-sm">
|
|
<thead>
|
|
<tr className="bg-gray-50 text-left text-xs text-gray-500 uppercase">
|
|
<th className="px-4 py-3">Date</th>
|
|
<th className="px-4 py-3">Utilisateur</th>
|
|
<th className="px-4 py-3">Action</th>
|
|
<th className="px-4 py-3">Détails</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y">
|
|
{logs.map((log) => (
|
|
<tr key={log.id} className="hover:bg-gray-50">
|
|
<td className="px-4 py-2.5 text-gray-500 whitespace-nowrap">
|
|
{new Date(log.created_at).toLocaleDateString('fr-FR', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' })}
|
|
</td>
|
|
<td className="px-4 py-2.5 whitespace-nowrap font-medium">{log.user_prenom} {log.user_nom}</td>
|
|
<td className="px-4 py-2.5 whitespace-nowrap">
|
|
<span className="bg-blue-100 text-blue-800 text-xs font-medium px-2 py-0.5 rounded">
|
|
{ACTION_LABELS[log.action] || log.action}
|
|
</span>
|
|
</td>
|
|
<td className="px-4 py-2.5 text-gray-600 max-w-xs truncate">
|
|
{log.details ? (() => { try { return JSON.stringify(JSON.parse(log.details), null, 1).replace(/[{}"]/g, '').replace(/,/g, ', ').trim() } catch { return log.details } })() : '-'}
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
)}
|
|
|
|
{pages > 1 && (
|
|
<div className="flex justify-center gap-2">
|
|
{Array.from({ length: pages }, (_, i) => i + 1).map((p) => (
|
|
<button key={p} onClick={() => load(p)}
|
|
className={`w-8 h-8 rounded-lg text-sm font-medium ${p === page ? 'bg-blue-900 text-white' : 'bg-white text-gray-600 border hover:bg-gray-50'}`}>
|
|
{p}
|
|
</button>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|