817 lines
23 KiB
Markdown
817 lines
23 KiB
Markdown
# 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
|
|
<template>
|
|
<div>
|
|
<div class="flex items-center justify-between">
|
|
<h2 class="text-lg font-bold text-neutral-900">Clients</h2>
|
|
<button
|
|
class="rounded-md bg-primary-500 px-4 py-2 text-sm font-semibold text-white hover:bg-secondary-500"
|
|
@click="openCreate"
|
|
>
|
|
+ Ajouter un client
|
|
</button>
|
|
</div>
|
|
|
|
<DataTable
|
|
:columns="columns"
|
|
:items="clients"
|
|
:loading="isLoading"
|
|
empty-message="Aucun client trouvé."
|
|
deletable
|
|
@row-click="openEdit"
|
|
@delete="(item) => handleDelete(item.id)"
|
|
>
|
|
<template #cell-email="{ item }">
|
|
{{ item.email ?? '-' }}
|
|
</template>
|
|
<template #cell-address="{ item }">
|
|
{{ formatAddress(item) }}
|
|
</template>
|
|
<template #cell-phone="{ item }">
|
|
{{ item.phone ?? '-' }}
|
|
</template>
|
|
</DataTable>
|
|
|
|
<ClientDrawer
|
|
v-model="drawerOpen"
|
|
:client="selectedClient"
|
|
@saved="onSaved"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import type { Client } from '~/services/dto/client'
|
|
import { useClientService } from '~/services/clients'
|
|
|
|
import type { DataTableColumn } from '~/components/ui/DataTable.vue'
|
|
|
|
const columns: DataTableColumn[] = [
|
|
{ key: 'name', label: 'Nom', primary: true },
|
|
{ key: 'email', label: 'Email', class: 'text-primary-500' },
|
|
{ key: 'address', label: 'Adresse', class: 'text-neutral-700' },
|
|
{ key: 'phone', label: 'Téléphone', class: 'text-primary-500' },
|
|
]
|
|
|
|
const { getAll, remove } = useClientService()
|
|
const clients = ref<Client[]>([])
|
|
const isLoading = ref(true)
|
|
const drawerOpen = ref(false)
|
|
const selectedClient = ref<Client | null>(null)
|
|
|
|
async function loadClients() {
|
|
isLoading.value = true
|
|
try {
|
|
clients.value = await getAll()
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
function openCreate() {
|
|
selectedClient.value = null
|
|
drawerOpen.value = true
|
|
}
|
|
|
|
function openEdit(client: Client) {
|
|
selectedClient.value = client
|
|
drawerOpen.value = true
|
|
}
|
|
|
|
function formatAddress(client: Client): string {
|
|
return [client.street, client.postalCode, client.city]
|
|
.filter(Boolean)
|
|
.join(', ') || '-'
|
|
}
|
|
|
|
async function handleDelete(id: number) {
|
|
await remove(id)
|
|
await loadClients()
|
|
}
|
|
|
|
async function onSaved() {
|
|
await loadClients()
|
|
}
|
|
|
|
onMounted(() => {
|
|
loadClients()
|
|
})
|
|
</script>
|
|
```
|
|
|
|
- [ ] **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<TabKey>('clients')
|
|
```
|
|
|
|
Add the component in the template `<div class="mt-6">` block:
|
|
|
|
```html
|
|
<AdminClientTab v-if="activeTab === 'clients'" />
|
|
```
|
|
|
|
- [ ] **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
|
|
<SidebarLink
|
|
to="/clients"
|
|
icon="mdi:account-group-outline"
|
|
label="Clients"
|
|
:collapsed="ui.sidebarCollapsed"
|
|
/>
|
|
```
|
|
|
|
- [ ] **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
|
|
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Entity;
|
|
|
|
use ApiPlatform\Metadata\ApiResource;
|
|
use ApiPlatform\Metadata\Delete;
|
|
use ApiPlatform\Metadata\Get;
|
|
use ApiPlatform\Metadata\GetCollection;
|
|
use ApiPlatform\Metadata\Patch;
|
|
use ApiPlatform\Metadata\Post;
|
|
use App\Repository\TaskStatusRepository;
|
|
use Doctrine\ORM\Mapping as ORM;
|
|
use Symfony\Component\Serializer\Attribute\Groups;
|
|
|
|
#[ApiResource(
|
|
operations: [
|
|
new GetCollection(),
|
|
new Get(),
|
|
new Post(security: "is_granted('ROLE_ADMIN')"),
|
|
new Patch(security: "is_granted('ROLE_ADMIN')"),
|
|
new Delete(security: "is_granted('ROLE_ADMIN')"),
|
|
],
|
|
normalizationContext: ['groups' => ['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<TaskStatus[]> {
|
|
const data = await api.get<HydraCollection<TaskStatus>>('/task_statuses')
|
|
return extractHydraMembers(data)
|
|
}
|
|
|
|
async function create(payload: TaskStatusWrite): Promise<TaskStatus> {
|
|
return api.post<TaskStatus>('/task_statuses', payload as Record<string, unknown>, {
|
|
toastSuccessKey: 'taskStatuses.created',
|
|
})
|
|
}
|
|
|
|
async function update(id: number, payload: Partial<TaskStatusWrite>): Promise<TaskStatus> {
|
|
return api.patch<TaskStatus>(`/task_statuses/${id}`, payload as Record<string, unknown>, {
|
|
toastSuccessKey: 'taskStatuses.updated',
|
|
})
|
|
}
|
|
|
|
async function remove(id: number): Promise<void> {
|
|
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<Task[]> {
|
|
const data = await api.get<HydraCollection<Task>>('/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
|
|
<template>
|
|
<div>
|
|
<div class="flex items-center justify-between">
|
|
<h2 class="text-lg font-bold text-neutral-900">Statuts</h2>
|
|
<button
|
|
class="rounded-md bg-primary-500 px-4 py-2 text-sm font-semibold text-white hover:bg-secondary-500"
|
|
@click="openCreate"
|
|
>
|
|
+ Ajouter un statut
|
|
</button>
|
|
</div>
|
|
|
|
<DataTable
|
|
:columns="columns"
|
|
:items="items"
|
|
:loading="isLoading"
|
|
empty-message="Aucun statut trouvé."
|
|
deletable
|
|
@row-click="openEdit"
|
|
@delete="requestDelete"
|
|
>
|
|
<template #cell-color="{ item }">
|
|
<span
|
|
class="inline-block h-6 w-6 rounded-full"
|
|
:style="{ backgroundColor: item.color }"
|
|
/>
|
|
</template>
|
|
</DataTable>
|
|
|
|
<TaskStatusDrawer
|
|
v-model="drawerOpen"
|
|
:item="selectedItem"
|
|
@saved="onSaved"
|
|
/>
|
|
|
|
<ConfirmDeleteStatusModal
|
|
v-model="confirmModalOpen"
|
|
:status-label="statusToDelete?.label ?? ''"
|
|
:task-count="affectedTaskCount"
|
|
:available-statuses="reassignTargets"
|
|
@confirm="onConfirmDelete"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import type { TaskStatus } from '~/services/dto/task-status'
|
|
import type { Task } from '~/services/dto/task'
|
|
import { useTaskStatusService } from '~/services/task-statuses'
|
|
import { useTaskService } from '~/services/tasks'
|
|
|
|
import type { DataTableColumn } from '~/components/ui/DataTable.vue'
|
|
|
|
const columns: DataTableColumn[] = [
|
|
{ key: 'label', label: 'Libellé', primary: true },
|
|
{ key: 'color', label: 'Couleur' },
|
|
{ key: 'position', label: 'Position', class: 'text-neutral-700' },
|
|
]
|
|
|
|
const statusService = useTaskStatusService()
|
|
const taskService = useTaskService()
|
|
|
|
const items = ref<TaskStatus[]>([])
|
|
const tasks = ref<Task[]>([])
|
|
const isLoading = ref(true)
|
|
const drawerOpen = ref(false)
|
|
const selectedItem = ref<TaskStatus | null>(null)
|
|
const confirmModalOpen = ref(false)
|
|
const statusToDelete = ref<TaskStatus | null>(null)
|
|
|
|
const affectedTaskCount = computed(() => {
|
|
if (!statusToDelete.value) return 0
|
|
return tasks.value.filter(t => t.status?.id === statusToDelete.value!.id).length
|
|
})
|
|
|
|
const reassignTargets = computed(() => {
|
|
if (!statusToDelete.value) return items.value
|
|
return items.value.filter(s => s.id !== statusToDelete.value!.id)
|
|
})
|
|
|
|
async function loadItems() {
|
|
isLoading.value = true
|
|
try {
|
|
const [statuses, allTasks] = await Promise.all([
|
|
statusService.getAll(),
|
|
taskService.getAll(),
|
|
])
|
|
items.value = statuses
|
|
tasks.value = allTasks
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
function openCreate() {
|
|
selectedItem.value = null
|
|
drawerOpen.value = true
|
|
}
|
|
|
|
function openEdit(item: TaskStatus) {
|
|
selectedItem.value = item
|
|
drawerOpen.value = true
|
|
}
|
|
|
|
async function requestDelete(item: TaskStatus) {
|
|
statusToDelete.value = item
|
|
const count = tasks.value.filter(t => t.status?.id === item.id).length
|
|
if (count === 0) {
|
|
await statusService.remove(item.id)
|
|
await loadItems()
|
|
} else {
|
|
confirmModalOpen.value = true
|
|
}
|
|
}
|
|
|
|
async function onConfirmDelete(targetStatusId: number | null) {
|
|
if (!statusToDelete.value) return
|
|
|
|
const affectedTasks = tasks.value.filter(t => t.status?.id === statusToDelete.value!.id)
|
|
const statusIri = targetStatusId ? `/api/task_statuses/${targetStatusId}` : null
|
|
|
|
await Promise.all(
|
|
affectedTasks.map(t => taskService.update(t.id, { status: statusIri }))
|
|
)
|
|
|
|
await statusService.remove(statusToDelete.value.id)
|
|
confirmModalOpen.value = false
|
|
statusToDelete.value = null
|
|
await loadItems()
|
|
}
|
|
|
|
async function onSaved() {
|
|
await loadItems()
|
|
}
|
|
|
|
onMounted(() => {
|
|
loadItems()
|
|
})
|
|
</script>
|
|
```
|
|
|
|
- [ ] **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
|
|
<AdminStatusTab v-if="activeTab === 'statuses'" />
|
|
```
|
|
|
|
- [ ] **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
|
|
<SidebarLink
|
|
:to="`/projects/${currentProjectId}/statuses`"
|
|
icon="mdi:list-status"
|
|
label="Statuts"
|
|
:collapsed="ui.sidebarCollapsed"
|
|
sub
|
|
/>
|
|
```
|
|
|
|
- [ ] **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"
|
|
```
|