# Migration sidebar vers MalioSidebar — Implementation Plan

> **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:** Remplacer la sidebar maison de Lesstime par le composant `MalioSidebar` de `@malio/layer-ui`, en 3 groupes (Général / Outils / Administration), avec timer + version dans le footer et le logo Malio de Starseed.

**Architecture:** Modèle backend-driven conservé — `config/sidebar.php` filtré par `SidebarProvider` (permissions/rôles/modules côté serveur), exposé via `/api/sidebar`, consommé par `useSidebar()`. Le layout `default.vue` mappe ces sections vers le format `MalioSidebar` et fusionne les items contextuels rendus côté client (Kanban/Groupes/Archives, Documents, Mail+badge, Mes absences).

**Tech Stack:** Nuxt 4 (SPA), Vue 3 `<script setup>` TS, Pinia, `@malio/layer-ui` ^1.7.16, i18n (@nuxtjs/i18n), Symfony 8 / API Platform 4 (backend config PHP).

## Global Constraints

- **Ne jamais modifier `@malio/layer-ui`** (lib externe). Source de référence en lecture seule : `frontend/node_modules/@malio/layer-ui/app/components/malio/sidebar/Sidebar.vue`.
- `MalioSidebar` : props `sections` (requis), `modelValue` (v-model collapse bool), `sidebarClass`, `toggleClass`. Item = `{ label: string; to: string; exact?: boolean }` (pas d'icône ni de badge par item). Section = `{ label?: string; icon?: string; items: SidebarItem[] }`. Slots : `#logo`, `#logo-collapsed`, `#footer`, `#footer-collapsed`.
- **TypeScript strict** ; indentation **4 espaces** (frontend).
- Backend : `declare(strict_types=1)` en tête des fichiers PHP.
- Commits format projet : `type(scope) : message` (espaces autour du `:`), types autorisés minuscules (`feat`, `fix`, `refactor`, `chore`, …). **Ne committer que sur demande de l'utilisateur** (règle CLAUDE.md). Travailler sur une branche dédiée (pas directement sur `develop`).
- **Pas de runner de test frontend** dans ce projet → vérification par `npm run build` (Nuxt, échoue sur erreur TS/template) + **QA manuelle navigateur** (`make dev-nuxt`, port 3002). Ne PAS introduire de framework de test (hors scope).
- Décisions validées : 3 groupes ; badge mail = **suffixe `(N)`** sur le label.

## File Structure

- `config/sidebar.php` — **Modify** : re-catégorisation en 3 sections.
- `frontend/i18n/locales/fr.json` — **Modify** : clés de sections/items.
- `frontend/i18n/locales/*.json` (autres langues présentes) — **Modify si existantes** : mêmes clés.
- `frontend/public/LOGO_MALIO.png` — **Create** (copie Starseed).
- `frontend/public/LOGO_MALIO_COLLAPSED.png` — **Create** (copie Starseed).
- `frontend/app/layouts/default.vue` — **Modify** : réécriture du template sidebar + logique `mergedSections`.
- `frontend/components/ui/SidebarLink.vue` — **Possible delete** (si plus aucun usage après migration).

---

## Task 0 : Branche de travail

**Files:** aucun (git).

- [ ] **Step 1 : Créer la branche depuis `develop`**

```bash
cd /home/m-tristan/workspace/Lesstime
git checkout develop && git pull --ff-only
git checkout -b feat/malio-sidebar
```

Expected : sur la branche `feat/malio-sidebar`.

---

## Task 1 : Backend — re-catégorisation `config/sidebar.php` + i18n

**Files:**
- Modify: `config/sidebar.php`
- Modify: `frontend/i18n/locales/fr.json`
- Modify: autres `frontend/i18n/locales/*.json` si présentes (mêmes clés)

**Interfaces:**
- Produces : `/api/sidebar` renvoie des sections dont les `label` sont les clés `sidebar.general.section`, `sidebar.tools.section`, `sidebar.admin.section`. Items inchangés en `to` ; gates (`module`/`roles`/`permission`) inchangés, juste réorganisés.

- [ ] **Step 1 : Réécrire `config/sidebar.php` en 3 sections**

Remplacer le `return [...]` (lignes 20-44) par :

```php
return [
    [
        'label' => 'sidebar.general.section',
        'icon'  => 'mdi:view-dashboard-outline',
        'items' => [
            ['label' => 'sidebar.general.dashboard', 'to' => '/', 'icon' => 'mdi:view-dashboard-outline'],
            ['label' => 'sidebar.general.myTasks', 'to' => '/my-tasks', 'icon' => 'mdi:clipboard-check-outline', 'module' => 'project-management', 'permission' => 'project-management.tasks.view'],
            ['label' => 'sidebar.general.projects', 'to' => '/projects', 'icon' => 'mdi:folder-outline', 'module' => 'project-management', 'permission' => 'project-management.projects.view'],
            ['label' => 'sidebar.general.timeTracking', 'to' => '/time-tracking', 'icon' => 'mdi:calendar-edit-outline', 'module' => 'time-tracking', 'permission' => 'time-tracking.entries.view'],
        ],
    ],
    [
        'label' => 'sidebar.tools.section',
        'icon'  => 'mdi:tools',
        'items' => [
            // Gating module uniquement : rendu visuel + badge non-lus gérés côté layout
            // (filtré de translatedSections puis ré-injecté avec suffixe (N)).
            ['label' => 'sidebar.general.mail', 'to' => '/mail', 'icon' => 'mdi:email-outline', 'module' => 'mail'],
        ],
    ],
    [
        'label' => 'sidebar.admin.section',
        'icon'  => 'mdi:cog-outline',
        'roles' => ['ROLE_ADMIN'],
        'items' => [
            ['label' => 'sidebar.admin.teamAbsences', 'to' => '/team-absences', 'icon' => 'mdi:calendar-account-outline', 'module' => 'absence'],
            ['label' => 'sidebar.admin.directory', 'to' => '/directory', 'icon' => 'mdi:card-account-details-outline', 'module' => 'directory'],
            ['label' => 'sidebar.admin.reporting', 'to' => '/reporting', 'icon' => 'mdi:chart-line', 'module' => 'reporting', 'permission' => 'reporting.view'],
            ['label' => 'sidebar.admin.administration', 'to' => '/admin', 'icon' => 'mdi:cog-outline', 'permission' => 'core.users.view'],
        ],
    ],
];
```

> Mettre aussi à jour le commentaire d'en-tête si nécessaire (le bloc décrivant Mail/contextuels reste valable).

- [ ] **Step 2 : Mettre à jour les clés i18n FR**

Dans `frontend/i18n/locales/fr.json`, bloc `sidebar` :
- `sidebar.general.section` : remplacer la valeur par `"Général"`.
- Ajouter `sidebar.tools.section` : `"Outils"`.
- Conserver `sidebar.general.dashboard|myTasks|projects|timeTracking|mail` et `sidebar.admin.*`.
- Ajouter les clés pour items client (utilisées en Task 3) :
  - `sidebar.general.myAbsences` : `"Mes absences"`
  - `sidebar.project.kanban` : `"Kanban"`
  - `sidebar.project.groups` : `"Groupes"`
  - `sidebar.project.archives` : `"Archives"`

Résultat attendu du bloc (extrait) :

```json
"sidebar": {
  "general": {
    "section": "Général",
    "dashboard": "Tableau de bord",
    "myTasks": "Mes tâches",
    "projects": "Projets",
    "timeTracking": "Suivi de temps",
    "mail": "Messagerie",
    "myAbsences": "Mes absences"
  },
  "tools": {
    "section": "Outils"
  },
  "project": {
    "kanban": "Kanban",
    "groups": "Groupes",
    "archives": "Archives"
  },
  "admin": {
    "section": "Administration",
    "teamAbsences": "Absences équipe",
    "directory": "Répertoire",
    "administration": "Administration",
    "reporting": "Rapports"
  }
}
```

- [ ] **Step 3 : Répliquer les clés dans les autres locales si présentes**

```bash
ls /home/m-tristan/workspace/Lesstime/frontend/i18n/locales/
```

Pour chaque fichier autre que `fr.json`, ajouter `tools.section`, `general.myAbsences`, `project.kanban|groups|archives` et ajuster `general.section`. S'il n'existe que `fr.json`, ne rien faire de plus.

- [ ] **Step 4 : Vérifier `/api/sidebar` (admin)**

```bash
docker exec -i php-lesstime-fpm php -r 'var_dump(require "/var/www/config/sidebar.php");' | head -5
```

Expected : le fichier PHP se parse sans erreur (3 entrées de premier niveau). (Le chemin exact dans le container peut différer — sinon, vérifier via `make cache-clear` qui échouerait sur une erreur de syntaxe PHP.)

```bash
make cache-clear
```

Expected : succès, pas d'erreur de parse.

- [ ] **Step 5 : Commit (sur demande utilisateur)**

```bash
git add config/sidebar.php frontend/i18n/locales/
git commit -m "refactor(sidebar) : re-catégorisation en 3 groupes (Général / Outils / Administration)"
```

---

## Task 2 : Frontend — assets logo

**Files:**
- Create: `frontend/public/LOGO_MALIO.png`
- Create: `frontend/public/LOGO_MALIO_COLLAPSED.png`

**Interfaces:**
- Produces : assets statiques servis à `/LOGO_MALIO.png` et `/LOGO_MALIO_COLLAPSED.png`.

- [ ] **Step 1 : Copier les logos depuis Starseed**

```bash
cp /home/m-tristan/workspace/Starseed/frontend/public/LOGO_MALIO.png \
   /home/m-tristan/workspace/Lesstime/frontend/public/LOGO_MALIO.png
cp /home/m-tristan/workspace/Starseed/frontend/public/LOGO_MALIO_COLLAPSED.png \
   /home/m-tristan/workspace/Lesstime/frontend/public/LOGO_MALIO_COLLAPSED.png
```

- [ ] **Step 2 : Vérifier**

```bash
ls -la /home/m-tristan/workspace/Lesstime/frontend/public/LOGO_MALIO*.png
```

Expected : deux fichiers présents (~5.8K et ~2.2K).

- [ ] **Step 3 : Commit (sur demande utilisateur)**

```bash
git add frontend/public/LOGO_MALIO.png frontend/public/LOGO_MALIO_COLLAPSED.png
git commit -m "chore(sidebar) : ajout des logos Malio (déplié / replié)"
```

---

## Task 3 : Frontend — migration du layout vers `MalioSidebar`

**Files:**
- Modify: `frontend/app/layouts/default.vue`

**Interfaces:**
- Consumes : `useSidebar().sections` (clés i18n des Task 1), `useUiStore().sidebarCollapsed`, `SidebarTimer` (`:collapsed`), `useAppVersion().version`, `useMailStore().globalUnreadCount`, `useShareStatus()`, `auth.user.isEmployee`, `auth.user.roles`, `useI18n().t`.
- Produces : layout rendant `<MalioSidebar>`.

> Ce task est une réécriture cohérente d'un seul fichier : la sidebar doit rester fonctionnelle (toutes features préservées) à la fin du task. On ne committe pas d'état intermédiaire cassé.

- [ ] **Step 1 : Remplacer le bloc `<aside>…</aside>` (lignes 13-104) par `<MalioSidebar>`**

Nouveau template de la zone sidebar (remplace l'overlay mobile lignes 5-11 **et** l'`<aside>`) :

```vue
            <MalioSidebar
                v-model="ui.sidebarCollapsed"
                :sections="mergedSections"
                :sidebar-class="ui.sidebarCollapsed ? '' : 'w-[232px]'"
            >
                <template #logo>
                    <img src="/LOGO_MALIO.png" alt="Malio"/>
                </template>
                <template #logo-collapsed>
                    <img src="/LOGO_MALIO_COLLAPSED.png" alt="Malio"/>
                </template>
                <template #footer>
                    <div class="flex flex-col gap-2">
                        <SidebarTimer :collapsed="false" />
                        <p v-if="version" class="text-center text-sm font-bold">v {{ version }}</p>
                    </div>
                </template>
                <template #footer-collapsed>
                    <SidebarTimer :collapsed="true" />
                </template>
            </MalioSidebar>
```

Le bloc `<div class="h-full flex-1 …">` (AppTopNav + `<main>` + `<slot/>`) et le `<TimeEntryDrawer>` restent **inchangés**.

- [ ] **Step 2 : Remplacer la logique `translatedSections` par `mergedSections` dans le `<script setup>`**

Supprimer le computed `translatedSections` (lignes 144-156) et le remplacer par :

```ts
type MalioItem = { label: string; to: string; exact?: boolean }
type MalioSection = { label: string; icon: string; items: MalioItem[] }

// Ordre d'affichage canonique des sections.
const SECTION_ORDER = [
    'sidebar.general.section',
    'sidebar.tools.section',
    'sidebar.admin.section',
] as const

// Icônes de secours pour les sections créées côté client (absentes du backend,
// ex. module mail off mais partage actif → section Outils à recréer).
const SECTION_ICON: Record<string, string> = {
    'sidebar.general.section': 'mdi:view-dashboard-outline',
    'sidebar.tools.section': 'mdi:tools',
    'sidebar.admin.section': 'mdi:cog-outline',
}

// Items rendus côté client (dépendent d'un état runtime ignoré du backend).
function clientItemsFor(key: string): MalioItem[] {
    if (key === 'sidebar.general.section') {
        const items: MalioItem[] = []
        if (currentProjectId.value) {
            const id = currentProjectId.value
            items.push({ label: t('sidebar.project.kanban'), to: `/projects/${id}`, exact: true })
            items.push({ label: t('sidebar.project.groups'), to: `/projects/${id}/groups` })
            items.push({ label: t('sidebar.project.archives'), to: `/projects/${id}/archives` })
        }
        if (isEmployee.value) {
            items.push({ label: t('sidebar.general.myAbsences'), to: '/absences' })
        }
        return items
    }
    if (key === 'sidebar.tools.section') {
        const items: MalioItem[] = []
        if (isMailVisible.value) {
            const n = mailStore.globalUnreadCount
            const suffix = n > 0 ? ` (${n > 99 ? '99+' : n})` : ''
            items.push({ label: `${t('mail.sidebar.title')}${suffix}`, to: '/mail' })
        }
        if (isDocumentsVisible.value) {
            items.push({ label: t('sharedFiles.sidebar.title'), to: '/documents' })
        }
        return items
    }
    return []
}

const mergedSections = computed<MalioSection[]>(() => {
    // 1. Sections backend (déjà filtrées par permissions), mail retiré (ré-injecté côté client).
    const backend = new Map<string, MalioSection>()
    for (const section of sections.value) {
        backend.set(section.label, {
            label: t(section.label),
            icon: section.icon,
            items: section.items
                .filter((item) => item.to !== '/mail')
                .map((item) => ({ label: t(item.label), to: item.to })),
        })
    }

    // 2. Fusion dans l'ordre canonique.
    const result: MalioSection[] = []
    for (const key of SECTION_ORDER) {
        const base = backend.get(key)
        const extra = clientItemsFor(key)
        if (base) {
            base.items.push(...extra)
            if (base.items.length > 0) {
                result.push(base)
            }
        } else if (extra.length > 0) {
            result.push({ label: t(key), icon: SECTION_ICON[key], items: extra })
        }
    }

    // 3. Garde-fou : toute section backend hors ordre canonique n'est pas perdue.
    for (const [key, section] of backend) {
        if (!(SECTION_ORDER as readonly string[]).includes(key) && section.items.length > 0) {
            result.push(section)
        }
    }

    return result
})
```

> `isDocumentsVisible` existe déjà (ligne 166). `isMailVisible`, `isEmployee`, `currentProjectId`, `sections`, `mailStore`, `t`, `version`, `ui` sont déjà déclarés — ne pas les redéclarer.

- [ ] **Step 3 : Nettoyer le `<script>` et les imports devenus inutiles**

- Supprimer `sidebarIsCollapsed` (computed lignes 169-172) **si** plus utilisé après suppression de l'`<aside>` (l'était pour le rendu manuel). Vérifier qu'aucune autre référence ne subsiste :

```bash
grep -n "sidebarIsCollapsed" frontend/app/layouts/default.vue
```

S'il ne reste aucune occurrence hors déclaration, supprimer le computed.

- Conserver `watch(() => route.path, () => { ui.closeMobileSidebar() })` (fermeture mobile sur navigation).
- Vérifier que `SidebarLink` n'est plus référencé dans ce fichier (le composant Malio le remplace) :

```bash
grep -n "SidebarLink" frontend/app/layouts/default.vue
```

Expected : aucune occurrence.

- [ ] **Step 4 : Build de vérification**

```bash
cd /home/m-tristan/workspace/Lesstime/frontend && npm run build
```

Expected : build Nuxt réussi, **aucune erreur TypeScript** ni de template. (Si `mergedSections`/types invalides, le build échoue ici.)

- [ ] **Step 5 : QA manuelle (dev server)**

```bash
make dev-nuxt   # port 3002
```

Vérifier en **admin** (`admin`/`admin`) :
- 3 groupes : Général, Outils, Administration.
- Général : Tableau de bord, Mes tâches, Projets, Suivi de temps.
- En ouvrant un projet (`/projects/<id>`) : Kanban/Groupes/Archives apparaissent dans Général ; Kanban actif uniquement sur la page kanban (exact).
- Outils : Messagerie (+ `(N)` si non-lus), Documents (si partage activé).
- Administration : Absences équipe, Répertoire, Rapports, Administration.
- Footer : timer cliquable (start/stop) + `v <version>` ; en replié, le timer reste (icône) et la version disparaît.
- Logo Malio déplié + replié (collapsed via toggle du composant).
- Route active surlignée ; pas de doublon `/mail`.

Vérifier en **utilisateur non-admin** (`alice`/`alice`) :
- **Pas** de groupe Administration.
- Items gated par permission absents si l'utilisateur n'a pas la permission.
- Mes absences visible uniquement si `isEmployee`.

- [ ] **Step 6 : Vérifier le comportement mobile (largeur < lg)**

Réduire la fenêtre / activer le responsive devtools.
- Vérifier l'ouverture/fermeture de la sidebar sur mobile.
- Vérifier le bouton hamburger éventuel de `AppTopNav` :

```bash
grep -rn "openMobileSidebar\|sidebarOpen\|closeMobileSidebar" frontend/app/components/ frontend/components/ frontend/app/layouts/default.vue
```

- Si `MalioSidebar` gère le responsive et que l'overlay supprimé n'est plus nécessaire : OK.
- Si l'ouverture mobile ne fonctionne plus (ex. AppTopNav appelait `openMobileSidebar` pour l'ancien overlay) : adapter **sans modifier la lib** — a minima conserver le repli/déploiement via `ui.sidebarCollapsed`, ou conserver un déclencheur. Documenter le choix retenu dans le commit.

- [ ] **Step 7 : Commit (sur demande utilisateur)**

```bash
git add frontend/app/layouts/default.vue
git commit -m "feat(sidebar) : migration du layout vers MalioSidebar (footer timer + version, logo Malio)"
```

---

## Task 4 : Nettoyage des éléments obsolètes

**Files:**
- Possible delete: `frontend/components/ui/SidebarLink.vue`
- Possible delete: anciens logos `frontend/public/malio.png`, `frontend/public/LOGO_CARRE.png`

**Interfaces:** aucun (suppression sûre uniquement si zéro référence).

- [ ] **Step 1 : Vérifier les usages restants de `SidebarLink`**

```bash
grep -rn "SidebarLink" /home/m-tristan/workspace/Lesstime/frontend --include="*.vue" --include="*.ts" | grep -v node_modules
```

- Si **aucune** occurrence : supprimer le fichier.

```bash
git rm frontend/components/ui/SidebarLink.vue
```

- Si encore référencé ailleurs : **ne pas supprimer**, laisser tel quel.

- [ ] **Step 2 : Vérifier les usages des anciens logos**

```bash
grep -rn "malio.png\|LOGO_CARRE.png" /home/m-tristan/workspace/Lesstime/frontend --include="*.vue" --include="*.ts" --include="*.css" | grep -v node_modules
```

- Si **aucune** occurrence : supprimer les deux PNG.

```bash
git rm frontend/public/malio.png frontend/public/LOGO_CARRE.png
```

- Sinon : conserver.

- [ ] **Step 3 : Build final**

```bash
cd /home/m-tristan/workspace/Lesstime/frontend && npm run build
```

Expected : build réussi.

- [ ] **Step 4 : Commit (sur demande utilisateur)**

```bash
git add -A
git commit -m "chore(sidebar) : suppression des composants/assets obsolètes de l'ancienne sidebar"
```

---

## Self-Review (auteur du plan)

**Spec coverage :**
- Remplacement par MalioSidebar → Task 3 ✓
- Permissions serveur préservées → Task 1 (gates inchangés) + Task 3 (mail filtré/ré-injecté, garde-fou sections) ✓
- 3 groupes Général/Outils/Administration → Task 1 + Task 3 (ordre canonique) ✓
- Footer timer + version → Task 3 Step 1 ✓
- Logo Malio Starseed → Task 2 + Task 3 ✓
- Items contextuels (Kanban/Groupes/Archives, Documents, Mes absences) → Task 3 `clientItemsFor` ✓
- Badge mail = suffixe `(N)` → Task 3 `clientItemsFor` ✓
- Mobile → Task 3 Step 6 ✓
- Nettoyage → Task 4 ✓

**Placeholder scan :** pas de TBD ; les branches conditionnelles de suppression (Task 4) et d'adaptation mobile (Task 3 Step 6) sont des décisions binaires basées sur un `grep`, pas des placeholders.

**Type consistency :** `MalioItem`/`MalioSection` définis une fois (Task 3) et utilisés de façon cohérente ; `clientItemsFor`/`mergedSections`/`SECTION_ORDER`/`SECTION_ICON` cohérents. Items produits conformes au type attendu par `MalioSidebar` (`{label, to, exact?}`).

**Réserve connue :** absence de runner de test FE → vérification par build + QA manuelle (assumé, conforme à l'état du repo).
