# CRUD Clients & Projets - Plan d'implémentation > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **Goal:** Implémenter le CRUD complet Client et Projet (backend API Platform + frontend Nuxt) avec pages dédiées, drawers latéraux pour ajout/modification, et fixtures de données. **Architecture:** Deux entités Doctrine (Client, Project) avec relation ManyToOne optionnelle Project→Client. API Platform gère le CRUD via les attributs sur les entités (pas de controllers). Frontend avec pages dédiées : tableau pour les clients, cartes colorées pour les projets, drawers droits pour les formulaires. **Tech Stack:** PHP 8.4 / Symfony 8 / API Platform 4 / Doctrine ORM / PostgreSQL (backend) — Nuxt 4 / Vue 3 / Pinia / Tailwind CSS / Malio UI Layer (frontend) --- ## Task 1 : Entité Client (Backend) **Files:** - Create: `src/Entity/Client.php` - Create: `src/Repository/ClientRepository.php` **Step 1: Créer le repository Client** ```php ['client:read']], denormalizationContext: ['groups' => ['client:write']], order: ['name' => 'ASC'], )] #[ORM\Entity(repositoryClass: ClientRepository::class)] class Client { #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column] #[Groups(['client:read', 'project:read'])] private ?int $id = null; #[ORM\Column(length: 255)] #[Groups(['client:read', 'client:write', 'project:read'])] private ?string $name = null; #[ORM\Column(length: 255, nullable: true)] #[Groups(['client:read', 'client:write'])] private ?string $email = null; #[ORM\Column(length: 50, nullable: true)] #[Groups(['client:read', 'client:write'])] private ?string $phone = null; #[ORM\Column(length: 255, nullable: true)] #[Groups(['client:read', 'client:write'])] private ?string $street = null; #[ORM\Column(length: 255, nullable: true)] #[Groups(['client:read', 'client:write'])] private ?string $city = null; #[ORM\Column(length: 20, nullable: true)] #[Groups(['client:read', 'client:write'])] private ?string $postalCode = null; /** @var Collection */ #[ORM\OneToMany(targetEntity: Project::class, mappedBy: 'client')] private Collection $projects; public function __construct() { $this->projects = new ArrayCollection(); } // Getters et setters pour chaque propriété (getId, getName/setName, getEmail/setEmail, // getPhone/setPhone, getStreet/setStreet, getCity/setCity, getPostalCode/setPostalCode, // getProjects) // Suivre le même pattern fluent (return $this) que User.php } ``` **Step 3: Générer et exécuter la migration** Run (dans le container PHP) : ```bash php bin/console doctrine:migrations:diff php bin/console doctrine:migrations:migrate --no-interaction ``` Expected: Migration créée et exécutée, table `client` créée en base. **Step 4: Vérifier l'API** Run: `curl -s http://localhost:8082/api/clients | head -20` (après login) Expected: Réponse Hydra vide `{"hydra:member":[],"hydra:totalItems":0}` **Step 5: Commit** ```bash git add src/Entity/Client.php src/Repository/ClientRepository.php migrations/ git commit -m "feat : add Client entity with CRUD API" ``` --- ## Task 2 : Entité Project (Backend) **Files:** - Create: `src/Entity/Project.php` - Create: `src/Repository/ProjectRepository.php` **Step 1: Créer le repository Project** ```php ['project:read']], denormalizationContext: ['groups' => ['project:write']], order: ['name' => 'ASC'], )] #[ORM\Entity(repositoryClass: ProjectRepository::class)] class Project { #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column] #[Groups(['project:read'])] private ?int $id = null; #[ORM\Column(length: 255)] #[Groups(['project:read', 'project:write'])] private ?string $name = null; #[ORM\Column(type: 'text', nullable: true)] #[Groups(['project:read', 'project:write'])] private ?string $description = null; #[ORM\Column(length: 7)] #[Groups(['project:read', 'project:write'])] private ?string $color = '#222783'; #[ORM\ManyToOne(targetEntity: Client::class, inversedBy: 'projects')] #[ORM\JoinColumn(nullable: true, onDelete: 'SET NULL')] #[Groups(['project:read', 'project:write'])] private ?Client $client = null; // Getters et setters pour chaque propriété (getId, getName/setName, getDescription/setDescription, // getColor/setColor, getClient/setClient) // Suivre le même pattern fluent (return $this) que User.php } ``` **Note importante pour API Platform :** Le champ `client` en écriture accepte un IRI (ex: `"/api/clients/1"`) ou `null`. **Step 3: Générer et exécuter la migration** Run (dans le container PHP) : ```bash php bin/console doctrine:migrations:diff php bin/console doctrine:migrations:migrate --no-interaction ``` Expected: Migration créée, table `project` avec foreign key vers `client`. **Step 4: Commit** ```bash git add src/Entity/Project.php src/Repository/ProjectRepository.php migrations/ git commit -m "feat : add Project entity with CRUD API and Client relation" ``` --- ## Task 3 : Fixtures Clients & Projets **Files:** - Modify: `src/DataFixtures/AppFixtures.php` **Step 1: Ajouter les fixtures** Modifier `AppFixtures.php` pour ajouter des clients et projets après la création du user admin : ```php setUsername('admin'); $admin->setRoles(['ROLE_ADMIN']); $admin->setPassword($this->passwordHasher->hashPassword($admin, 'admin')); $manager->persist($admin); // Clients $clientLiot = new Client(); $clientLiot->setName('LIOT'); $clientLiot->setEmail('contact@liot.fr'); $clientLiot->setPhone('05 50 50 50 50'); $clientLiot->setStreet('14 allée d\'argenson'); $clientLiot->setCity('Poitiers'); $clientLiot->setPostalCode('86100'); $manager->persist($clientLiot); $clientAcme = new Client(); $clientAcme->setName('ACME Corp'); $clientAcme->setEmail('contact@acme.com'); $clientAcme->setPhone('01 23 45 67 89'); $clientAcme->setStreet('10 rue de la Paix'); $clientAcme->setCity('Paris'); $clientAcme->setPostalCode('75002'); $manager->persist($clientAcme); $clientNova = new Client(); $clientNova->setName('Nova Tech'); $clientNova->setEmail('info@novatech.io'); $clientNova->setPhone('04 56 78 90 12'); $clientNova->setStreet('5 avenue Jean Jaurès'); $clientNova->setCity('Lyon'); $clientNova->setPostalCode('69007'); $manager->persist($clientNova); // Projets $colors = ['#222783', '#E91E63', '#4A90D9', '#7E57C2', '#26A69A', '#FDD835', '#8BC34A', '#FF7043']; $projectSirh = new Project(); $projectSirh->setName('SIRH'); $projectSirh->setDescription('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer ac blandit turpis.'); $projectSirh->setColor($colors[0]); $projectSirh->setClient($clientLiot); $manager->persist($projectSirh); $projectCrm = new Project(); $projectCrm->setName('CRM'); $projectCrm->setDescription('Gestion de la relation client et suivi commercial.'); $projectCrm->setColor($colors[1]); $projectCrm->setClient($clientAcme); $manager->persist($projectCrm); $projectErp = new Project(); $projectErp->setName('ERP'); $projectErp->setDescription('Planification des ressources et gestion des stocks.'); $projectErp->setColor($colors[2]); $projectErp->setClient($clientNova); $manager->persist($projectErp); $projectInterne = new Project(); $projectInterne->setName('Site vitrine'); $projectInterne->setDescription('Refonte du site web corporate.'); $projectInterne->setColor($colors[4]); $projectInterne->setClient(null); $manager->persist($projectInterne); $manager->flush(); } } ``` **Step 2: Recharger les fixtures** Run: `make fixtures` ou dans le container : `php bin/console doctrine:fixtures:load --no-interaction` Expected: 1 user, 3 clients, 4 projets créés. **Step 3: Commit** ```bash git add src/DataFixtures/AppFixtures.php git commit -m "feat : add Client and Project fixtures" ``` --- ## Task 4 : DTOs & Service Client (Frontend) **Files:** - Create: `frontend/services/dto/client.ts` - Create: `frontend/services/clients.ts` - Modify: `frontend/utils/api.ts` (créer si inexistant — utilitaire Hydra) **Step 1: Créer l'utilitaire Hydra** Créer `frontend/utils/api.ts` : ```typescript export type HydraCollection = { 'hydra:member': T[] 'hydra:totalItems': number } export function extractHydraMembers(response: HydraCollection): T[] { return response['hydra:member'] ?? [] } ``` **Step 2: Créer le DTO Client** Créer `frontend/services/dto/client.ts` : ```typescript export type Client = { id: number '@id'?: string name: string email: string | null phone: string | null street: string | null city: string | null postalCode: string | null } export type ClientWrite = { name: string email: string | null phone: string | null street: string | null city: string | null postalCode: string | null } ``` **Step 3: Créer le service Client** Créer `frontend/services/clients.ts` : ```typescript import type { Client, ClientWrite } from './dto/client' import type { HydraCollection } from '~/utils/api' import { extractHydraMembers } from '~/utils/api' export function useClientService() { const api = useApi() async function getAll(): Promise { const data = await api.get>('/clients') return extractHydraMembers(data) } async function create(payload: ClientWrite): Promise { return api.post('/clients', payload as Record, { toastSuccessKey: 'clients.created', }) } async function update(id: number, payload: Partial): Promise { return api.patch(`/clients/${id}`, payload as Record, { toastSuccessKey: 'clients.updated', }) } async function remove(id: number): Promise { await api.delete(`/clients/${id}`, {}, { toastSuccessKey: 'clients.deleted', }) } return { getAll, create, update, remove } } ``` **Step 4: Commit** ```bash git add frontend/utils/api.ts frontend/services/dto/client.ts frontend/services/clients.ts git commit -m "feat : add Client DTO, service and Hydra utils (frontend)" ``` --- ## Task 5 : DTOs & Service Project (Frontend) **Files:** - Create: `frontend/services/dto/project.ts` - Create: `frontend/services/projects.ts` **Step 1: Créer le DTO Project** Créer `frontend/services/dto/project.ts` : ```typescript import type { Client } from './client' export type Project = { id: number '@id'?: string name: string description: string | null color: string client: Client | null } export type ProjectWrite = { name: string description: string | null color: string client: string | null // IRI : "/api/clients/1" ou null } ``` **Step 2: Créer le service Project** Créer `frontend/services/projects.ts` : ```typescript import type { Project, ProjectWrite } from './dto/project' import type { HydraCollection } from '~/utils/api' import { extractHydraMembers } from '~/utils/api' export function useProjectService() { const api = useApi() async function getAll(): Promise { const data = await api.get>('/projects') return extractHydraMembers(data) } async function create(payload: ProjectWrite): Promise { return api.post('/projects', payload as Record, { toastSuccessKey: 'projects.created', }) } async function update(id: number, payload: Partial): Promise { return api.patch(`/projects/${id}`, payload as Record, { toastSuccessKey: 'projects.updated', }) } async function remove(id: number): Promise { await api.delete(`/projects/${id}`, {}, { toastSuccessKey: 'projects.deleted', }) } return { getAll, create, update, remove } } ``` **Step 3: Commit** ```bash git add frontend/services/dto/project.ts frontend/services/projects.ts git commit -m "feat : add Project DTO and service (frontend)" ``` --- ## Task 6 : Composant Drawer réutilisable **Files:** - Create: `frontend/components/AppDrawer.vue` **Step 1: Créer le composant Drawer** Créer `frontend/components/AppDrawer.vue` : ```vue ``` **Step 2: Vérifier le rendu** Tester en important temporairement dans une page existante (optionnel). **Step 3: Commit** ```bash git add frontend/components/AppDrawer.vue git commit -m "feat : add reusable AppDrawer component" ``` --- ## Task 7 : Page Clients (Frontend) **Files:** - Create: `frontend/pages/clients.vue` - Create: `frontend/components/ClientDrawer.vue` **Step 1: Créer le formulaire drawer Client** Créer `frontend/components/ClientDrawer.vue` : ```vue ``` **Step 2: Créer la page Clients** Créer `frontend/pages/clients.vue` : ```vue ``` **Step 3: Vérifier le rendu** Run: `make dev-nuxt` puis naviguer vers `/clients` Expected: Tableau vide (ou avec données si fixtures chargées), bouton "+ Ajouter un client", drawer s'ouvre au clic. **Step 4: Commit** ```bash git add frontend/pages/clients.vue frontend/components/ClientDrawer.vue git commit -m "feat : add Clients page with table and drawer form" ``` --- ## Task 8 : Page Projets (Frontend) **Files:** - Create: `frontend/pages/projects.vue` - Create: `frontend/components/ProjectDrawer.vue` - Create: `frontend/components/ColorPicker.vue` **Step 1: Créer le composant ColorPicker** Créer `frontend/components/ColorPicker.vue` : ```vue ``` **Step 2: Créer le formulaire drawer Project** Créer `frontend/components/ProjectDrawer.vue` : ```vue ``` **Step 3: Créer la page Projets** Créer `frontend/pages/projects.vue` : ```vue ``` **Step 4: Supprimer l'ancienne page project-list si elle existe** Vérifier si `frontend/pages/project-list.vue` existe. Si oui, la supprimer (c'était un placeholder). **Step 5: Commit** ```bash git add frontend/components/ColorPicker.vue frontend/components/ProjectDrawer.vue frontend/pages/projects.vue git rm frontend/pages/project-list.vue 2>/dev/null || true git commit -m "feat : add Projects page with cards and drawer form" ``` --- ## Task 9 : Navigation Sidebar + Traductions i18n **Files:** - Modify: `frontend/layouts/default.vue` - Modify: `frontend/i18n/locales/fr.json` **Step 1: Mettre à jour la sidebar** Dans `frontend/layouts/default.vue`, ajouter le lien "Clients" sous "Projets" dans la nav : ```html Clients ``` Aussi, mettre à jour le lien Projets pour pointer vers `/projects` (au lieu de `/project-list`) : Changer `to="/project-list"` → `to="/projects"` **Step 2: Ajouter les traductions** Modifier `frontend/i18n/locales/fr.json` pour ajouter les clés de toast : ```json { "errors": { "http": { "get": "Impossible de récupérer les données.", "post": "Impossible de créer la ressource.", "put": "Impossible de mettre à jour la ressource.", "patch": "Impossible de mettre à jour la ressource.", "delete": "Impossible de supprimer la ressource." }, "auth": { "login": "Identifiants invalides.", "logout": "Impossible de se déconnecter.", "session": "Session expirée" } }, "success": { "auth": { "login": "Connexion réussie.", "logout": "Déconnexion réussie." } }, "clients": { "created": "Client créé avec succès.", "updated": "Client mis à jour avec succès.", "deleted": "Client supprimé avec succès." }, "projects": { "created": "Projet créé avec succès.", "updated": "Projet mis à jour avec succès.", "deleted": "Projet supprimé avec succès." } } ``` **Step 3: Commit** ```bash git add frontend/layouts/default.vue frontend/i18n/locales/fr.json git commit -m "feat : add Clients nav link and i18n translations" ``` --- ## Task 10 : Mise à jour CLAUDE.md **Files:** - Modify: `CLAUDE.md` **Step 1: Mettre à jour CLAUDE.md** Ajouter dans la section structure les nouveaux fichiers et patterns. Points à ajouter/mettre à jour : - Ajouter `Client` et `Project` dans les entités documentées - Ajouter les fixtures complètes (3 clients, 4 projets) - Documenter le pattern Drawer (AppDrawer + formulaires spécifiques) - Documenter les services frontend (useClientService, useProjectService) - Documenter les DTOs (Client, ClientWrite, Project, ProjectWrite) - Documenter l'utilitaire Hydra (`utils/api.ts`) - Documenter le composant ColorPicker - Mettre à jour la structure des dossiers **Step 2: Commit** ```bash git add CLAUDE.md git commit -m "docs : update CLAUDE.md with Client and Project architecture" ``` --- ## Task 11 : Tests manuels de bout en bout **Step 1: Recharger les fixtures** Run: `make db-reset` ou `make fixtures` Expected: BDD reset avec admin + 3 clients + 4 projets **Step 2: Tester l'API Client via curl** ```bash # Login curl -c cookies.txt -X POST http://localhost:8082/login_check \ -H 'Content-Type: application/json' \ -d '{"username":"admin","password":"admin"}' # GET clients curl -b cookies.txt http://localhost:8082/api/clients ``` Expected: Liste de 3 clients au format Hydra **Step 3: Tester l'API Project via curl** ```bash curl -b cookies.txt http://localhost:8082/api/projects ``` Expected: Liste de 4 projets au format Hydra, chaque projet avec `client` imbriqué ou null **Step 4: Tester le frontend** 1. Naviguer vers `http://localhost:8082/` 2. Se connecter avec `admin` / `admin` 3. Cliquer "Projets" dans la sidebar → page avec 4 cartes colorées 4. Cliquer "+ Ajouter un projet" → drawer s'ouvre, remplir et enregistrer 5. Cliquer "Clients" dans la sidebar → page avec tableau de 3 clients 6. Cliquer sur une ligne → drawer s'ouvre avec les données pré-remplies 7. Cliquer "+ Ajouter un client" → drawer vide s'ouvre **Step 5: Commit final si ajustements** ```bash git add -A git commit -m "fix : adjustments after manual testing" ``` --- ## Résumé des commits prévus | # | Message | Scope | |---|---------|-------| | 1 | `feat : add Client entity with CRUD API` | Backend | | 2 | `feat : add Project entity with CRUD API and Client relation` | Backend | | 3 | `feat : add Client and Project fixtures` | Backend | | 4 | `feat : add Client DTO, service and Hydra utils (frontend)` | Frontend | | 5 | `feat : add Project DTO and service (frontend)` | Frontend | | 6 | `feat : add reusable AppDrawer component` | Frontend | | 7 | `feat : add Clients page with table and drawer form` | Frontend | | 8 | `feat : add Projects page with cards and drawer form` | Frontend | | 9 | `feat : add Clients nav link and i18n translations` | Frontend | | 10 | `docs : update CLAUDE.md with Client and Project architecture` | Docs |