diff --git a/src/pages/Admin.tsx b/src/pages/Admin.tsx
index 49c2b52..fa2275e 100644
--- a/src/pages/Admin.tsx
+++ b/src/pages/Admin.tsx
@@ -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() {
{sortedUsers.map(u=> (
- | {u.id} |
- {u.email} |
- {u.full_name||'-'} |
- {u.role} |
+ {u.id} |
+ {u.email} |
+ {u.full_name||'-'} |
+ {u.role} |
@@ -212,9 +212,9 @@ export default function Admin() {
| { setSortKey('id'); setSortDir(sortDir==='asc'?'desc':'asc') }}>ID |
- { setSortKey('title'); setSortDir(sortDir==='asc'?'desc':'asc') }}>Title |
+ { setSortKey('title'); setSortDir(sortDir==='asc'?'desc':'asc') }}>Titre |
{ setSortKey('source'); setSortDir(sortDir==='asc'?'desc':'asc') }}>Source |
- { setSortKey('ingredient'); setSortDir(sortDir==='asc'?'desc':'asc') }}>Ingredient |
+ { setSortKey('ingredient'); setSortDir(sortDir==='asc'?'desc':'asc') }}>Ingrédient |
{ setSortKey('date'); setSortDir(sortDir==='asc'?'desc':'asc') }}>Date |
|
@@ -222,11 +222,11 @@ export default function Admin() {
|
{sortedDocs.map(d=> (
- | {d.id} |
- {d.title} |
- {d.source} |
- {d.ingredient||'-'} |
- {d.date||'-'} |
+ {d.id} |
+ {d.title} |
+ {d.source} |
+ {d.ingredient||'-'} |
+ {d.date||'-'} |
|
))}
@@ -242,16 +242,16 @@ export default function Admin() {
Total documents
{stats?.total_documents ?? '-'}
-
Documents CIR
{stats?.cir ?? '-'}
-
Documents SCCS
{stats?.sccs ?? '-'}
-
Total utilisateurs
{stats?.users ?? '-'}
+
Documents CIR
{stats?.cir_documents ?? '-'}
+
Documents SCCS
{stats?.sccs_documents ?? '-'}
+
Total utilisateurs
{stats?.total_users ?? '-'}
CIR vs SCCS
-
-
+
CIR
{stats?.cir_documents||0}
+
SCCS
{stats?.sccs_documents||0}
diff --git a/src/styles/Admin.module.css b/src/styles/Admin.module.css
index f572718..29fc969 100644
--- a/src/styles/Admin.module.css
+++ b/src/styles/Admin.module.css
@@ -1,58 +1,582 @@
-.page{padding:20px;max-width:1200px;margin:0 auto}
-.header{display:flex;align-items:center;justify-content:space-between;gap:16px}
-.tabs{display:flex;gap:8px}
-.tabs button{background:transparent;border:1px solid #e6e9ef;padding:8px 12px;border-radius:8px;cursor:pointer}
-.tabs .active{background:#eef2ff;border-color:#c7b3ff}
-.content{margin-top:16px}
-.spinner{padding:12px;background:#f8fafc;border:1px solid #e6eef6;border-radius:8px}
-.error{padding:12px;background:#fff1f2;border:1px solid #fecaca;color:#b91c1c;border-radius:8px}
+/* Administration — aligné sur la DA des autres pages :
+ accent violet #7c3aed (celui de la Sidebar), échelle de gris Tailwind,
+ cartes blanches bordées, rayons 0.5/0.75rem, unités rem. */
-.panelHeader{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;gap:12px}
-.search{padding:8px;border:1px solid #e6eef6;border-radius:6px}
-
-.tableWrap{overflow:auto;background:#fff;border:1px solid #eef2f7;padding:8px;border-radius:8px}
-.table{width:100%;border-collapse:collapse}
-.table th,.table td{padding:10px;border-bottom:1px solid #f1f5f9;text-align:left}
-.rowActions{display:flex;gap:8px}
-.danger{background:#ef4444;color:#fff;border:none;padding:8px 10px;border-radius:6px;cursor:pointer}
-
-.badgeAdmin{background:#7c3aed;color:#fff;padding:4px 8px;border-radius:999px;font-weight:600}
-.badgeUser{background:#94a3b8;color:#fff;padding:4px 8px;border-radius:999px;font-weight:600}
-.badgeCIR{background:#2563eb;color:#fff;padding:4px 8px;border-radius:6px}
-.badgeSCCS{background:#10b981;color:#fff;padding:4px 8px;border-radius:6px}
-
-.empty{padding:12px;color:#64748b}
-
-.cards{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:16px}
-.card{background:#fff;padding:16px;border-radius:8px;display:flex;flex-direction:column;gap:8px;align-items:flex-start}
-.cardLabel{font-size:14px;color:#64748b}
-.cardValue{font-size:20px;font-weight:700}
-
-.chartWrap{background:#fff;padding:12px;border-radius:8px}
-.bars{display:flex;flex-direction:column;gap:8px}
-.barRow{display:flex;align-items:center;gap:8px}
-.barTitle{width:60px}
-.barFill{height:14px;background:#2563eb;border-radius:6px;flex:1}
-.barFillAlt{background:#10b981}
-.barVal{width:40px;text-align:right}
-
-.scraperControls{display:flex;gap:20px;align-items:flex-start}
-.logList{background:#fff;padding:12px;border-radius:8px}
-.status{font-weight:700}
-
-.modalBackdrop{position:fixed;left:0;right:0;top:0;bottom:0;background:rgba(2,6,23,0.45);display:flex;align-items:center;justify-content:center;z-index:9999}
-.modal{background:#fff;padding:20px;border-radius:8px;min-width:320px}
-.modalActions{display:flex;gap:8px;justify-content:flex-end;margin-top:12px}
-
-@media (max-width: 768px){
- .cards{grid-template-columns:repeat(2,1fr)}
- .table th, .table td{display:block}
- .table tr{margin-bottom:12px;border-bottom:none}
- .tableWrap{padding:0}
+.page {
+ padding: 2rem;
+ max-width: 80rem;
+ margin: 0 auto;
}
-@media (max-width: 480px){
- .cards{grid-template-columns:1fr}
- .scraperControls{flex-direction:column}
- .tabs{flex-wrap:wrap}
+/* ── En-tête et onglets ─────────────────────────────────────── */
+
+.header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 1rem;
+ flex-wrap: wrap;
+ margin-bottom: 1.75rem;
}
+
+.header h1 {
+ font-size: 2.25rem;
+ font-weight: 700;
+ color: rgb(17, 24, 39);
+ line-height: 1.1;
+ margin: 0;
+}
+
+.tabs {
+ display: flex;
+ gap: 0.5rem;
+}
+
+.tabs button {
+ background: transparent;
+ border: 1px solid rgb(229, 231, 235);
+ color: rgb(55, 65, 81);
+ padding: 0.5rem 0.875rem;
+ border-radius: 0.5rem;
+ font-size: 0.9375rem;
+ font-weight: 500;
+ cursor: pointer;
+ white-space: nowrap;
+ transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
+}
+
+.tabs button:hover {
+ background: rgb(249, 250, 251);
+ border-color: rgb(209, 213, 219);
+}
+
+.tabs .active,
+.tabs .active:hover {
+ background: rgba(124, 58, 237, 0.08);
+ border-color: rgba(124, 58, 237, 0.45);
+ color: #7c3aed;
+}
+
+.tabs button:focus-visible,
+.danger:focus-visible,
+.search:focus-visible {
+ outline: 2px solid #7c3aed;
+ outline-offset: 2px;
+}
+
+.content {
+ margin-top: 0.5rem;
+}
+
+/* ── États ──────────────────────────────────────────────────── */
+
+.spinner {
+ padding: 0.875rem 1rem;
+ background: rgb(249, 250, 251);
+ border: 1px solid rgb(229, 231, 235);
+ border-radius: 0.75rem;
+ color: rgb(75, 85, 99);
+ margin-bottom: 1rem;
+}
+
+.error {
+ padding: 0.875rem 1rem;
+ background: rgb(254, 242, 242);
+ border: 1px solid rgb(254, 202, 202);
+ color: rgb(185, 28, 28);
+ border-radius: 0.75rem;
+ margin-bottom: 1rem;
+}
+
+.empty {
+ padding: 1.5rem;
+ text-align: center;
+ color: rgb(107, 114, 128);
+}
+
+/* ── Barre de panneau ───────────────────────────────────────── */
+
+.panelHeader {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 0.75rem;
+ flex-wrap: wrap;
+ margin-bottom: 1rem;
+ color: rgb(75, 85, 99);
+ font-size: 0.9375rem;
+}
+
+.search {
+ padding: 0.5rem 0.75rem;
+ border: 1px solid rgb(229, 231, 235);
+ border-radius: 0.5rem;
+ font-size: 0.9375rem;
+ min-width: 14rem;
+ flex: 1 1 14rem;
+ max-width: 22rem;
+}
+
+/* ── Tableaux ───────────────────────────────────────────────── */
+
+.tableWrap {
+ background: white;
+ border: 1px solid rgb(229, 231, 235);
+ border-radius: 0.75rem;
+ overflow-x: auto;
+}
+
+.table {
+ width: 100%;
+ border-collapse: collapse;
+ font-size: 0.9375rem;
+}
+
+.table th,
+.table td {
+ padding: 0.75rem 1rem;
+ text-align: left;
+ border-bottom: 1px solid rgb(243, 244, 246);
+}
+
+.table th {
+ background: rgb(249, 250, 251);
+ color: rgb(55, 65, 81);
+ font-weight: 600;
+ white-space: nowrap;
+ cursor: pointer;
+ user-select: none;
+}
+
+.table th:hover {
+ color: #7c3aed;
+}
+
+.table tbody tr:last-child td {
+ border-bottom: none;
+}
+
+.table tbody tr:hover {
+ background: rgb(249, 250, 251);
+}
+
+.rowActions {
+ display: flex;
+ gap: 0.5rem;
+ flex-wrap: wrap;
+}
+
+.rowActions button {
+ background: white;
+ border: 1px solid rgb(229, 231, 235);
+ color: rgb(55, 65, 81);
+ padding: 0.375rem 0.75rem;
+ border-radius: 0.5rem;
+ font-size: 0.875rem;
+ cursor: pointer;
+}
+
+.rowActions button:hover {
+ border-color: rgba(124, 58, 237, 0.45);
+ color: #7c3aed;
+}
+
+.danger,
+.rowActions .danger {
+ background: rgb(239, 68, 68);
+ color: white;
+ border: 1px solid rgb(239, 68, 68);
+}
+
+.danger:hover,
+.rowActions .danger:hover {
+ background: rgb(220, 38, 38);
+ border-color: rgb(220, 38, 38);
+ color: white;
+}
+
+/* ── Badges ─────────────────────────────────────────────────── */
+
+.badgeAdmin,
+.badgeUser,
+.badgeCIR,
+.badgeSCCS {
+ display: inline-block;
+ padding: 0.1875rem 0.625rem;
+ font-size: 0.8125rem;
+ font-weight: 600;
+ white-space: nowrap;
+}
+
+.badgeAdmin,
+.badgeUser {
+ border-radius: 999px;
+}
+
+.badgeAdmin {
+ background: rgba(124, 58, 237, 0.1);
+ color: #6d28d9;
+ border: 1px solid rgba(124, 58, 237, 0.35);
+}
+
+.badgeUser {
+ background: rgb(243, 244, 246);
+ color: rgb(75, 85, 99);
+ border: 1px solid rgb(229, 231, 235);
+}
+
+.badgeCIR,
+.badgeSCCS {
+ border-radius: 0.5rem;
+}
+
+.badgeCIR {
+ background: rgba(37, 99, 235, 0.1);
+ color: rgb(29, 78, 216);
+ border: 1px solid rgba(37, 99, 235, 0.3);
+}
+
+.badgeSCCS {
+ background: rgba(16, 185, 129, 0.12);
+ color: rgb(4, 120, 87);
+ border: 1px solid rgba(16, 185, 129, 0.3);
+}
+
+/* ── Statistiques ───────────────────────────────────────────── */
+
+.cards {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
+ gap: 1rem;
+ margin-bottom: 1.25rem;
+}
+
+.card {
+ background: white;
+ border: 1px solid rgb(229, 231, 235);
+ border-radius: 0.75rem;
+ padding: 1.25rem;
+ display: flex;
+ flex-direction: column;
+ gap: 0.375rem;
+ align-items: flex-start;
+}
+
+.card svg {
+ width: 1.25rem;
+ height: 1.25rem;
+ color: #7c3aed;
+ margin-bottom: 0.25rem;
+}
+
+.cardLabel {
+ font-size: 0.875rem;
+ color: rgb(107, 114, 128);
+}
+
+.cardValue {
+ font-size: 1.75rem;
+ font-weight: 700;
+ color: rgb(17, 24, 39);
+ line-height: 1.1;
+}
+
+.chartWrap {
+ background: white;
+ border: 1px solid rgb(229, 231, 235);
+ border-radius: 0.75rem;
+ padding: 1.25rem;
+}
+
+.chartLabel {
+ font-weight: 600;
+ color: rgb(17, 24, 39);
+ margin-bottom: 1rem;
+}
+
+.bars {
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+}
+
+.barRow {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+}
+
+.barTitle {
+ width: 3.5rem;
+ flex-shrink: 0;
+ font-size: 0.875rem;
+ font-weight: 500;
+ color: rgb(55, 65, 81);
+}
+
+/* La largeur est posée en style inline par le composant : min-width garde une
+ amorce visible quand la valeur est nulle. */
+.barFill {
+ height: 0.625rem;
+ min-width: 0.625rem;
+ background: rgb(37, 99, 235);
+ border-radius: 999px;
+}
+
+.barFillAlt {
+ background: rgb(16, 185, 129);
+}
+
+.barVal {
+ width: 3rem;
+ flex-shrink: 0;
+ text-align: right;
+ font-variant-numeric: tabular-nums;
+ color: rgb(75, 85, 99);
+}
+
+/* ── Scrapers ───────────────────────────────────────────────── */
+
+.scraperControls {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
+ gap: 1rem;
+ align-items: start;
+}
+
+.scraperControls > div:first-child {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+}
+
+.scraperControls button {
+ background: #7c3aed;
+ color: white;
+ border: 1px solid #7c3aed;
+ padding: 0.625rem 1rem;
+ border-radius: 0.5rem;
+ font-size: 0.9375rem;
+ font-weight: 500;
+ cursor: pointer;
+}
+
+.scraperControls button:hover {
+ background: #6d28d9;
+ border-color: #6d28d9;
+}
+
+.logList {
+ background: white;
+ border: 1px solid rgb(229, 231, 235);
+ border-radius: 0.75rem;
+ padding: 1.25rem;
+}
+
+.logList h4 {
+ margin: 0 0 0.75rem;
+ font-size: 0.9375rem;
+ font-weight: 600;
+ color: rgb(17, 24, 39);
+}
+
+.logList ul {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+}
+
+.logList li {
+ padding: 0.625rem 0.75rem;
+ background: rgb(249, 250, 251);
+ border: 1px solid rgb(243, 244, 246);
+ border-radius: 0.5rem;
+ font-size: 0.875rem;
+ color: rgb(75, 85, 99);
+ overflow-wrap: anywhere;
+}
+
+.status {
+ font-weight: 600;
+ color: rgb(17, 24, 39);
+}
+
+/* ── Modale ─────────────────────────────────────────────────── */
+
+.modalBackdrop {
+ position: fixed;
+ inset: 0;
+ background: rgba(17, 24, 39, 0.5);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 1rem;
+ z-index: 9999;
+}
+
+.modal {
+ background: white;
+ border-radius: 0.75rem;
+ padding: 1.5rem;
+ width: 100%;
+ max-width: 26rem;
+ box-shadow: 0 10px 25px rgba(17, 24, 39, 0.15);
+}
+
+.modalActions {
+ display: flex;
+ gap: 0.5rem;
+ justify-content: flex-end;
+ margin-top: 1.25rem;
+ flex-wrap: wrap;
+}
+
+.modalActions button {
+ background: white;
+ border: 1px solid rgb(229, 231, 235);
+ color: rgb(55, 65, 81);
+ padding: 0.5rem 1rem;
+ border-radius: 0.5rem;
+ font-size: 0.9375rem;
+ cursor: pointer;
+}
+
+.modalActions button:last-child {
+ background: #7c3aed;
+ border-color: #7c3aed;
+ color: white;
+}
+
+/* ── Responsive ─────────────────────────────────────────────── */
+
+@media (max-width: 1024px) {
+ .scraperControls {
+ grid-template-columns: 1fr;
+ }
+
+ .scraperControls > div:first-child {
+ flex-direction: row;
+ flex-wrap: wrap;
+ }
+}
+
+@media (max-width: 768px) {
+ .page {
+ padding: 1.25rem 1rem;
+ }
+
+ .header {
+ align-items: flex-start;
+ flex-direction: column;
+ gap: 0.875rem;
+ }
+
+ .header h1 {
+ font-size: 1.75rem;
+ }
+
+ /* Onglets en bande défilante plutôt qu'en retour à la ligne. */
+ .tabs {
+ width: 100%;
+ overflow-x: auto;
+ padding-bottom: 0.25rem;
+ scrollbar-width: none;
+ }
+
+ .tabs::-webkit-scrollbar {
+ display: none;
+ }
+
+ /* Chaque ligne devient une carte, avec l'intitulé de colonne en regard. */
+ .tableWrap {
+ background: transparent;
+ border: none;
+ border-radius: 0;
+ overflow-x: visible;
+ }
+
+ .table,
+ .table tbody,
+ .table tr,
+ .table td {
+ display: block;
+ width: 100%;
+ }
+
+ .table thead {
+ display: none;
+ }
+
+ .table tr {
+ background: white;
+ border: 1px solid rgb(229, 231, 235);
+ border-radius: 0.75rem;
+ padding: 0.25rem 0;
+ margin-bottom: 0.75rem;
+ }
+
+ .table tbody tr:hover {
+ background: white;
+ }
+
+ .table td {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 1rem;
+ padding: 0.5rem 1rem;
+ border-bottom: none;
+ text-align: right;
+ overflow-wrap: anywhere;
+ }
+
+ .table td::before {
+ content: attr(data-label);
+ font-weight: 600;
+ color: rgb(107, 114, 128);
+ font-size: 0.8125rem;
+ text-align: left;
+ flex-shrink: 0;
+ }
+
+ .rowActions {
+ justify-content: stretch;
+ padding-top: 0.75rem;
+ border-top: 1px solid rgb(243, 244, 246);
+ margin-top: 0.25rem;
+ }
+
+ .rowActions button {
+ flex: 1;
+ padding: 0.5rem 0.75rem;
+ }
+
+ .search {
+ max-width: none;
+ }
+}
+
+@media (max-width: 480px) {
+ .cards {
+ grid-template-columns: 1fr;
+ }
+
+ .scraperControls > div:first-child {
+ flex-direction: column;
+ }
+
+ .scraperControls button {
+ width: 100%;
+ }
+
+ .barTitle {
+ width: 2.75rem;
+ }
+}
+
+/* NB : le site n'a pas de mode sombre réel — index.css bascule --bg sur le body
+ mais aucune page n'utilise ces variables et toutes gardent des cartes claires.
+ Cette page suit la même convention pour rester cohérente avec ses sœurs. */