import { useEffect, useState } from 'react'; import { Link, Navigate } from 'react-router-dom'; import { useAuth } from '../lib/AuthContext.jsx'; import { api, dateParts, statutLabel } from '../lib/api.js'; import { MembersTab, RencontresTab, InscriptionsTab } from '../components/admin/AdminTabs.jsx'; import { CategoriesTab, ContenuTab } from '../components/admin/AdminContent.jsx'; import { PastEventsTab } from '../components/admin/AdminPast.jsx'; import { UsersTab } from '../components/admin/AdminUsers.jsx'; // Moderators only get members/rencontres/inscriptions/rencontres passées // (create + edit, never delete) — everything else (dashboard, taxonomy, // site content, staff account management) is admin-only, enforced both // here (sidebar) and server-side (routes). const TABS = [ { key: 'dashboard', icon: '◧', label: 'Tableau de bord', title: 'Tableau de bord', roles: ['admin'] }, { key: 'membres', icon: '▤', label: 'Membres', title: 'Gestion des membres', roles: ['admin', 'moderator'] }, { key: 'rencontres', icon: '◈', label: 'Rencontres', title: 'Rencontres', roles: ['admin', 'moderator'] }, { key: 'passees', icon: '▦', label: 'Rencontres passées', title: 'Rencontres passées', roles: ['admin', 'moderator'] }, { key: 'inscriptions', icon: '✎', label: 'Inscriptions', title: 'Inscriptions', roles: ['admin', 'moderator'] }, { key: 'categories', icon: '☲', label: 'Catégories', title: 'Catégories', roles: ['admin'] }, { key: 'contenu', icon: '▧', label: 'Contenu du site', title: 'Contenu du site', roles: ['admin'] }, { key: 'utilisateurs', icon: '⚿', label: 'Administrateurs', title: 'Administrateurs & modérateurs', roles: ['admin'] }, ]; function Dashboard() { const [data, setData] = useState(null); useEffect(() => { api.get('/api/admin/dashboard').then(setData).catch(() => {}); }, []); if (!data) return

Chargement…

; const k = data.kpis; const daysToNext = k.prochaine_date ? Math.max(0, Math.round((new Date(k.prochaine_date) - Date.now()) / 86400000)) : null; const kpis = [ { n: k.membres_valides, l: 'Membres validés', d: 'saison en cours' }, { n: k.a_renouveler, l: 'À renouveler', d: 'non validés' }, { n: k.rencontres_a_venir, l: 'Rencontres à venir', d: daysToNext != null ? `prochaine dans ${daysToNext} j` : '—' }, { n: k.inscriptions_attente, l: 'Inscriptions en attente', d: 'à valider' }, ]; return ( <>
{kpis.map((x) => (
{x.l}
{x.n}
{x.d}
))}

Dernières inscriptions

{data.latestInscriptions.map((i) => (
{i.nom}
{i.entreprise} · {i.rencontre}
{statutLabel(i.statut)}
))}

Prochaines rencontres

{data.upcoming.map((e) => { const { jour, mois } = dateParts(e.date_renc); return (
{jour}
{mois}
{e.titre}
{Math.max(0, e.places - e.inscrits)} places restantes
); })}
); } // Rendered inside /espace-membre once a logged-in user is confirmed as // admin or moderator (see Espace.jsx). Assumes an authenticated staff // user — no auth gate here, the caller already checked it. export function AdminShell() { const { user, logout } = useAuth(); const tabs = TABS.filter((t) => t.roles.includes(user.role)); const [tab, setTab] = useState(tabs[0].key); const current = tabs.find((t) => t.key === tab) || tabs[0]; const today = new Date().toLocaleDateString('fr-FR', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' }); return (

{current.title}

{today}
{(user.email || 'AD').slice(0, 2).toUpperCase()}
{tab === 'dashboard' && } {tab === 'membres' && } {tab === 'rencontres' && } {tab === 'passees' && } {tab === 'inscriptions' && } {tab === 'categories' && } {tab === 'contenu' && } {tab === 'utilisateurs' && }
); } // Legacy /admin bookmarks: everyone now logs in from a single entry point. export default function Admin() { return ; }