# Menus — Création et conventions

## Concept

Les menus sont **indépendants des pages**.
Un menu est créé, peuplé d'items, puis assigné à un **emplacement**.
Les emplacements sont définis dans les layouts (header, footer, etc.).

```
MenuLocation "header"   ←── Menu "Menu Principal"
                                ├── Accueil       → page: home
                                ├── Logements     → page: logements
                                │    ├── Appartements → page: appartements
                                │    └── Villas       → page: villas
                                └── Contact       → page: contact

MenuLocation "footer"   ←── Menu "Footer"
                                ├── Mentions légales → url: /mentions
                                └── CGU              → url: /cgu
```

---

## Modèle de données

```sql
menu_locations
  id, slug (unique), label

menus
  id, name, created_at, updated_at

menu_location (pivot)
  menu_id, menu_location_id

menu_items
  id, menu_id, parent_id (nullable → auto-référence),
  type ENUM('page','url','custom'),
  label,
  page_id (nullable, FK → pages),
  url (nullable),
  target ENUM('_self','_blank') DEFAULT '_self',
  order INT DEFAULT 0,
  created_at, updated_at
```

---

## Emplacements définis (seeder)

| Slug | Label | Utilisé dans |
|---|---|---|
| `header` | Menu principal | `DefaultLayout.vue`, tous les layouts |
| `footer` | Footer principal | Tous les layouts |
| `footer-legal` | Footer légal | Tous les layouts |

Pour ajouter un emplacement : ajouter une entrée dans le seeder `MenuLocationSeeder`
et ajouter le `<NavMenu>` correspondant dans le(s) layout(s) concernés.

---

## Relations Eloquent

```php
// Menu.php
public function items(): HasMany
{
    // Uniquement les items racines, avec enfants récursifs
    return $this->hasMany(MenuItem::class)
                ->whereNull('parent_id')
                ->orderBy('order')
                ->with('children');
}

public function locations(): BelongsToMany
{
    return $this->belongsToMany(MenuLocation::class, 'menu_location');
}

// MenuItem.php
public function children(): HasMany
{
    return $this->hasMany(MenuItem::class, 'parent_id')
                ->orderBy('order')
                ->with('children');  // récursif, max 3 niveaux en pratique
}

public function page(): BelongsTo
{
    return $this->belongsTo(Page::class);
}

// Accessor : résout l'URL selon le type
public function getResolvedUrlAttribute(): string
{
    return match($this->type) {
        'page'           => '/' . ($this->page?->slug ?? ''),
        'url', 'custom'  => $this->url ?? '#',
        default          => '#',
    };
}
```

---

## MenuService

```php
// app/Services/MenuService.php

// Retourne tous les menus indexés par slug d'emplacement
// Injecté globalement via HandleInertiaRequests
public static function all(): array
{
    return cache()->remember('menus.all', 3600, function () {
        return MenuLocation::with([
            'menus.items.children.children',
            'menus.items.page',
        ])
        ->get()
        ->mapWithKeys(fn($location) => [
            $location->slug => $location->menus->first()?->items ?? collect()
        ])
        ->toArray();
    });
}

// Appeler après toute mutation de menu ou menu_item
public static function clearCache(): void
{
    cache()->forget('menus.all');
}
```

---

## Injection globale (Inertia)

```php
// app/Http/Middleware/HandleInertiaRequests.php
public function share(Request $request): array
{
    return array_merge(parent::share($request), [
        'menus'    => fn() => MenuService::all(),
        'settings' => fn() => SettingsService::shared(),
        'auth'     => fn() => ['user' => $request->user()],
    ]);
}
```

Accès dans n'importe quel composant Vue :
```js
const { menus } = usePage().props
// menus['header'] → array d'items avec children récursifs
// menus['footer'] → array d'items
```

---

## Composant NavMenu (front-office)

```vue
<!-- frontoffice/components/NavMenu.vue -->
<!-- Récursif : s'appelle lui-même pour les sous-menus -->
<template>
  <ul :class="depth === 0 ? 'nav-menu' : 'nav-submenu'">
    <li v-for="item in items" :key="item.id" class="nav-item">
      <a :href="item.resolved_url" :target="item.target">
        {{ item.label }}
      </a>
      <NavMenu
        v-if="item.children?.length"
        :items="item.children"
        :depth="depth + 1"
      />
    </li>
  </ul>
</template>

<script setup>
defineProps({
  items: { type: Array, default: () => [] },
  depth: { type: Number, default: 0 },
})
</script>
```

---

## Éditeur de menus (back-office)

```
MenuEditor.vue
 ├── LocationSelector.vue      ← Choisir l'emplacement (header / footer...)
 ├── MenuSelector.vue          ← Assigner / créer un menu
 │
 ├── ItemList.vue (vuedraggable, imbriqué)
 │   └── MenuItemRow.vue
 │       ├── drag handle
 │       ├── label + url résolue
 │       ├── bouton éditer → ouvre ItemForm
 │       └── ItemList récursif (sous-items)
 │
 └── AddItemPanel.vue
     ├── Onglet "Pages"      → liste des pages publiées
     ├── Onglet "URL"        → champ url libre
     └── Onglet "Personnalisé" → label + url + target
```

### Sauvegarde de l'ordre (drag & drop)

```
PUT /admin/menus/{menu}/items/reorder
Body: { items: [ { id, parent_id, order }, ... ] }  ← arbre à plat
```

---

## Checklist lors de toute modification des menus

- [ ] Appeler `MenuService::clearCache()` après insertion / mise à jour / suppression
- [ ] Sauvegarder l'ordre via `reorder` et non item par item
- [ ] Ne jamais supprimer un `MenuLocation` (ils sont fixes, définis par les layouts)
