xpeditis2.0/docs/ui-audit.md
2026-08-13 12:26:46 +02:00

420 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# UI Audit — Cartographie de l'application Xpeditis
**Date :** 2026-08-12 · **Branche :** `ui_update` · **Périmètre :** `apps/frontend`
**Volumétrie :** 70 routes `page.tsx` · 126 fichiers `.tsx` · ~38 000 lignes · 51 pages `'use client'`
> Document d'observation. **Aucune UI n'a été modifiée.** À lire avec [`design-system-audit.md`](./design-system-audit.md), qui verrouille la charte.
---
## Sommaire
- [A. Synthèse — 12 problèmes transversaux](#a-synthèse--12-problèmes-transversaux)
- [B. Cartographie par zone](#b-cartographie-par-zone)
- [B1. Chrome applicatif](#b1-chrome-applicatif-layouts)
- [B2. Zone Dashboard (produit)](#b2-zone-dashboard--produit)
- [B3. Zone Admin](#b3-zone-admin)
- [B4. Zone Wiki](#b4-zone-wiki)
- [B5. Zone Auth](#b5-zone-auth)
- [B6. Portail Transporteur & Booking public](#b6-portail-transporteur--booking-public)
- [B7. Zone Marketing / Public](#b7-zone-marketing--public)
- [B8. Blog & Docs](#b8-blog--docs)
- [B9. Routes techniques / mortes](#b9-routes-techniques--mortes)
- [C. Composants à mutualiser](#c-composants-à-mutualiser)
- [D. Duplications identifiées](#d-duplications-identifiées)
- [E. Tableau de priorisation](#e-tableau-de-priorisation)
---
## A. Synthèse — 12 problèmes transversaux
| # | Problème | Mesure | Gravité |
|---|----------|--------|---------|
| **A1** | **Deux identités visuelles.** Le produit est bleu Tailwind générique ; la marque est navy + turquoise. | 816 `blue-*` / 64 fichiers | 🔴 Critique |
| **A2** | **Design system non adopté.** Les primitives existent mais ne sont pas utilisées. | 299 `<button>` vs 17 `<Button>` · 175 `<input>` vs 16 `<Input>` · 26 `<table>` vs 2 `<Table>` | 🔴 Critique |
| **A3** | **Primitives cassées.** 6 paquets Radix installés, **0 import Radix** dans le code. `Select` n'affiche jamais sa valeur, `Popover` n'est pas portalisé, `Dialog` n'a ni focus trap ni `Escape`. | `ui/select.tsx`, `popover.tsx`, `dialog.tsx`, `command.tsx` | 🔴 Critique |
| **A4** | **Aucun système de feedback.** Pas de toast. Le retour utilisateur passe par `alert()` / `confirm()` natifs — modaux, bloquants, non traduits, non stylés. | 58 appels natifs / 18 fichiers | 🔴 Critique |
| **A5** | **Titre dupliqué.** La topbar affiche le nom de l'entrée de nav active **et** chaque page affiche son propre `<h1>`. Deux titres empilés sur chaque écran. | `dashboard/layout.tsx` + toutes les pages | 🟠 Majeur |
| **A6** | **Pas de fil d'Ariane.** Aucun composant Breadcrumb. Sur `/dashboard/settings/users` ou `/dashboard/wiki/incoterms`, aucun repère de profondeur ni retour. | — | 🟠 Majeur |
| **A7** | **États non mutualisés.** Loading, empty, error, skeleton réécrits à la main sur chaque page. | 40 fichiers `animate-spin` · 27 empty states · 29 error states · 1 skeleton | 🟠 Majeur |
| **A8** | **Zéro adaptation grand écran.** `2xl:` n'apparaît jamais. Tout est plafonné à `max-w-7xl` (1280px). | 0 occurrence `2xl:` | 🟠 Majeur |
| **A9** | **Tablette non traitée.** Palier `md` (768–1023px) 2× moins couvert que `sm` et `lg`. La sidebar reste un drawer alors que le contenu est déjà en grille desktop. | 120 `md:` vs 287 `sm:` / 259 `lg:` | 🟠 Majeur |
| **A10** | **Tables non responsives.** 21 fichiers gèrent le débordement par `overflow-x-auto`. Sur mobile : scroll horizontal, pas d'alternative en cartes. | 21 fichiers | 🟠 Majeur |
| **A11** | **Accessibilité minimale.** 29 `aria-label` pour 299 boutons. 9 `role=`. Focus `focus:` (454) au lieu de `focus-visible:` (13) → anneau visible à la souris. Aucun `prefers-reduced-motion`. | — | 🟠 Majeur |
| **A12** | **Pages monolithiques.** Aucune séparation composants/page. 25 `useState` dans un seul fichier de 1 169 lignes. | `documents/page.tsx` 1169 l. · `admin/blog` 1341 l. · `search-advanced` 1067 l. | 🟡 Moyen |
---
## B. Cartographie par zone
**Légende des colonnes de métriques :** `L` = lignes · `B` = occurrences `blue-*` · `N` = appels `alert()`/`confirm()` natifs · `S` = `useState` · `PH` = utilise `PageHeader`
---
### B1. Chrome applicatif (layouts)
#### `app/[locale]/layout.tsx` — Root layout
**Objectif :** Injecter polices, i18n (`next-intl`), providers (React Query, Auth, Subscription, Cookie), métadonnées SEO.
| Problème | Type |
|----------|------|
| `<html>` ne porte jamais `class="dark"` → le dark mode défini dans `globals.css` est mort | Incohérence |
| Le favicon et l'image OpenGraph pointent vers `logo-black.svg` (1875×1699) — inadapté au format OG 1200×630 | UI |
| Pas de `<Toaster>` monté (aucun système de toast) | Manque |
---
#### `app/[locale]/dashboard/layout.tsx` — App shell produit
**Objectif :** Sidebar + topbar + bottom-nav + garde d'authentification + gating par plan.
**Composants :** `NotificationDropdown`, `LanguageSwitcher`, `StatusBadge`, `next/image`, icônes lucide.
| Problème | Type |
|----------|------|
| Item de sidebar actif en `bg-blue-50 text-blue-700` — hors charte | 🔴 UI |
| Avatar sidebar `bg-blue-600` **et** avatar topbar `bg-blue-600` — deux avatars du même utilisateur visibles simultanément | 🔴 UI + UX |
| Bottom-nav active en `text-blue-600` — hors charte | 🔴 UI |
| **Double titre** : la topbar affiche le nom de nav actif, la page affiche son `<h1>` | 🟠 UX |
| Sidebar `w-64` fixe, **non repliable** — 256px perdus en permanence sur les vues denses (tables) | 🟠 UX |
| Bottom-nav = 5 entrées sur 8. Wiki, Organisation, Clés API absents de la nav mobile primaire | 🟠 UX |
| SVG burger et croix écrits à la main alors que `Menu`/`X` de lucide sont disponibles | 🟡 Cohérence |
| Aucun état actif sur le sous-arbre `/dashboard/settings/*` : trois entrées de nav pointent vers des enfants de `settings` sans regroupement visuel | 🟠 UX |
| Aucune recherche globale / command menu | 🟠 Manque |
| `logo-black.svg` rendu en `width=50 height=60` avec `className="h-auto"` → ratio non contrôlé | 🟡 UI |
| Bouton logout `text-red-700 bg-red-50` pleine largeur en pied de sidebar : poids visuel disproportionné pour une action rare | 🟠 Hiérarchie |
| Overlay mobile `bg-gray-600 bg-opacity-75` (syntaxe Tailwind v2 legacy) | 🟡 Cohérence |
| Aucun focus trap dans le drawer mobile ouvert | 🟠 A11y |
---
#### `app/[locale]/admin/layout.tsx` — App shell admin
**Objectif :** Même shell, thème navy, garde de rôle `ADMIN`, exception pour `/admin/login`.
| Problème | Type |
|----------|------|
| ✅ **Seule zone conforme à la charte** (sidebar `bg-brand-navy`, logo blanc) | — |
| Structure **dupliquée à 80 %** avec `dashboard/layout.tsx` (drawer, overlay, burger, garde, pied) sans mutualisation | 🟠 Duplication |
| Pas de bottom-nav mobile (contrairement au dashboard) → incohérence de navigation entre les deux espaces | 🟠 UX |
| Pas de breadcrumb | 🟠 UX |
---
### B2. Zone Dashboard — produit
| Route | Objectif | L | B | N | S | PH |
|-------|----------|---|---|---|---|----|
| `/dashboard` | Vue d'ensemble KPI + graphiques | 454 | 6 | 0 | 0 | ❌ |
| `/dashboard/bookings` | Liste des réservations | 908 | 9 | 0 | 9 | ✅ |
| `/dashboard/bookings/[id]` | Détail réservation | 284 | 4 | 1 | 0 | ❌ |
| `/dashboard/bookings/new` | Création réservation | 871 | 31 | 0 | 4 | ❌ |
| `/dashboard/booking/new` | Création réservation (**doublon**) | 736 | 18 | 0 | 7 | ❌ |
| `/dashboard/booking/[id]/pay` | Paiement Stripe | 621 | 20 | 0 | 12 | ❌ |
| `/dashboard/booking/[id]/payment-success` | Confirmation paiement | 146 | 9 | 2 | 3 | ❌ |
| `/dashboard/documents` | Gestion documentaire | 1169 | 27 | 9 | 25 | ✅ |
| `/dashboard/search` | Recherche de taux | 614 | 13 | 0 | 9 | ❌ |
| `/dashboard/search-advanced` | Tunnel de recherche multi-étapes | 1067 | 28 | 0 | 11 | ❌ |
| `/dashboard/search-advanced/results` | Résultats de recherche | 520 | 18 | 0 | 6 | ❌ |
| `/dashboard/track-trace` | Suivi d'expédition | 642 | 27 | 0 | 8 | ❌ |
| `/dashboard/notifications` | Centre de notifications | 410 | 13 | 1 | 3 | ❌ |
| `/dashboard/profile` | Profil utilisateur | 453 | 15 | 0 | 7 | ❌ |
| `/dashboard/settings/organization` | Paramètres organisation | 524 | 15 | 0 | 8 | ❌ |
| `/dashboard/settings/users` | Gestion des membres | 848 | 16 | 3 | 9 | ✅ |
| `/dashboard/settings/api-keys` | Clés API | 457 | 6 | 0 | 7 | ✅ |
| `/dashboard/settings/subscription` | Abonnement (redirection) | 27 | 1 | 0 | 0 | ❌ |
| `/dashboard/docs` | Redirection docs | 7 | 0 | 0 | 0 | ❌ |
| `/dashboard/admin/[[...rest]]` | Redirection vers `/admin` | 11 | 0 | 0 | 0 | ❌ |
#### Détail des pages critiques
**`/dashboard` — Vue d'ensemble**
Composants : `Card`, `Button`, `Badge`, `ExportButton`, Recharts (`PieChart`, `BarChart`).
- 🔴 CTA principal `bg-blue-600 hover:bg-blue-700 shadow-lg` — hors charte, et `shadow-lg` sur un bouton est un poids d'ombre de modale.
- 🔴 `<Link><Button>` → `<a>` contenant `<button>` : **HTML invalide**, `Button` n'a pas de `asChild`.
- 🔴 Couleurs de graphiques hardcodées hors charte : `#10b981` / `#ef4444` / `#f59e0b` (Pie), `#3b82f6` (Bar), et `fill="#8884d8"` — **le violet par défaut de Recharts, resté par oubli**.
- 🟠 **10 cartes KPI toutes identiques visuellement**, sur 3 grilles séparées (4 + 2 + 3) : aucune hiérarchie, l'œil ne sait pas quoi lire en premier.
- 🟠 Contenu KPI centré (`items-center text-center`) : casse le balayage vertical d'un tableau de bord ; l'alignement à gauche est la norme pour des chiffres à comparer.
- 🟠 Pastilles d'icône colorées (`bg-green-100`, `bg-red-100`, `bg-amber-100`, `bg-blue-100`, `bg-purple-100`) — 5 teintes, dont **violet sans justification sémantique**.
- 🟠 Skeletons inline `bg-gray-100 animate-pulse rounded` répétés 8 fois.
- 🟠 `<h1>` de page en `text-xl sm:text-3xl` alors que la topbar affiche déjà un titre.
- 🟡 Graphiques non responsives sous 640px : `angle={-15}` sur l'axe X écrase les libellés.
- 🟡 Tooltip Recharts par défaut : ni police ni couleurs de marque.
- 🟡 Le double `min-h-screen` (layout + page) crée un conteneur imbriqué inutile.
**`/dashboard/bookings` — Liste des réservations** (908 l.)
- 🟠 Menu contextuel positionné manuellement via `menuPos {top, right}` + listener global — se désaligne au scroll, ne gère pas le clavier, sort du viewport en bas de liste.
- 🟠 Table brute en `overflow-x-auto` : 10+ colonnes illisibles sous 1024px.
- 🟠 Filtres (recherche, type de recherche, statut) sans état « filtres actifs » ni réinitialisation.
- 🟠 Pagination réimplémentée localement.
- 🟡 Bandeau `showTransferBanner` géré en `useState` local, sans persistance.
**`/dashboard/documents`** (1 169 l., 25 `useState`, 9 `alert`/`confirm`) — **page la plus dégradée**
- 🔴 **3 modales réimplémentées inline** (ajout / remplacement / suppression) au lieu d'un `Dialog` partagé.
- 🔴 9 `alert()`/`confirm()` natifs pour toute la confirmation de suppression et le retour d'upload.
- 🟠 Deux zones de glisser-déposer distinctes (`isDraggingAdd`, `isDraggingReplace`) avec logique dupliquée.
- 🟠 Aucune barre de progression d'upload : `uploadingFiles` est un booléen global.
- 🟠 Pagination + `itemsPerPage` réimplémentés.
- 🟠 25 `useState` dans un seul composant : re-renders larges, état non testable.
**`/dashboard/search-advanced`** (1 067 l.) + **`/results`** (520 l.)
- 🔴 28 + 18 occurrences de `blue-*` (barre de progression du tunnel, étapes actives, boutons).
- 🟠 Tunnel multi-étapes (`currentStep`) **sans indicateur de progression accessible** ni possibilité de revenir en arrière par l'URL — l'état vit uniquement en mémoire ; un rechargement perd la saisie.
- 🟠 4 `useState` distincts pour les autocomplétions de ports (`originSearch`, `showOriginDropdown`, `destinationSearch`, `showDestinationDropdown`) → composant `PortAutocomplete` à extraire.
- 🟠 Dropdowns d'autocomplétion non portalisés → clipping possible.
- 🟠 Aucune navigation clavier dans les listes de suggestions.
**`/dashboard/bookings/new` (871 l.) vs `/dashboard/booking/new` (736 l.)**
- 🔴 **Deux tunnels de création de réservation coexistent** sous des routes quasi identiques (`bookings/new` et `booking/new`). Duplication majeure, risque de divergence fonctionnelle. À trancher avant refonte.
**`/dashboard/track-trace`** (642 l., 27 `blue-*`)
- 🟠 Timeline de suivi entièrement stylée en bleu générique.
- 🟠 Carte Leaflet (`PortRouteMap`) : hauteur fixe, non responsive ; marqueurs aux couleurs hors charte.
**`/dashboard/settings/*`**
- 🟠 Trois pages de paramètres **sans conteneur commun** : pas d'onglets, pas de sous-navigation. L'utilisateur doit repasser par la sidebar.
- 🟠 `subscription/page.tsx` (27 l.) est une simple redirection — route fantôme dans la nav.
- 🟠 `settings/users` : 2 tables brutes + 3 `confirm()` natifs pour la révocation d'accès (action destructive).
**`/dashboard/profile`** (453 l., 15 `blue-*`)
- 🟠 Formulaire sans état de sauvegarde clair (pas de « modifications non enregistrées »).
- 🟠 Aucun feedback de succès autre qu'un bandeau inline.
---
### B3. Zone Admin
| Route | Objectif | L | B | N | S | PH |
|-------|----------|---|---|---|---|----|
| `/admin` | Redirection | 8 | 0 | 0 | 0 | ❌ |
| `/admin/login` | Connexion admin dédiée | 141 | 0 | 0 | 6 | ❌ |
| `/admin/users` | Gestion utilisateurs | 507 | 15 | 3 | 11 | ✅ |
| `/admin/organizations` | Gestion organisations | 832 | 8 | 9 | 12 | ✅ |
| `/admin/bookings` | Réservations globales | 735 | 6 | 2 | 12 | ❌ |
| `/admin/documents` | Documents globaux | 684 | 10 | 2 | 13 | ✅ |
| `/admin/csv-rates` | Import grilles tarifaires | 209 | 1 | 3 | 4 | ❌ |
| `/admin/blog` | Éditeur d'articles | 1341 | 30 | 11 | 19 | ✅ |
| `/admin/logs` | Journaux d'audit | 547 | 2 | 0 | 9 | ✅ |
- 🔴 `/admin/blog` : **1 341 lignes, 19 `useState`, 11 `alert`/`confirm`, 30 `blue-*`, 24 `<button>` bruts.** Le fichier le plus lourd du projet. Éditeur TipTap + liste + formulaire SEO + planification, tout dans un composant.
- 🔴 `/admin/organizations` : 9 `alert`/`confirm` — dont la validation SIRET et la suspension d'organisation (actions à fort impact) confirmées par `confirm()` natif.
- 🟠 Le shell admin est navy mais **le contenu des pages reste en bleu générique** : rupture visuelle entre chrome et contenu.
- 🟠 5 tables brutes, aucune mutualisée, chacune avec ses propres filtres et sa propre pagination.
- 🟠 `/admin/csv-rates` : import de fichier sans prévisualisation ni rapport d'erreur structuré (3 `alert()`).
- 🟠 `/admin/logs` : 547 lignes de journal sans virtualisation (`@tanstack/react-virtual` est pourtant installé).
- 🟡 `/admin/login` : page de connexion distincte de `/login`, avec un design différent — duplication de formulaire d'auth.
---
### B4. Zone Wiki
13 routes : `page` (index) · `incoterms` · `conteneurs` · `lcl-vs-fcl` · `transit-time` · `calcul-fret` · `documents-transport` · `douanes` · `imdg` · `vgm` · `assurance` · `lettre-credit` · `ports-routes`.
**Objectif :** contenu pédagogique métier (fret maritime) accessible aux clients selon leur plan.
| Constat | Type |
|---------|------|
| 🟠 **13 pages statiques rédigées en JSX en dur** (110–244 lignes chacune) : le contenu éditorial est dans le code | Architecture |
| 🟠 **93 occurrences `blue-*` cumulées** sur la zone : encadrés d'information, tableaux, badges — tous en bleu générique | 🔴 UI |
| 🟠 10 tables brutes dispersées, chacune avec son propre style | Duplication |
| 🟠 Pas de sommaire, pas d'ancres, pas de « page suivante/précédente », pas de recherche dans le wiki | UX |
| 🟠 Pas de breadcrumb : impossible de savoir où l'on est dans l'arborescence | UX |
| 🟠 Aucune largeur de lecture contrainte (`max-w-prose`) sur certaines pages → lignes de 1 200px, illisibles | Lisibilité |
| 🟡 Le plugin `@tailwindcss/typography` est installé mais n'est pas utilisé pour ces pages | Manque |
| 🟡 Encadrés d'information (`bg-blue-50 border-blue-200`) réécrits ~30 fois → composant `Callout` évident | Duplication |
---
### B5. Zone Auth
| Route | Objectif | L | B | S |
|-------|----------|---|---|---|
| `/login` | Connexion | 523 | 0 | 10 |
| `/register` | Inscription (multi-étapes) | 832 | 0 | 25 |
| `/forgot-password` | Demande de réinitialisation | 264 | 0 | 5 |
| `/reset-password` | Réinitialisation | 336 | 0 | 10 |
| `/verify-email` | Vérification e-mail | 151 | 6 | 4 |
| `/admin/login` | Connexion admin | 141 | 0 | 6 |
- ✅ **Zone la plus conforme à la charte** (0 `blue-*` sauf `/verify-email`).
- 🟠 `/register` : **832 lignes, 25 `useState`** — tunnel multi-étapes (compte / organisation / SIRET / plan) sans machine à états ni validation par étape mutualisée. `react-hook-form` + `zod` sont installés mais non utilisés ici.
- 🟠 4 mises en page d'auth distinctes (`/login` avec image de fond, `/register` pleine largeur, `/forgot-password` centré, `/admin/login` minimal) → aucun `AuthLayout` partagé.
- 🟠 `/verify-email` : 6 `blue-*` — seule fuite de la zone.
- 🟠 Pas de mesure de robustesse du mot de passe visible sur `/register` ni `/reset-password`.
- 🟡 États d'erreur de formulaire en bandeaux inline, différents d'une page à l'autre.
---
### B6. Portail Transporteur & Booking public
| Route | Objectif | L | B | S |
|-------|----------|---|---|---|
| `/carrier/accept/[token]` | Acceptation par magic link | 144 | 3 | 4 |
| `/carrier/reject/[token]` | Refus par magic link | 144 | 1 | 4 |
| `/carrier/documents/[token]` | Dépôt de documents transporteur | 589 | 0 | 10 |
| `/booking/confirm/[token]` | Confirmation client | 300 | 7 | 4 |
| `/booking/reject/[token]` | Refus client | 363 | 4 | 8 |
- 🟠 **`accept` et `reject` sont deux fichiers de 144 lignes quasi identiques** (même structure, même mise en page, variable d'action différente).
- 🟠 Même remarque pour `booking/confirm` (300 l.) et `booking/reject` (363 l.).
- 🟠 Aucun chrome : ces pages sont sans en-tête ni logo persistant — le transporteur ne sait pas sur quelle plateforme il se trouve.
- 🟠 Pas d'état d'expiration de lien distinct de l'état d'erreur générique (le magic link expire à 1 h).
- 🟠 `/carrier/documents/[token]` (589 l.) : upload sans progression, 1 `alert()` natif.
- 🟡 Pas de version imprimable des pages de confirmation.
---
### B7. Zone Marketing / Public
| Route | Objectif | L | B |
|-------|----------|---|---|
| `/` | Landing | 1082 | 1 |
| `/pricing` | Grille tarifaire | 334 | 0 |
| `/about` | À propos | 414 | 1 |
| `/contact` | Contact | 681 | 1 |
| `/careers` | Recrutement | 574 | 0 |
| `/press` | Presse | 509 | 0 |
| `/security` | Sécurité | 274 | 0 |
| `/compliance` | Conformité | 361 | 0 |
| `/privacy` `/terms` `/cookies` | Légal | 182 / 190 / 313 | 0 |
**Composants :** `LandingHeader` (388 l.), `LandingFooter`, `DemoVideoModal`, `CookieConsent`, `framer-motion`.
- ✅ **Charte respectée.** C'est la référence visuelle de la marque.
- 🟠 `/` (1 082 l.) : page monolithique, aucune section extraite en composant.
- 🟠 Rayons `rounded-2xl` / `rounded-3xl` et `shadow-2xl` employés ici uniquement → **écart de langage visuel avec le produit** (`rounded-lg`, `shadow-sm`).
- 🟠 Dégradés navy avec nuances inventées (`#1A2550`, `#1A2A5E`) hors charte.
- 🟠 8 pages légales/institutionnelles avec 8 mises en page rédactionnelles différentes → un `LegalPageLayout` s'impose.
- 🟠 Logos partenaires : fichiers PNG aux noms contenant des espaces (`Rectangle 4.png`, `TCC LOG 1.png`) — fragile en URL, et `Rectangle 4.png` / `image 1.png` ne sont pas nommés.
- 🟡 `framer-motion` (16 fichiers) concentré ici : animations d'apparition au scroll, sans `prefers-reduced-motion`.
---
### B8. Blog & Docs
| Route | Objectif | L |
|-------|----------|---|
| `/blog` | Liste des articles | 424 |
| `/blog/[slug]` | Article (+ `BlogPostContent` 350 l.) | 152 |
| `/docs/api` | Documentation API | 7 |
| `/docs/layout.tsx` | Shell docs | — |
**Composants :** `DocsPageContent` (**1 506 lignes — le plus gros fichier du projet**), `CodeBlock`, `docsNav.ts`, `RichTextEditor` (TipTap, 381 l.).
- 🔴 `DocsPageContent.tsx` : 1 506 lignes, contenu de documentation API entièrement en JSX. Non maintenable, non recherchable.
- 🟠 `CodeBlock` utilise un thème GitHub Dark hardcodé (`#0D1117`, `#161B22`) sans lien avec la charte.
- 🟠 Le blog utilise `.prose` (`@tailwindcss/typography`) avec des overrides de marque dans `globals.css` — **le seul endroit du projet où la typographie éditoriale est correctement traitée**. Le wiki devrait suivre le même modèle.
- 🟡 Pas de recherche ni d'ancrage dans les docs.
---
### B9. Routes techniques / mortes
| Route | Statut |
|-------|--------|
| `/test-image` (49 l.) | 🔴 Page de test à supprimer |
| `/demo-carte` (72 l.) | 🔴 Démo Leaflet à supprimer (6 `blue-*`) |
| `/dashboard/docs` (7 l.) | Redirection |
| `/dashboard/admin/[[...rest]]` (11 l.) | Redirection legacy vers `/admin` |
| `/dashboard/settings/subscription` (27 l.) | Redirection |
| `src/app/rates/csv-search/page.tsx` | 🟠 **Page hors `app/[locale]`** — non internationalisée, non protégée par le middleware, hors du shell. Route orpheline. |
| `src/legacy-pages/` (`BookingsManagement`, `CarrierManagement`, `CarrierMonitoring`) | 🟠 Code mort (3 `alert()` dans `CarrierManagement`) |
| `src/components/examples/DesignSystemShowcase.tsx` (320 l.) | 🟡 Vitrine du DS — à conserver et mettre à jour, ou supprimer |
---
## C. Composants à mutualiser
### C1. À créer (n'existent pas)
| Composant | Remplace | Occurrences éliminées |
|-----------|----------|----------------------|
| **`Toast` / `Toaster`** | `alert()`, `confirm()` natifs, bandeaux inline | **58 appels natifs** |
| **`ConfirmDialog`** | `confirm()` natif sur actions destructives | ~14 |
| **`Skeleton`** | `bg-gray-100 animate-pulse rounded` inline | ~30 |
| **`EmptyState`** | Blocs vides réécrits à la main | **27** |
| **`ErrorState`** | Bandeaux d'erreur inline | **29** |
| **`Spinner`** | `animate-spin` + `Loader2` ad hoc | **40 fichiers** |
| **`Breadcrumb`** | — | 0 (manque total) |
| **`Pagination`** | Paginations locales | **7 implémentations** |
| **`DataTable`** (sur `@tanstack/react-table`) | `<table>` bruts + tri + filtres + responsive | **26 tables** |
| **`Drawer`** | Panneaux latéraux ad hoc | — |
| **`CommandMenu`** (⌘K) | — | 0 (manque total) |
| **`Callout`** | Encadrés `bg-blue-50 border-blue-200` du wiki | ~30 |
| **`AuthLayout`** | 4 mises en page d'auth distinctes | 4 |
| **`SettingsLayout`** | Sous-navigation des paramètres | 3 pages |
| **`AppShell`** | Base commune dashboard/admin | 2 layouts à 80 % dupliqués |
| **`FormField`** | `<label>` + `<input>` + erreur | **175 inputs bruts** |
| **`PortAutocomplete`** | Autocomplétion de ports | 2× dupliquée |
| **`FileDropzone`** | Zones de glisser-déposer | 4 implémentations |
| **`StatCard`** | Cartes KPI | 10 sur `/dashboard`, + admin |
### C2. À réparer (existent mais cassés ou non adoptés)
| Composant | Action |
|-----------|--------|
| `ui/button.tsx` | Réécrire sur `cva` + `cn()` + `asChild` (Radix Slot). Corriger `hover:bg-accent` (turquoise plein). Ajouter `loading`, `active:`. |
| `ui/input.tsx` | Passer le focus sur le turquoise de marque. Ajouter états `error`, préfixe/suffixe, tailles. |
| `ui/select.tsx` | **Réécrire sur `@radix-ui/react-select`** (déjà installé) — `SelectValue` est non fonctionnel. |
| `ui/dialog.tsx` | **Réécrire sur `@radix-ui/react-dialog`** — portal, focus trap, `Escape`, scroll-lock, animations. |
| `ui/popover.tsx` | **Réécrire sur `@radix-ui/react-popover`** — portalisation. |
| `ui/command.tsx` | Compléter ou fonder le `CommandMenu`. |
| `ui/card.tsx` | Ramener `CardTitle` à une taille réaliste (`text-base`/`text-lg`), padding par défaut `p-4`/`p-5`. |
| `ui/table.tsx` | Base du `DataTable` ; densités compacte/confortable. |
| `ui/badge.tsx` | Ajouter les variants sémantiques réellement utilisés (success, warning, info, neutral) avec les tokens de charte. |
| `ui/PageHeader.tsx` | Adopté sur 9 pages seulement. Ajouter breadcrumb, onglets, description, actions secondaires. Généraliser. |
| `ui/alert.tsx` | Aligner sur `Callout` ou fusionner. |
| `ui/FeatureGate.tsx` | **0 usage** — le gating est réimplémenté dans `dashboard/layout.tsx`. À adopter ou supprimer. |
---
## D. Duplications identifiées
| # | Duplication | Fichiers | Volume |
|---|-------------|----------|--------|
| D1 | **Deux tunnels de création de réservation** | `dashboard/bookings/new` (871 l.) + `dashboard/booking/new` (736 l.) | 1 607 l. |
| D2 | **Shell applicatif** | `dashboard/layout.tsx` + `admin/layout.tsx` | ~80 % commun |
| D3 | **Pages accept/reject transporteur** | `carrier/accept/[token]` + `carrier/reject/[token]` | 2 × 144 l. |
| D4 | **Pages confirm/reject booking** | `booking/confirm/[token]` + `booking/reject/[token]` | 663 l. |
| D5 | **Formulaires de connexion** | `/login` + `/admin/login` | 664 l. |
| D6 | **Systèmes de bouton** | `ui/button.tsx` + `.btn-*` (globals.css) + 299 `<button>` bruts | 3 systèmes |
| D7 | **Systèmes d'input** | `ui/input.tsx` + `.input` (globals.css) + 175 `<input>` bruts | 3 systèmes |
| D8 | **Paginations** | 7 implémentations locales | — |
| D9 | **Zones de glisser-déposer** | `documents` (×2), `admin/documents`, `carrier/documents` | 4 |
| D10 | **Encadrés d'information** | 13 pages wiki | ~30 |
| D11 | **Mises en page légales** | 8 pages institutionnelles | 8 |
| D12 | **Icônes burger/close en SVG inline** | 2 layouts | doublon de lucide |
| D13 | **Échelles neutres concurrentes** | `gray-*` (2 485) vs `neutral-*` (92) | — |
---
## E. Tableau de priorisation
| Lot | Contenu | Impact | Effort | Ordre |
|-----|---------|--------|--------|-------|
| **L0 — Fondations** | Réparer les primitives (`Button`, `Input`, `Select`, `Dialog`, `Popover` sur Radix) · `cn()` partout · corriger `--ring` · ajouter `Toast`, `Skeleton`, `EmptyState`, `ErrorState`, `Spinner`, `Pagination`, `Breadcrumb`, `Callout`, `FormField` | 🔴 Maximal | M | 1 |
| **L1 — App shell** | `AppShell` mutualisé · sidebar repliable · suppression du double titre · breadcrumb · bottom-nav complète · command menu ⌘K · **restauration charte navy/turquoise sur le chrome** | 🔴 Maximal | M | 2 |
| **L2 — Tables & listes** | `DataTable` sur `@tanstack/react-table` · responsive carte-sur-mobile · tri/filtres/pagination mutualisés · virtualisation des logs | 🔴 Fort | L | 3 |
| **L3 — Dashboard & réservations** | `/dashboard` (hiérarchie KPI, charts à la charte) · `/bookings` · arbitrage D1 · `/bookings/[id]` | 🔴 Fort | L | 4 |
| **L4 — Documents & recherche** | `/documents` (démonolithisation, `FileDropzone`, modales Radix) · `/search-advanced` (état en URL, `PortAutocomplete`) · `/track-trace` | 🔴 Fort | L | 5 |
| **L5 — Paramètres & profil** | `SettingsLayout` avec onglets · `/profile` · `/settings/*` | 🟠 Moyen | M | 6 |
| **L6 — Admin** | Alignement chrome/contenu · démonolithisation de `/admin/blog` · suppression des `alert`/`confirm` | 🟠 Moyen | L | 7 |
| **L7 — Wiki & docs** | `Callout` · `.prose` · sommaire + ancres · largeur de lecture · breadcrumb | 🟠 Moyen | M | 8 |
| **L8 — Auth & portails** | `AuthLayout` · fusion accept/reject · états d'expiration de lien · robustesse mot de passe | 🟠 Moyen | S | 9 |
| **L9 — Marketing** | Extraction en sections · alignement des rayons/ombres sur le produit · suppression des nuances inventées | 🟡 Faible | M | 10 |
| **L10 — Nettoyage** | Supprimer `/test-image`, `/demo-carte`, `src/legacy-pages/` · trancher `src/app/rates/csv-search` · `gray-*` → `neutral-*` | 🟡 Faible | S | 11 |
**Effort :** S = < 1 j · M = 1–3 j · L = 3–5 j
---
## F. Points nécessitant un arbitrage produit
Ces questions ne peuvent pas être tranchées par l'audit seul :
1. **D1** — Lequel des deux tunnels de réservation (`bookings/new` vs `booking/new`) est le tunnel actif ? L'autre peut-il être supprimé ?
2. **`src/app/rates/csv-search`** — route orpheline hors i18n et hors middleware : à migrer sous `app/[locale]/dashboard/` ou à supprimer ?
3. **`src/legacy-pages/`** — code mort confirmé ? Suppression autorisée ?
4. **Dark mode** — l'implémenter réellement, ou retirer le scaffolding `.dark` mort ?
5. **Wiki** — le contenu reste-t-il en JSX, ou passe-t-il en MDX / CMS ?
6. **§23 du design audit** — validation explicite du remplacement `blue-*` → tokens de marque sur les 64 fichiers concernés.