# Réorganisation gestion employés — Plan d'implémentation > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Sortir l'édition des informations RH du `UserDrawer` (qui ne garde que la case « Employé ») vers un onglet « Employés » dédié dans `team-absences`, avec une liste users⋈soldes et un drawer d'édition. **Architecture:** Réorganisation 100 % frontend. Les champs employé existent déjà sur l'entité `User` (backend) et le DTO `UserData`/`UserWrite` ; la persistance passe par `usersService.update()` (PATCH partiel, sans écrasement). La liste de l'onglet joint `usersService.getAll()` (filtré `isEmployee`) avec `absenceService.getBalances({ type: 'cp' })`. **Tech Stack:** Nuxt 4 / Vue 3 Composition API, TypeScript, composants `@malio/layer-ui` (MalioDate, MalioSelect, MalioInputText, MalioDataTable, MalioDrawer, MalioCheckbox), i18n `@nuxtjs/i18n`. **Conventions projet :** - 4 espaces d'indentation, TypeScript strict. - Pas de framework de test frontend → vérification au navigateur (serveur dev sur `http://localhost:3002`, Chrome DevTools MCP) + compilation HMR sans erreur console. - **Commits gérés par l'utilisateur** : ne committer qu'après son feu vert explicite (règle CLAUDE.md). Les étapes « Commit » sont fournies mais à déclencher sur demande. Format : `() : `. --- ### Task 1 : Clés i18n **Files:** - Modify: `frontend/i18n/locales/fr.json` (objet `absences.admin`) - [ ] **Step 1 : Ajouter l'onglet et le bloc `employees`** Dans `absences.admin`, ajouter `"employees"` à `tabs`, et un nouveau bloc `employees`. Repérer le bloc existant : ```json "tabs": { "requests": "Demandes", "calendar": "Calendrier", "balances": "Soldes" }, ``` le remplacer par : ```json "tabs": { "requests": "Demandes", "calendar": "Calendrier", "balances": "Soldes", "employees": "Employés" }, ``` puis ajouter, à la suite des clés de `admin` (par ex. après `"adjust"`), le bloc : ```json "employees": { "columns": { "name": "Nom", "contract": "Contrat", "cpTaken": "CP pris", "cpRemaining": "CP restants" }, "empty": "Aucun employé. Cochez « Employé » sur un utilisateur dans l'administration.", "noContract": "—", "drawer": { "title": "Informations employé", "save": "Enregistrer" }, "fields": { "hireDate": "Date d'embauche", "endDate": "Date de sortie", "contractType": "Type de contrat", "familySituation": "Situation familiale", "workTimeRatio": "Temps de travail (ex : 1.0)", "annualLeaveDays": "CP annuels (jours)", "referencePeriodStart": "Début période réf. (MM-DD)", "initialLeaveBalance": "Solde CP initial", "nbChildren": "Nombre d'enfants" }, "contract": { "cdi": "CDI", "cdd": "CDD", "stage": "Stage", "alternance": "Alternance", "autre": "Autre" }, "family": { "celibataire": "Célibataire", "marie": "Marié(e)", "pacse": "Pacsé(e)", "divorce": "Divorcé(e)", "veuf": "Veuf(ve)" } } ``` - [ ] **Step 2 : Vérifier la validité JSON** Run: `cd frontend && python3 -c "import json; json.load(open('i18n/locales/fr.json')); print('OK')"` Expected: `OK` - [ ] **Step 3 : Commit** (sur feu vert utilisateur) ```bash git add frontend/i18n/locales/fr.json git commit -m "feat(absences) : clés i18n onglet et drawer employés" ``` --- ### Task 2 : Composant `EmployeeDrawer.vue` **Files:** - Create: `frontend/components/absence/EmployeeDrawer.vue` - [ ] **Step 1 : Créer le composant** Crée `frontend/components/absence/EmployeeDrawer.vue` avec ce contenu exact : ```vue ``` - [ ] **Step 2 : Vérifier la compilation** Le serveur dev (`http://localhost:3002`) recompile à la sauvegarde. Vérifier qu'aucune erreur de compilation/HMR n'apparaît dans la console du terminal `make dev-nuxt` ni dans la console navigateur. (Le composant n'est pas encore monté ; cette étape ne fait que valider la syntaxe.) - [ ] **Step 3 : Commit** (sur feu vert utilisateur) ```bash git add frontend/components/absence/EmployeeDrawer.vue git commit -m "feat(absences) : drawer d'édition des informations employé" ``` --- ### Task 3 : Onglet « Employés » dans `team-absences` **Files:** - Modify: `frontend/pages/team-absences.vue` - [ ] **Step 1 : Ajouter l'import du service users et le type** Après les imports existants (`useAbsenceHelpers`), ajouter : ```ts import { useUserService } from "~/services/users"; import type { UserData } from "~/services/dto/user-data"; ``` Et après la déclaration `type BalanceRow = ...`, ajouter le type de ligne : ```ts type EmployeeRow = UserData & { contractText: string; cpTakenText: string; cpRemainingText: string; }; ``` - [ ] **Step 2 : Ajouter l'onglet à `tabs`** Remplacer le tableau `tabs` (qui se termine par l'onglet `balances`) en ajoutant l'entrée employés : ```ts const tabs = [ { key: "requests", label: t("absences.admin.tabs.requests"), icon: "mdi:format-list-bulleted", }, { key: "calendar", label: t("absences.admin.tabs.calendar"), icon: "mdi:calendar-month", }, { key: "balances", label: t("absences.admin.tabs.balances"), icon: "mdi:scale-balance", }, { key: "employees", label: t("absences.admin.tabs.employees"), icon: "mdi:account-group", }, ]; ``` - [ ] **Step 3 : Ajouter l'état, les colonnes et les lignes de l'onglet** Après `const balances = ref([]);`, ajouter : ```ts const employees = ref([]); const employeeDrawerOpen = ref(false); const selectedEmployee = ref(null); ``` Après `const balanceRows = computed(...)`, ajouter colonnes + lignes : ```ts const employeeColumns = [ { key: "username", label: t("absences.admin.employees.columns.name") }, { key: "contractText", label: t("absences.admin.employees.columns.contract") }, { key: "cpTakenText", label: t("absences.admin.employees.columns.cpTaken") }, { key: "cpRemainingText", label: t("absences.admin.employees.columns.cpRemaining") }, ]; const employeeRows = computed(() => { // Map user.id -> solde CP de la période courante. const cpByUser = new Map(); for (const b of balances.value) { if (b.type === "cp") cpByUser.set(b.user.id, b); } const dash = t("absences.admin.employees.noContract"); return employees.value.map((u) => { const cp = cpByUser.get(u.id); return { ...u, contractText: u.contractType ?? dash, cpTakenText: cp ? formatDays(cp.taken) : dash, cpRemainingText: cp ? formatDays(cp.available) : dash, }; }); }); ``` - [ ] **Step 4 : Ajouter le chargement et l'ouverture du drawer** Après `async function loadBalances() {...}`, ajouter : ```ts async function loadEmployees() { const all = await useUserService().getAll(); employees.value = all.filter((u) => u.isEmployee); } function openEmployee(item: Record) { selectedEmployee.value = item as EmployeeRow; employeeDrawerOpen.value = true; } ``` Puis inclure `loadEmployees()` au montage. Remplacer le `onMounted` existant : ```ts onMounted(async () => { await Promise.all([reloadRequests(), loadBalances()]); }); ``` par : ```ts onMounted(async () => { await Promise.all([reloadRequests(), loadBalances(), loadEmployees()]); }); ``` - [ ] **Step 5 : Ajouter le slot d'onglet dans le template** Juste après la fermeture du slot `` de l'onglet `#balances` (avant ``), ajouter : ```vue ``` - [ ] **Step 6 : Monter le drawer employé** Après le composant `` (avant `` de fin de template), ajouter : ```vue ``` - [ ] **Step 7 : Vérification navigateur** Aller sur `http://localhost:3002/team-absences`, onglet « Employés ». Vérifier : liste des users `isEmployee` avec Nom / Contrat / CP pris / CP restants ; clic sur une ligne ouvre le drawer ; aucune erreur console. - [ ] **Step 8 : Commit** (sur feu vert utilisateur) ```bash git add frontend/pages/team-absences.vue git commit -m "feat(absences) : onglet Employés (liste + ouverture drawer)" ``` --- ### Task 4 : Allègement du `UserDrawer` **Files:** - Modify: `frontend/components/user/UserDrawer.vue` - [ ] **Step 1 : Réduire le bloc RH du template à la seule case** Remplacer le bloc (lignes ~74-107) : ```vue
``` par : ```vue

Les informations RH (contrat, dates, CP…) se gèrent dans Absences équipe → onglet Employés.

``` - [ ] **Step 2 : Nettoyer l'état du formulaire** Dans `const form = reactive({...})`, supprimer les champs détaillés et ne garder que `isEmployee`. Résultat : ```ts const form = reactive({ username: '', password: '', roles: [] as string[], clientId: null as number | null, allowedProjectIds: [] as number[], isEmployee: false, }) ``` - [ ] **Step 3 : Nettoyer l'hydratation à l'ouverture** Dans le `watch(() => props.modelValue, ...)`, supprimer toutes les lignes `form.hireDate = ...` → `form.nbChildren = ...` des deux branches (`props.item` et `else`). Conserver `form.isEmployee = props.item.isEmployee ?? false` (branche édition) et `form.isEmployee = false` (branche création). - [ ] **Step 4 : Ne plus envoyer les champs détaillés dans le payload** Dans `handleSubmit`, réduire le `payload` aux champs de compte + `isEmployee` : ```ts const payload: UserWrite = { username: form.username.trim(), roles: form.roles, client: form.clientId !== null ? `/api/clients/${form.clientId}` : null, allowedProjects: form.clientId !== null ? form.allowedProjectIds.map((id) => `/api/projects/${id}`) : [], isEmployee: form.isEmployee, } if (form.password) { payload.plainPassword = form.password } ``` - [ ] **Step 5 : Supprimer les imports/constantes devenus inutiles** Dans le `