# Admin Clients + Global Statuses Implementation Plan > **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Move clients management into the admin page as a tab, and make task statuses global (shared across all projects) instead of per-project. **Architecture:** Two independent changes: (1) Extract client CRUD from its dedicated page into an `AdminClientTab` component inside the admin page, remove the standalone `/clients` page and sidebar link. (2) Remove the `project` relationship from `TaskStatus` entity, update the frontend to use `getAll()` everywhere instead of `getByProject()`, remove per-project status management pages/links, and update `AdminStatusTab` + `TaskStatusDrawer` to work without `projectId`. **Tech Stack:** PHP 8.4 / Symfony 8 / Doctrine ORM (backend), Nuxt 4 / Vue 3 / TypeScript (frontend) --- ## Chunk 1: Move Clients into Admin ### Task 1: Create AdminClientTab component **Files:** - Create: `frontend/components/admin/AdminClientTab.vue` - [ ] **Step 1: Create AdminClientTab.vue** Extract the logic from `frontend/pages/clients.vue` into a new admin tab component, following the same pattern as `AdminPriorityTab.vue` (h2 title instead of h1, no `useHead`). ```vue ``` - [ ] **Step 2: Commit** ```bash git add frontend/components/admin/AdminClientTab.vue git commit -m "feat(admin) : add AdminClientTab component" ``` ### Task 2: Add Clients tab to admin page and remove standalone page **Files:** - Modify: `frontend/pages/admin.vue` - Delete: `frontend/pages/clients.vue` - [ ] **Step 3: Update admin.vue to include Clients tab** Add `AdminClientTab` to the admin page. Add the tab entry to the `tabs` array and the corresponding `v-if` block: In `frontend/pages/admin.vue`, update the `tabs` array: ```typescript const tabs = [ { key: 'clients', label: 'Clients' }, { key: 'efforts', label: 'Efforts' }, { key: 'priorities', label: 'Priorités' }, { key: 'types', label: 'Types' }, { key: 'users', label: 'Utilisateurs' }, ] as const ``` Change the default active tab: ```typescript const activeTab = ref('clients') ``` Add the component in the template `
` block: ```html ``` - [ ] **Step 4: Delete standalone clients page** Delete `frontend/pages/clients.vue`. - [ ] **Step 5: Commit** ```bash git add frontend/pages/admin.vue git rm frontend/pages/clients.vue git commit -m "feat(admin) : move clients into admin page, remove standalone page" ``` ### Task 3: Remove clients sidebar link **Files:** - Modify: `frontend/layouts/default.vue` - [ ] **Step 6: Remove the Clients SidebarLink from default.vue** Remove the following block from `frontend/layouts/default.vue` (lines 60-65): ```html ``` - [ ] **Step 7: Commit** ```bash git add frontend/layouts/default.vue git commit -m "refactor(frontend) : remove clients sidebar link" ``` --- ## Chunk 2: Make Task Statuses Global ### Task 4: Remove project relationship from TaskStatus entity **Files:** - Modify: `src/Entity/TaskStatus.php` - [ ] **Step 8: Update TaskStatus entity to remove project relationship** In `src/Entity/TaskStatus.php`: 1. Remove the `SearchFilter` import and `#[ApiFilter]` attribute 2. Remove the `$project` property, its `#[ORM]` annotations, and the `getProject()`/`setProject()` methods The entity should become: ```php ['task_status:read']], denormalizationContext: ['groups' => ['task_status:write']], order: ['position' => 'ASC'], )] #[ORM\Entity(repositoryClass: TaskStatusRepository::class)] class TaskStatus { #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column] #[Groups(['task_status:read', 'task:read'])] private ?int $id = null; #[ORM\Column(length: 255)] #[Groups(['task_status:read', 'task_status:write', 'task:read'])] private ?string $label = null; #[ORM\Column(length: 7)] #[Groups(['task_status:read', 'task_status:write', 'task:read'])] private ?string $color = '#222783'; #[ORM\Column] #[Groups(['task_status:read', 'task_status:write', 'task:read'])] private ?int $position = 0; public function getId(): ?int { return $this->id; } public function getLabel(): ?string { return $this->label; } public function setLabel(string $label): static { $this->label = $label; return $this; } public function getColor(): ?string { return $this->color; } public function setColor(string $color): static { $this->color = $color; return $this; } public function getPosition(): ?int { return $this->position; } public function setPosition(int $position): static { $this->position = $position; return $this; } } ``` - [ ] **Step 9: Commit** ```bash git add src/Entity/TaskStatus.php git commit -m "refactor(backend) : remove project relationship from TaskStatus entity" ``` ### Task 5: Generate and run Doctrine migration - [ ] **Step 10: Generate the migration** ```bash make shell # Inside container: php bin/console doctrine:migrations:diff exit ``` This should generate a migration that: - Drops the `project_id` foreign key from `task_status` table - Drops the `project_id` column from `task_status` table - [ ] **Step 11: Review the migration** Read the generated migration file in `migrations/` to verify it only drops the FK and column. - [ ] **Step 12: Reset database (since structure changed significantly)** ```bash make db-reset ``` - [ ] **Step 13: Commit** ```bash git add migrations/ git commit -m "feat(backend) : add migration to remove project_id from task_status" ``` ### Task 6: Update fixtures for global statuses **Files:** - Modify: `src/DataFixtures/AppFixtures.php` - [ ] **Step 14: Update fixtures to create global statuses instead of per-project** In `src/DataFixtures/AppFixtures.php`, replace the entire per-project status block (lines 95-124, from `// Task Statuses (per project)` through `$statusDone = $sirhStatuses['Terminé'];`) with global creation: ```php // Task Statuses (global) $defaultStatuses = [ ['A faire', '#222783', 0], ['En cours', '#4A90D9', 1], ['Bloqué', '#C62828', 2], ['En attente de validation', '#FF8F00', 3], ['Terminé', '#26A69A', 4], ]; $statusObjects = []; foreach ($defaultStatuses as [$label, $color, $position]) { $status = new TaskStatus(); $status->setLabel($label); $status->setColor($color); $status->setPosition($position); $manager->persist($status); $statusObjects[$label] = $status; } $statusTodo = $statusObjects['A faire']; $statusInProgress = $statusObjects['En cours']; $statusBlocked = $statusObjects['Bloqué']; $statusReview = $statusObjects['En attente de validation']; $statusDone = $statusObjects['Terminé']; ``` This replaces the loop that created statuses per-project AND the `$statusesByProject` / `$sirhStatuses` extraction lines (95-124). The task variable references (`$statusTodo`, etc.) remain identical so downstream task creation is unchanged. - [ ] **Step 15: Reload fixtures to verify** ```bash make db-reset ``` - [ ] **Step 16: Commit** ```bash git add src/DataFixtures/AppFixtures.php git commit -m "fix(fixtures) : create global statuses instead of per-project" ``` ### Task 7: Update frontend DTO and service for global statuses **Files:** - Modify: `frontend/services/dto/task-status.ts` - Modify: `frontend/services/task-statuses.ts` - [ ] **Step 17: Update TaskStatus DTO to remove project field** In `frontend/services/dto/task-status.ts`, remove the `project` import and field from both types: ```typescript export type TaskStatus = { id: number '@id'?: string label: string color: string position: number } export type TaskStatusWrite = { label: string color: string position: number } ``` - [ ] **Step 18: Remove getByProject from task-statuses service** In `frontend/services/task-statuses.ts`, remove the `getByProject` function and its return: ```typescript import type { TaskStatus, TaskStatusWrite } from './dto/task-status' import type { HydraCollection } from '~/utils/api' import { extractHydraMembers } from '~/utils/api' export function useTaskStatusService() { const api = useApi() async function getAll(): Promise { const data = await api.get>('/task_statuses') return extractHydraMembers(data) } async function create(payload: TaskStatusWrite): Promise { return api.post('/task_statuses', payload as Record, { toastSuccessKey: 'taskStatuses.created', }) } async function update(id: number, payload: Partial): Promise { return api.patch(`/task_statuses/${id}`, payload as Record, { toastSuccessKey: 'taskStatuses.updated', }) } async function remove(id: number): Promise { await api.delete(`/task_statuses/${id}`, {}, { toastSuccessKey: 'taskStatuses.deleted', }) } return { getAll, create, update, remove } } ``` - [ ] **Step 19: Commit** ```bash git add frontend/services/dto/task-status.ts frontend/services/task-statuses.ts git commit -m "refactor(frontend) : remove project from TaskStatus DTO and service" ``` ### Task 8: Update TaskStatusDrawer to remove projectId **Files:** - Modify: `frontend/components/task/TaskStatusDrawer.vue` - [ ] **Step 20: Remove projectId prop from TaskStatusDrawer** In `frontend/components/task/TaskStatusDrawer.vue`: 1. Remove `projectId` from props: ```typescript const props = defineProps<{ modelValue: boolean item: TaskStatus | null }>() ``` 2. Remove the `project` field from the payload in `handleSubmit`: ```typescript const payload: TaskStatusWrite = { label: form.label.trim(), position: Number(form.position), color: form.color, } ``` - [ ] **Step 21: Commit** ```bash git add frontend/components/task/TaskStatusDrawer.vue git commit -m "refactor(frontend) : remove projectId from TaskStatusDrawer" ``` ### Task 9: Add getAll to task service and update AdminStatusTab **Files:** - Modify: `frontend/services/tasks.ts` - Modify: `frontend/components/admin/AdminStatusTab.vue` - [ ] **Step 22: Add getAll() method to task service** `frontend/services/tasks.ts` currently only has `getByProject()`. Add a `getAll` function (needed by AdminStatusTab to check all tasks across projects when deleting a status). Add this function inside `useTaskService()`, before `getByProject`: ```typescript async function getAll(): Promise { const data = await api.get>('/tasks') return extractHydraMembers(data) } ``` Update the return statement to include it: ```typescript return { getAll, getByProject, create, update, remove } ``` - [ ] **Step 23: Update AdminStatusTab to handle task reassignment on delete** The existing `AdminStatusTab` does a simple `remove(id)` which would leave tasks orphaned. Port the reassignment logic from `ProjectStatusTab` (which is being deleted). Since statuses are now global, we need to load ALL tasks (not per-project) to check for affected tasks. Replace the full content of `frontend/components/admin/AdminStatusTab.vue` with: ```vue ``` - [ ] **Step 24: Commit** ```bash git add frontend/services/tasks.ts frontend/components/admin/AdminStatusTab.vue git commit -m "feat(admin) : add task reassignment logic to AdminStatusTab" ``` ### Task 10: Add Statuts tab to admin page **Files:** - Modify: `frontend/pages/admin.vue` - [ ] **Step 24: Add Statuts tab to admin.vue** In `frontend/pages/admin.vue`, update the `tabs` array to include statuses: ```typescript const tabs = [ { key: 'clients', label: 'Clients' }, { key: 'statuses', label: 'Statuts' }, { key: 'efforts', label: 'Efforts' }, { key: 'priorities', label: 'Priorités' }, { key: 'types', label: 'Types' }, { key: 'users', label: 'Utilisateurs' }, ] as const ``` Add the component in the template: ```html ``` - [ ] **Step 25: Commit** ```bash git add frontend/pages/admin.vue git commit -m "feat(admin) : add statuts tab to admin page" ``` ### Task 11: Update kanban page to use global statuses **Files:** - Modify: `frontend/pages/projects/[id]/index.vue` - [ ] **Step 26: Change kanban to load global statuses** In `frontend/pages/projects/[id]/index.vue`, in the `loadData` function, change: ```typescript statusService.getByProject(projectId.value), ``` to: ```typescript statusService.getAll(), ``` - [ ] **Step 27: Commit** ```bash git add frontend/pages/projects/[id]/index.vue git commit -m "refactor(frontend) : load global statuses in kanban page" ``` ### Task 12: Remove per-project status pages, sidebar link, and orphaned components **Files:** - Delete: `frontend/pages/projects/[id]/statuses.vue` - Delete: `frontend/components/project/ProjectStatusTab.vue` - Modify: `frontend/layouts/default.vue` - [ ] **Step 28: Delete per-project statuses page and ProjectStatusTab** ```bash git rm frontend/pages/projects/[id]/statuses.vue git rm frontend/components/project/ProjectStatusTab.vue ``` - [ ] **Step 29: Remove statuses SidebarLink from default.vue** In `frontend/layouts/default.vue`, remove the statuses sidebar link block (inside the `v-if="currentProjectId"` template, lines 52-58): ```html ``` - [ ] **Step 30: Commit** ```bash git add frontend/layouts/default.vue git commit -m "refactor(frontend) : remove per-project statuses page and sidebar link" ``` ### Task 13: Verify and clean up - [ ] **Step 31: Check for remaining references to getByProject in task-statuses** Search for any remaining `getByProject` calls on the status service and `projectId` references in status-related components: ```bash cd /home/matthieu/dev_malio/Lesstime grep -rn "getByProject\|projectId" frontend/ --include="*.vue" --include="*.ts" | grep -i status grep -rn "ConfirmDeleteStatusModal\|ProjectStatusTab" frontend/ --include="*.vue" --include="*.ts" ``` Fix any remaining references found. - [ ] **Step 32: Run the dev server and verify** ```bash make db-reset && make dev-nuxt ``` Verify: 1. Admin page shows Clients tab with full CRUD (create, edit, delete) 2. Admin page shows Statuts tab with global statuses CRUD 3. Sidebar no longer shows "Clients" or per-project "Statuts" links 4. Kanban board displays all global statuses as columns 5. No errors in browser console - [ ] **Step 33: Final commit if any cleanup was needed** ```bash git add -A git commit -m "chore : clean up remaining references after global statuses refactor" ```