Nouvelle page /admin/categories/new, jumeau de l'écran d'édition :
- Form complet : name, slug (auto), description, color_hex, icon,
dimension, sort_order
- Preview live identique à l'éditeur (top border + folder tinté + badge)
- Slug auto-généré depuis le nom (normalisation NFD + lowercase + dashes)
tant que l'utilisateur n'a pas touché manuellement au champ. Flag
slugDirty mis à true sur premier @input du slug -> on arrête l'auto.
- Validation color_hex /^#RRGGBB$/ côté client + normalisation upper-case
- Body construit en omettant les champs vides -> l'API applique ses
défauts (color_hex='#888888', dimension='transversal', sort_order=0)
- Sur 201 : redirige vers /admin/categories/:slug/edit pour que l'admin
voie tout de suite sa nouvelle catégorie et puisse ajuster
- 422/409/401 gérés comme sur les autres écrans admin
UI
- AdminCategoriesPage : CTA "Nouvelle catégorie" en haut à droite
- Router : route nommée admin-category-new avec guard admin
Backend
- Aucune nouvelle route : POST /api/categories existait déjà
- Le contrôleur valide name (required, max 128), dimension enum, color
hex format, slug auto-unique
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Nouvelle page /admin/categories/:slug/edit, même pattern que members :
- Preview live de la carte catégorie (top border colorée + folder icon
tinté + badge "X projets" + label dimension)
- Form complet : name, slug, description, color_hex, icon, dimension, sort_order
- Color picker natif <input type="color"> + champ HEX texte synchronisés,
validation /^#[0-9A-Fa-f]{6}$/ côté client
- Diff PATCH (champs vides -> null), upper-case normalisé pour color_hex
- Si le slug change -> router.replace vers la nouvelle URL
- Bouton Supprimer en header désactivé si projects_count > 0 (FK RESTRICT)
- 422 -> fieldErrors, 409 -> slug collision, 401 -> session expirée
- projects_count récupéré direct depuis GET /api/categories/:slug
(la réponse contient ce champ pour la route détail, cf. controller PHP)
UI
- AdminCategoriesPage : nouveau lien "Éditer" par ligne du tableau
- Router : route nommée admin-category-edit avec guard admin
Backend
- Aucune nouvelle route : PATCH /api/categories/:slug existait déjà
- Validation côté serveur : in dimension enum, colorHex format,
integer sort_order >= 0, slug auto-unique avec suffixe -2/-3...
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Nouvelle page /admin/members/:slug/edit avec :
- Preview avatar live (initiales/URL mis à jour à la saisie)
- Form complet : display_name, slug, initials, kind, bio, email, website, avatar
- Envoie un diff au PATCH (champs vides -> null, pas chaîne vide)
- Si le slug change, redirige automatiquement vers la nouvelle URL
- Encart "Projets attachés" en lecture (avec lien vers chaque fiche)
pour rappeler l'impact d'une suppression ou d'un changement de slug
- Bouton Supprimer en header avec confirm qui mentionne le nb de projets
- Banner vert "Modifications enregistrées" sur succès
- Gestion erreurs : 422 -> fieldErrors, 401, 409 (slug collision), réseau
Backend
- Aucune nouvelle route : PATCH /api/members/:slug existait déjà
- Le contrôleur gère déjà slug auto-unique, initials, kind enum, etc.
UI
- AdminMembersPage : nouveau lien "Éditer" par ligne, à côté de Supprimer
- Router : route nommée admin-member-edit avec guard admin
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
==== Galerie d'images ====
Backend
- Migration 003 : table project_images (FK CASCADE sur projects)
- ImagesController : CRUD complet, deux modes de création
* URL externe via JSON body
* Upload disque via multipart/form-data avec :
- validation MIME RÉELLE via finfo (jpeg/png/webp/gif)
- taille max 5 Mo, vérifiée explicitement (au-delà du php.ini)
- vérification UPLOAD_ERR_* avec messages clairs
- stockage dans public/uploads/projects/:slug/<hash>.<ext>
- dimensions image extraites via getimagesize (best-effort)
- row DB avec mime_type / size_bytes / width / height
- Request : ajout isMultipart(), formField(), file()
- public/index.php : 3 routes (index public, store/update/destroy admin)
Proxy
- caddy.conf : /uploads/* ajouté au reverse_proxy comme /api/*
- vite.config.ts : pareil pour le dev local
Frontend
- Type ProjectImage (api.ts) avec url + storage_path + dimensions
- uploadFile() helper dans client.ts (FormData + Bearer)
- ImageGallery.vue : grille thumbnails + lightbox plein-écran avec
nav clavier (← → Esc), bouton supprimer par image en mode admin
- ImageUploader.vue : drag-drop + file picker, multi-fichiers en série,
validation taille côté client, légende commune optionnelle
- ProjectDetailPage : nouvel onglet Images entre Roadmap et Discussions
==== Dashboard /admin ====
5 nouvelles pages sous /admin :
- /admin : KPI + répartition catégorie + flux activité
- /admin/projects : table CRUD (recherche + filtres status/categorie),
actions Éditer / Archiver / Supprimer par ligne
- /admin/projects/:slug/edit : form complet d'édition (envoie un diff au PATCH)
- /admin/members : annuaire avec recherche + Supprimer
- /admin/categories : table avec compte de projets ;
Supprimer désactivé si la catégorie est utilisée
- AdminSubNav.vue : 4 onglets en haut de chaque page admin
Garde d'auth :
- beforeEnter sur les routes /admin/* (sauf /admin/login)
- Redirige vers /admin/login?next=<url> si pas de token
- Après login, retour automatique vers next= (sinon /admin)
Sidebar :
- Nouvelle section "Administration" visible UNIQUEMENT quand le token
est en localStorage, avec un lien vers le dashboard
Vérifié localement
- Build vue-tsc : 91 modules, OK
- Upload disque : fichier PNG stocké dans public/uploads/, accessible via /uploads/
- GET /api/projects/nos-ecoles/images : 2 entrées renvoyées (1 URL + 1 disque)
- Toutes les routes /admin/* répondent 200 via le proxy Vite
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>