Page admin : statistiques réparées et refonte responsive dans la DA du site
Build & Deploy / build (push) Successful in 22s
Build & Deploy / build (push) Successful in 22s
Statistiques : le type Stats attendait cir/sccs/users alors que /admin/stats renvoie cir_documents/sccs_documents/total_users. Trois cartes sur quatre affichaient « - » et les deux barres restaient à zéro. Contrat aligné sur l'API. Style : la feuille n'utilisait aucune couleur du site — palette bleu ardoise, unités en px, cartes sans bordure. Reprise de la DA des autres pages : accent violet #7c3aed (celui de la Sidebar), échelle de gris et rayons 0.5/0.75rem de DocumentsPage, cartes blanches bordées, unités rem. Responsive : - onglets en bande défilante au lieu d'un retour à la ligne qui cassait l'entête ; - les tableaux deviennent des cartes sous 768px, chaque cellule portant son intitulé de colonne via data-label + ::before — l'ancien display:block empilait les valeurs sans dire à quoi elles correspondaient ; - cartes de stats en grille auto-fit, plus de paliers codés en dur ; - panneau scrapers en deux colonnes, empilé sous 1024px, boutons pleine largeur sous 480px ; - modale contrainte à la largeur de l'écran, focus visible au clavier. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+17
-17
@@ -7,7 +7,7 @@ type User = { id: string; email: string; full_name?: string; role: 'admin' | 'us
|
||||
type Document = { id: string; title: string; source: 'CIR' | 'SCCS'; ingredient?: string; date?: string }
|
||||
|
||||
type ScraperLog = { id: string; when: string; source: string; status: 'pending' | 'running' | 'success' | 'error' }
|
||||
type Stats = { total_documents?: number; cir?: number; sccs?: number; users?: number }
|
||||
type Stats = { total_documents?: number; cir_documents?: number; sccs_documents?: number; total_users?: number }
|
||||
|
||||
export default function Admin() {
|
||||
const [tab, setTab] = useState<'users'|'documents'|'stats'|'scrapers'>('users')
|
||||
@@ -184,10 +184,10 @@ export default function Admin() {
|
||||
<tbody>
|
||||
{sortedUsers.map(u=> (
|
||||
<tr key={u.id}>
|
||||
<td>{u.id}</td>
|
||||
<td>{u.email}</td>
|
||||
<td>{u.full_name||'-'}</td>
|
||||
<td><span className={u.role==='admin'?styles.badgeAdmin:styles.badgeUser}>{u.role}</span></td>
|
||||
<td data-label="ID">{u.id}</td>
|
||||
<td data-label="Email">{u.email}</td>
|
||||
<td data-label="Nom complet">{u.full_name||'-'}</td>
|
||||
<td data-label="Rôle"><span className={u.role==='admin'?styles.badgeAdmin:styles.badgeUser}>{u.role}</span></td>
|
||||
<td className={styles.rowActions}>
|
||||
<button className={styles.danger} onClick={()=>openConfirm('Supprimer cet utilisateur ?', ()=>deleteUser(u.id))}>Supprimer</button>
|
||||
<button onClick={()=>openConfirm(u.role==='admin'? 'Rétrograder cet utilisateur ?' : 'Passer admin ?', ()=>toggleRole(u))}>{u.role==='admin'?'Rétrograder':'Passer admin'}</button>
|
||||
@@ -212,9 +212,9 @@ export default function Admin() {
|
||||
<thead>
|
||||
<tr>
|
||||
<th onClick={()=>{ setSortKey('id'); setSortDir(sortDir==='asc'?'desc':'asc') }}>ID</th>
|
||||
<th onClick={()=>{ setSortKey('title'); setSortDir(sortDir==='asc'?'desc':'asc') }}>Title</th>
|
||||
<th onClick={()=>{ setSortKey('title'); setSortDir(sortDir==='asc'?'desc':'asc') }}>Titre</th>
|
||||
<th onClick={()=>{ setSortKey('source'); setSortDir(sortDir==='asc'?'desc':'asc') }}>Source</th>
|
||||
<th onClick={()=>{ setSortKey('ingredient'); setSortDir(sortDir==='asc'?'desc':'asc') }}>Ingredient</th>
|
||||
<th onClick={()=>{ setSortKey('ingredient'); setSortDir(sortDir==='asc'?'desc':'asc') }}>Ingrédient</th>
|
||||
<th onClick={()=>{ setSortKey('date'); setSortDir(sortDir==='asc'?'desc':'asc') }}>Date</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
@@ -222,11 +222,11 @@ export default function Admin() {
|
||||
<tbody>
|
||||
{sortedDocs.map(d=> (
|
||||
<tr key={d.id}>
|
||||
<td>{d.id}</td>
|
||||
<td>{d.title}</td>
|
||||
<td><span className={d.source==='CIR'?styles.badgeCIR:styles.badgeSCCS}>{d.source}</span></td>
|
||||
<td>{d.ingredient||'-'}</td>
|
||||
<td>{d.date||'-'}</td>
|
||||
<td data-label="ID">{d.id}</td>
|
||||
<td data-label="Titre">{d.title}</td>
|
||||
<td data-label="Source"><span className={d.source==='CIR'?styles.badgeCIR:styles.badgeSCCS}>{d.source}</span></td>
|
||||
<td data-label="Ingrédient">{d.ingredient||'-'}</td>
|
||||
<td data-label="Date">{d.date||'-'}</td>
|
||||
<td className={styles.rowActions}><button className={styles.danger} onClick={()=>openConfirm('Supprimer ce document ?', ()=>deleteDocument(d.id))}>Supprimer</button></td>
|
||||
</tr>
|
||||
))}
|
||||
@@ -242,16 +242,16 @@ export default function Admin() {
|
||||
<section>
|
||||
<div className={styles.cards}>
|
||||
<div className={styles.card}><FileText/><div className={styles.cardLabel}>Total documents</div><div className={styles.cardValue}>{stats?.total_documents ?? '-'}</div></div>
|
||||
<div className={styles.card}><Database/><div className={styles.cardLabel}>Documents CIR</div><div className={styles.cardValue}>{stats?.cir ?? '-'}</div></div>
|
||||
<div className={styles.card}><Database/><div className={styles.cardLabel}>Documents SCCS</div><div className={styles.cardValue}>{stats?.sccs ?? '-'}</div></div>
|
||||
<div className={styles.card}><Users/><div className={styles.cardLabel}>Total utilisateurs</div><div className={styles.cardValue}>{stats?.users ?? '-'}</div></div>
|
||||
<div className={styles.card}><Database/><div className={styles.cardLabel}>Documents CIR</div><div className={styles.cardValue}>{stats?.cir_documents ?? '-'}</div></div>
|
||||
<div className={styles.card}><Database/><div className={styles.cardLabel}>Documents SCCS</div><div className={styles.cardValue}>{stats?.sccs_documents ?? '-'}</div></div>
|
||||
<div className={styles.card}><Users/><div className={styles.cardLabel}>Total utilisateurs</div><div className={styles.cardValue}>{stats?.total_users ?? '-'}</div></div>
|
||||
</div>
|
||||
|
||||
<div className={styles.chartWrap}>
|
||||
<div className={styles.chartLabel}>CIR vs SCCS</div>
|
||||
<div className={styles.bars}>
|
||||
<div className={styles.barRow}><div className={styles.barTitle}>CIR</div><div className={styles.barFill} style={{width:`${(stats?.cir||0)/(Math.max(1, (stats?.cir||0)+(stats?.sccs||0))) * 100}%`}}></div><div className={styles.barVal}>{stats?.cir||0}</div></div>
|
||||
<div className={styles.barRow}><div className={styles.barTitle}>SCCS</div><div className={`${styles.barFill} ${styles.barFillAlt}`} style={{width:`${(stats?.sccs||0)/(Math.max(1, (stats?.cir||0)+(stats?.sccs||0))) * 100}%`}}></div><div className={styles.barVal}>{stats?.sccs||0}</div></div>
|
||||
<div className={styles.barRow}><div className={styles.barTitle}>CIR</div><div className={styles.barFill} style={{width:`${(stats?.cir_documents||0)/(Math.max(1, (stats?.cir_documents||0)+(stats?.sccs_documents||0))) * 100}%`}}></div><div className={styles.barVal}>{stats?.cir_documents||0}</div></div>
|
||||
<div className={styles.barRow}><div className={styles.barTitle}>SCCS</div><div className={`${styles.barFill} ${styles.barFillAlt}`} style={{width:`${(stats?.sccs_documents||0)/(Math.max(1, (stats?.cir_documents||0)+(stats?.sccs_documents||0))) * 100}%`}}></div><div className={styles.barVal}>{stats?.sccs_documents||0}</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user