Commit Graph

4 Commits

Author SHA1 Message Date
Arnaud 8ab02409f6 fix(admin): clic sur nom de membre -> écran d'édition (était un lien mort)
Le lien sur display_name dans la liste /admin/members pointait sur
`project-detail` avec le slug du MEMBRE comme :slug -> aboutissait sur
une 404 ou la mauvaise fiche projet selon les collisions de slug.

Maintenant : pointe sur `admin-member-edit`. Plus cohérent avec le pattern
utilisé pour les projets (clic sur titre -> fiche) et avec ce que l'admin
attend dans un tableau d'admin (clic = édition).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 13:50:42 +02:00
Arnaud 97c68531ed feat(admin): création de membre via dashboard
Nouvelle page /admin/members/new, jumeau de l'écran d'édition :
  - Form complet : display_name, slug (auto), initials (auto), kind, bio,
    email, website, avatar URL
  - Avatar preview live (initiales / URL d'avatar mis à jour à la saisie)
  - Slug auto-généré depuis display_name (NFD + lowercase + tirets)
    tant que l'utilisateur n'a pas édité manuellement (flag slugDirty)
  - Initiales auto-suggérées (1re lettre de chaque mot, max 3, upper),
    flag initialsDirty distinct -> tu peux laisser le slug auto mais
    forcer des initiales manuelles ou vice-versa
  - Body construit en omettant les champs vides -> l'API génère ses propres
    initiales si vides, slug auto-uniqueifie sur collision (-2/-3...)
  - 201 -> redirige vers /admin/members/:slug/edit pour voir le résultat
  - 422/409/401 gérés comme les autres écrans admin

UI
  - AdminMembersPage : CTA "Nouveau membre" en haut à droite (à côté de
    la recherche, justify-between sur le wrap parent)
  - Router : route nommée admin-member-new avec guard admin

Backend
  - Aucune nouvelle route : POST /api/members existait déjà
  - Le contrôleur valide display_name (required), kind enum, email format,
    URLs ; auto-génère initiales si non fournies (1res lettres de chaque mot)
  - Slug auto avec suffixe en cas de collision (cf. Slugger::unique)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 13:47:42 +02:00
Arnaud cb37b461c5 feat(admin): édition de membre via dashboard
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>
2026-05-29 13:39:33 +02:00
Arnaud adb2bd3fc1 feat: galerie d'images projet (upload disque) + dashboard /admin
==== 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>
2026-05-28 23:07:59 +02:00