Menu d'administration regroupé et version mobile du site

Le menu empilait douze liens sous un seul intitulé, avec des carrés, ronds et
losanges qui ne portaient aucun sens. Sur téléphone, la barre latérale se
dépliait en ligne et devenait un pavé de boutons inutilisable.

Navigation
- Six rubriques au lieu d'une pile : Pilotage, Vie du réseau, Rencontres,
  Communication, Configuration, Côté adhérent. Le quotidien est ainsi séparé de
  ce qu'on ne touche qu'occasionnellement.
- Une icône par entrée (`BackendIcons.tsx`) à la place des formes génériques.
- Le menu devient une table de données filtrée par capacité, au lieu de douze
  blocs JSX répétant les mêmes appels à `can()`.

Mobile
- Sous 1024px la barre latérale devient un tiroir : voile de fond, fermeture par
  Échap, par clic hors zone et à la navigation, défilement de la page bloqué,
  actions Voir le site / Déconnexion rapatriées dans le tiroir.
- `min-height: 0` sur la zone de navigation : sans lui l'enfant flex refusait de
  rétrécir et la carte utilisateur recouvrait les derniers liens, interceptant
  les clics.
- En-tête public repensé : logo toujours présent sur téléphone (seul retour à
  l'accueil), accès adhérent sur la même ligne, navigation répartie en dessous
  au lieu de se replier en escalier.
- Liste des adhérents : le tableau à cinq colonnes devient une fiche par
  adhérent, sur deux lignes constantes, sans défilement horizontal.
- Compteurs du tableau de bord en deux colonnes plutôt qu'empilés.

Vérifié : aucun débordement horizontal sur les 23 pages en 320, 390 et 768 px ;
tiroir et cibles tactiles testés au doigt sur toutes les interactions.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QXNXRC4j5VLfKvpyyisrnb
This commit is contained in:
Claude committed 2026-07-25 00:25:56 +00:00
1 parent 19d1104ab0
commit db361051e6
6 files changed
+719 -256

No files matched your search

+134
View File
@@ -0,0 +1,134 @@
import type { ReactNode } from "react";
/**
* Icônes de la navigation d'administration. Les anciens carrés / ronds /
* losanges ne portaient aucun sens : une icône par rubrique rend le menu
* lisible d'un coup d'œil.
*/
export type BackendIconName =
| "dashboard"
| "members"
| "inbox"
| "promos"
| "meetings"
| "registrations"
| "gallery"
| "emails"
| "social"
| "categories"
| "settings"
| "admins"
| "space"
| "menu"
| "close";
const PATHS: Record<BackendIconName, ReactNode> = {
dashboard: (
<>
<rect x="3" y="3" width="7.5" height="7.5" rx="2" />
<rect x="13.5" y="3" width="7.5" height="7.5" rx="2" />
<rect x="3" y="13.5" width="7.5" height="7.5" rx="2" />
<rect x="13.5" y="13.5" width="7.5" height="7.5" rx="2" />
</>
),
members: (
<>
<circle cx="9" cy="8" r="3.4" />
<path d="M2.8 20c0-3.4 2.8-5.6 6.2-5.6s6.2 2.2 6.2 5.6" />
<path d="M16.4 5.2a3.2 3.2 0 0 1 0 6M18 14.8c2 .7 3.2 2.4 3.2 5.2" />
</>
),
inbox: (
<>
<rect x="2.5" y="4.5" width="19" height="15" rx="2.5" />
<path d="m4 6.5 8 6 8-6" />
</>
),
promos: (
<>
<path d="M11.6 2.8H20a1.2 1.2 0 0 1 1.2 1.2v8.4a1.6 1.6 0 0 1-.5 1.1l-7.4 7.4a1.6 1.6 0 0 1-2.3 0l-7-7a1.6 1.6 0 0 1 0-2.3l7.4-7.4a1.6 1.6 0 0 1 1.1-.5Z" />
<circle cx="16.6" cy="7.4" r="1.4" />
</>
),
meetings: (
<>
<rect x="3" y="5" width="18" height="16" rx="2.5" />
<path d="M3 9.5h18M8 3v4M16 3v4" />
</>
),
registrations: (
<>
<path d="M8 4.5H6.5A2.5 2.5 0 0 0 4 7v12a2.5 2.5 0 0 0 2.5 2.5h11A2.5 2.5 0 0 0 20 19V7a2.5 2.5 0 0 0-2.5-2.5H16" />
<rect x="8" y="2.5" width="8" height="4" rx="1.4" />
<path d="m8.6 12.4 1.7 1.7 3.4-3.4M8.6 17.2h6.8" />
</>
),
gallery: (
<>
<rect x="3" y="4.5" width="18" height="15" rx="2.5" />
<circle cx="8.6" cy="9.6" r="1.7" />
<path d="m4 17 4.8-4.4a1.8 1.8 0 0 1 2.4 0L16 17M14 14.4l1.6-1.5a1.8 1.8 0 0 1 2.4 0L21 15.6" />
</>
),
emails: (
<>
<path d="M20.5 3.5 10.8 13.2M20.5 3.5 14.3 21l-3.5-7.8L3 9.7l17.5-6.2Z" />
</>
),
social: (
<>
<circle cx="18" cy="5.4" r="2.9" />
<circle cx="6" cy="12" r="2.9" />
<circle cx="18" cy="18.6" r="2.9" />
<path d="m8.5 10.6 7-3.8M8.5 13.4l7 3.8" />
</>
),
categories: (
<>
<rect x="3" y="3.5" width="7.5" height="7.5" rx="2" />
<rect x="3" y="13" width="7.5" height="7.5" rx="2" />
<rect x="13.5" y="13" width="7.5" height="7.5" rx="2" />
<circle cx="17.2" cy="7.2" r="3.8" />
</>
),
settings: (
<>
<path d="M4 7h10M18 7h2M4 17h2M10 17h10" />
<circle cx="16" cy="7" r="2.4" />
<circle cx="8" cy="17" r="2.4" />
</>
),
admins: (
<>
<path d="M12 2.6 4.5 5.8V11c0 4.7 3.1 8.6 7.5 10.4 4.4-1.8 7.5-5.7 7.5-10.4V5.8L12 2.6Z" />
<path d="m8.8 11.8 2.2 2.2 4.2-4.2" />
</>
),
space: (
<>
<circle cx="12" cy="8.2" r="3.6" />
<path d="M4.8 20.4c0-3.7 3.2-6 7.2-6s7.2 2.3 7.2 6" />
</>
),
menu: <path d="M4 7h16M4 12h16M4 17h16" />,
close: <path d="m6 6 12 12M18 6 6 18" />,
};
export function BackendIcon({ name, size = 18 }: { name: BackendIconName; size?: number }) {
return (
<svg
aria-hidden="true"
viewBox="0 0 24 24"
width={size}
height={size}
fill="none"
stroke="currentColor"
strokeWidth="1.7"
strokeLinecap="round"
strokeLinejoin="round"
style={{ display: "block", flexShrink: 0 }}
>
{PATHS[name]}
</svg>
);
}
+19 -38
View File
@@ -9,56 +9,37 @@ const NAV = [
export function SiteHeader({ active, logo = false }: { active?: string; logo?: boolean }) {
return (
<header
className="container"
style={{
display: "flex",
alignItems: "center",
justifyContent: logo ? "space-between" : "flex-end",
padding: "18px clamp(16px, 5vw, 56px)",
gap: 20,
flexWrap: "wrap",
}}
>
{logo && (
<Link href="/" aria-label="Plein R — accueil" style={{ display: "inline-flex", alignItems: "center" }}>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src="/assets/logo.png" alt="Plein R" style={{ height: 46, width: "auto", display: "block" }} />
</Link>
)}
<nav style={{ display: "flex", alignItems: "center", gap: 30, flexWrap: "wrap" }}>
<header className="container site-header">
{/* Sur téléphone le logo est toujours visible : c'est le seul retour à
l'accueil, la page d'accueil ne l'affichant qu'en grand dans le hero. */}
<Link
href="/"
aria-label="Plein R — accueil"
className={`site-header__brand${logo ? "" : " is-desktop-hidden"}`}
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src="/assets/logo.png" alt="Plein R" />
</Link>
<nav className="site-header__nav">
{NAV.map((n) => {
const isActive = active === n.key;
return (
<Link
key={n.key}
href={n.href}
style={{
textDecoration: "none",
color: isActive ? "#3c3322" : "#6f6450",
fontWeight: isActive ? 600 : 500,
fontSize: 15,
}}
aria-current={isActive ? "page" : undefined}
className={`site-header__link${isActive ? " is-active" : ""}`}
>
{n.label}
</Link>
);
})}
<Link
href="/backend"
style={{
textDecoration: "none",
color: "#fff",
background: "#9a6638",
fontWeight: 600,
fontSize: 14.5,
padding: "10px 18px",
borderRadius: 999,
}}
>
Espace adhérent
</Link>
</nav>
<Link href="/backend" className="site-header__cta">
Espace adhérent
</Link>
</header>
);
}