# Guide Frontend — Inventory

Guide complet du frontend Nuxt/Vue pour comprendre comment tout fonctionne, même si tu débutes.

## Table des matières

1. [Vue d'ensemble](#vue-densemble)
2. [Nuxt et Vue — les bases](#nuxt-et-vue--les-bases)
3. [L'architecture de l'application](#larchitecture-de-lapplication)
4. [Les Pages (le routing)](#les-pages)
5. [Les Composables (la logique métier)](#les-composables)
6. [Les Composants (l'interface)](#les-composants)
7. [L'API et les appels HTTP](#lapi-et-les-appels-http)
8. [L'authentification côté frontend](#lauthentification-côté-frontend)
9. [Le style (TailwindCSS + DaisyUI)](#le-style)
10. [Les utilitaires et types](#les-utilitaires-et-types)
11. [Flux complet d'une fonctionnalité](#flux-complet-dune-fonctionnalité)
12. [Patterns et conventions du projet](#patterns-et-conventions)
13. [Les tests](#les-tests)
14. [Commandes utiles](#commandes-utiles)

---

## Vue d'ensemble

Le frontend est une **SPA** (Single Page Application) construite avec :

- **Nuxt 4** : framework basé sur Vue.js qui ajoute le routing automatique, les composables, et plein d'outils
- **Vue 3** : la librairie d'interface (composants, réactivité, etc.)
- **TypeScript** : JavaScript avec des types (réduit les bugs)
- **TailwindCSS 4** : des classes CSS utilitaires (`flex`, `p-4`, `text-lg`, etc.)
- **DaisyUI 5** : des composants visuels prêts à l'emploi (boutons, modales, tableaux, etc.)

### SPA, c'est quoi ?

Dans une SPA, le navigateur charge **une seule page HTML** au départ, puis tout se passe en JavaScript : la navigation entre les pages, le chargement de données, etc. Pas de rechargement complet de la page.

> **SSR est désactivé** (`ssr: false` dans `nuxt.config.ts`). Le rendu se fait entièrement côté client (dans le navigateur).

---

## Nuxt et Vue — les bases

### Vue 3 en 30 secondes

Vue utilise des **composants** : des blocs réutilisables qui combinent HTML, JavaScript et CSS.

```vue
<script setup lang="ts">
// La partie logique (JavaScript/TypeScript)
import { ref, computed } from 'vue'

const count = ref(0)                          // ref() = variable réactive
const doubled = computed(() => count.value * 2) // computed() = valeur calculée

function increment() {
  count.value++                               // Modifier un ref → l'interface se met à jour
}
</script>

<template>
  <!-- La partie visuelle (HTML) -->
  <div>
    <p>Compteur : {{ count }}</p>              <!-- {{ }} = afficher une valeur -->
    <p>Double : {{ doubled }}</p>
    <button @click="increment">+1</button>    <!-- @click = événement au clic -->
  </div>
</template>
```

### Les concepts clés de Vue 3

| Concept | Syntaxe | Description |
|---------|---------|-------------|
| **ref** | `const x = ref(0)` | Variable réactive. Quand elle change, l'interface se met à jour |
| **computed** | `const y = computed(() => x.value * 2)` | Valeur calculée, se recalcule automatiquement |
| **v-model** | `<input v-model="name">` | Lie un input à une variable (bidirectionnel) |
| **v-if** | `<div v-if="show">` | Afficher conditionnellement |
| **v-for** | `<div v-for="item in items">` | Boucler sur une liste |
| **@event** | `<button @click="fn">` | Écouter un événement |
| **:prop** | `<Comp :title="myTitle">` | Passer une valeur à un composant enfant |
| **emit** | `emit('save', data)` | Envoyer un événement au composant parent |

### Ce que Nuxt ajoute par rapport à Vue seul

| Fonctionnalité | Description |
|----------------|-------------|
| **File-based routing** | Chaque fichier dans `pages/` crée automatiquement une route URL |
| **Auto-imports** | Les composants et composables sont importés automatiquement |
| **Middleware** | Code qui s'exécute avant chaque navigation (ex: vérifier l'auth) |
| **useRoute / navigateTo** | Utilitaires de navigation fournis par Nuxt |
| **nuxt.config.ts** | Configuration centralisée du projet |

---

## L'architecture de l'application

### Structure des dossiers

```
app/
├── pages/                  # Les pages (1 fichier = 1 URL)
│   ├── index.vue           # → /
│   ├── machines/
│   │   └── index.vue       # → /machines
│   ├── machine/
│   │   └── [id].vue        # → /machine/abc123 (paramètre dynamique)
│   └── ...
│
├── components/             # Les composants réutilisables (auto-importés)
│   ├── common/             #   Composants génériques (DataTable, Pagination, Modal)
│   ├── form/               #   Champs de formulaire (email, téléphone)
│   ├── layout/             #   Navbar
│   ├── machine/            #   Composants spécifiques aux machines
│   ├── model-types/        #   Composants de gestion des types
│   └── sites/              #   Composants de gestion des sites
│
├── composables/            # La logique métier (appels API, gestion d'état)
│   ├── useApi.ts           #   Wrapper HTTP centralisé
│   ├── useMachines.ts      #   CRUD machines
│   ├── useProfileSession.ts #  Gestion de session
│   └── ...                 #   Un composable par domaine
│
├── shared/                 # Types, utilitaires, validation
│   ├── types/              #   Interfaces TypeScript
│   ├── utils/              #   Fonctions helpers
│   ├── validation/         #   Validation email, téléphone
│   └── model/              #   Structures de données
│
├── middleware/             # Code exécuté avant chaque navigation
│   └── profile.global.ts  #   Vérifie que l'utilisateur est connecté
│
├── services/              # Couche service
│   └── modelTypes.ts      #   Service spécialisé
│
├── utils/                 # Fonctions de formatage
│   └── ...
│
├── assets/                # Fichiers CSS, images
│   └── app.css            #   Styles globaux
│
└── app.vue                # Le composant racine de l'application
```

### Le composant racine : app.vue

C'est le "cadre" de l'application. Tout ce qui est affiché passe par ce fichier :

```vue
<template>
  <div class="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
    <!-- La barre de navigation (toujours visible) -->
    <LayoutAppNavbar @open-settings="showSettings = true" @logout="logout" />

    <!-- Le contenu de la page (change selon l'URL) -->
    <NuxtPage />

    <!-- Les éléments globaux (toujours disponibles) -->
    <ToastContainer />          <!-- Notifications en bas à droite -->
    <CommonConfirmModal />      <!-- Modale de confirmation -->
    <DisplaySettings />         <!-- Paramètres d'affichage -->

    <footer>© Malio 2025 · v{{ appVersion }}</footer>
  </div>
</template>
```

---

## Les Pages

### Le routing basé sur les fichiers

Nuxt crée automatiquement les routes à partir de la structure des fichiers dans `pages/` :

| Fichier | URL | Description |
|---------|-----|-------------|
| `pages/index.vue` | `/` | Page d'accueil (dashboard) |
| `pages/profiles/index.vue` | `/profiles` | Page de login |
| `pages/machines/index.vue` | `/machines` | Catalogue des machines |
| `pages/machines/new.vue` | `/machines/new` | Création d'une machine |
| `pages/machine/[id].vue` | `/machine/abc123` | Détail d'une machine (id dynamique) |
| `pages/component-catalog.vue` | `/component-catalog` | Catalogue des composants |
| `pages/component/create.vue` | `/component/create` | Création d'un composant |
| `pages/component/[id]/edit.vue` | `/component/abc123/edit` | Édition d'un composant |
| `pages/pieces-catalog.vue` | `/pieces-catalog` | Catalogue des pièces |
| `pages/product-catalog.vue` | `/product-catalog` | Catalogue des produits |
| `pages/sites.vue` | `/sites` | Gestion des sites |
| `pages/constructeurs.vue` | `/constructeurs` | Gestion des fournisseurs |
| `pages/models/index.vue` | `/models` | Gestion des types/catégories |
| `pages/documents.vue` | `/documents` | Navigateur de documents |
| `pages/comments.vue` | `/comments` | Commentaires/tickets |
| `pages/activity-log.vue` | `/activity-log` | Journal d'activité |
| `pages/admin/index.vue` | `/admin` | Administration (profils) |
| `pages/changelog.vue` | `/changelog` | Historique des versions |

### Les paramètres dynamiques

Les crochets `[id]` dans le nom du fichier créent un paramètre dynamique :

```vue
<!-- pages/machine/[id].vue -->
<script setup lang="ts">
const route = useRoute()
const machineId = route.params.id   // → "abc123" si l'URL est /machine/abc123
</script>
```

### Anatomie d'une page typique

```vue
<!-- pages/machines/index.vue — Catalogue des machines -->
<script setup lang="ts">
// 1. Utiliser les composables pour la logique
const { machines, loading, loadMachines } = useMachines()
const { canEdit } = usePermissions()

// 2. Charger les données au montage
onMounted(() => {
  loadMachines()
})

// 3. Logique de la page
function handleCreate() {
  navigateTo('/machines/new')
}
</script>

<template>
  <!-- 4. L'interface -->
  <div class="container mx-auto p-4">
    <PageHero title="Machines" subtitle="Catalogue des machines du parc">
      <button v-if="canEdit" class="btn btn-primary" @click="handleCreate">
        <IconLucidePlus class="w-5 h-5" />
        Nouvelle machine
      </button>
    </PageHero>

    <!-- Composant DataTable pour afficher les données -->
    <CommonDataTable
      :items="machines"
      :loading="loading"
      :columns="columns"
    />
  </div>
</template>
```

---

## Les Composables

### C'est quoi un composable ?

Un composable est une **fonction qui encapsule de la logique réutilisable**. C'est le pattern central du projet. Au lieu de mettre toute la logique dans les pages/composants, on la met dans des composables.

### Pourquoi ?

- **Réutilisation** : la même logique peut être utilisée dans plusieurs pages
- **Séparation** : la page s'occupe de l'affichage, le composable de la logique
- **Testabilité** : plus facile à tester isolément

### Les composables principaux

#### useApi.ts — Le wrapper HTTP

C'est le composable le plus important : il centralise **tous les appels API**.

```typescript
// Utilisation dans un autre composable
const api = useApi()

// GET : récupérer des données
const result = await api.get<Machine[]>('/machines')
if (result.success) {
  console.log(result.data)  // → les machines
}

// POST : créer une donnée
const result = await api.post<Machine>('/machines', {
  name: 'CNC 01',
  reference: 'CNM-001',
  site: '/api/sites/cl...',
})

// PATCH : modifier partiellement
const result = await api.patch<Machine>('/machines/cl...', {
  name: 'CNC 02',
})

// DELETE : supprimer
const result = await api.delete('/machines/cl...')
```

**Points clés :**
- Ajoute automatiquement `credentials: 'include'` (cookies de session)
- Utilise `application/ld+json` pour POST/PUT et `application/merge-patch+json` pour PATCH
- Gère les erreurs et affiche des toasts automatiquement
- Retourne toujours un objet `{ success, data?, error?, status? }`

#### useProfileSession.ts — Gestion de session

```typescript
const { activeProfile, sessionLoaded, loading } = useProfileSession()

// Vérifier la session (appelé automatiquement par le middleware)
await ensureSession()

// Se connecter
await activateProfile('cl...profileId', 'password123')

// Se déconnecter
await logout()
```

#### usePermissions.ts — Vérification des droits

```typescript
const { isAdmin, canEdit, canView } = usePermissions()

// Utilisation dans le template
// <button v-if="canEdit">Modifier</button>
// <div v-if="isAdmin">Section admin</div>
```

#### useMachines.ts — CRUD machines

Pattern typique : un composable par domaine métier.

```typescript
const {
  machines,        // ref<Machine[]> — la liste des machines
  loading,         // ref<boolean> — en cours de chargement ?
  loaded,          // ref<boolean> — déjà chargé ?
  loadMachines,    // () => Promise<void>
  createMachine,   // (data) => Promise<Machine | null>
  updateMachine,   // (id, data) => Promise<boolean>
  deleteMachine,   // (id) => Promise<boolean>
  cloneMachine,    // (sourceId, data) => Promise<Machine | null>
} = useMachines()
```

Ce pattern se répète pour `useComposants`, `usePieces`, `useProducts`, `useConstructeurs`, `useSites`, etc.

#### useDataTable.ts — Table de données générique

Gère la pagination, le tri, la recherche et les filtres pour toutes les pages catalogue :

```typescript
const {
  sort,              // { field: 'name', direction: 'asc' }
  pagination,        // { currentPage: 1, totalPages: 5, perPage: 30, ... }
  handleSort,        // (field) => void
  handlePageChange,  // (page) => void
  handlePerPageChange, // (perPage) => void
} = useDataTable({ ... })
```

#### useConfirm.ts — Modale de confirmation

```typescript
const { confirm } = useConfirm()

// Afficher une modale et attendre la réponse
const ok = await confirm({
  title: 'Supprimer la machine ?',
  message: 'Cette action est irréversible.',
})
if (ok) {
  // l'utilisateur a cliqué "Confirmer"
}
```

#### useToast.ts — Notifications

```typescript
const { showSuccess, showError, showWarning, showInfo } = useToast()

showSuccess('Machine créée avec succès')
showError('Erreur lors de la suppression')
```

#### useMachineDetailData.ts — Le composable le plus complexe

Gère toute la logique de la page de détail d'une machine :
- Chargement des données (machine, composants, pièces, produits, documents, custom fields)
- Mode édition (toggle)
- Mise à jour des champs
- Ajout/suppression de liens (composant, pièce, produit)
- Upload/suppression de documents

#### useDocuments.ts — Gestion de fichiers

```typescript
const { uploadDocuments, deleteDocument, loadDocumentsByMachine } = useDocuments()

// Upload de fichiers
await uploadDocuments('machine', machineId, fileList)

// Suppression
await deleteDocument(documentId)
```

#### useEntityHistory.ts — Historique d'audit

```typescript
const { history, loading, loadHistory } = useEntityHistory()

await loadHistory('machine', machineId)
// history.value → liste des événements d'audit
```

### Pattern des composables (convention du projet)

Certains composables utilisent une injection de dépendances explicite :

```typescript
// Définir les dépendances nécessaires
interface Deps {
  machineId: Ref<string>
  onSave: () => void
}

// Le composable reçoit ses dépendances
export function useMachineDetail(deps: Deps) {
  const { machineId, onSave } = deps

  // ... logique utilisant machineId et onSave
}
```

---

## Les Composants

### Auto-import par Nuxt

Tous les composants dans `components/` sont auto-importés. Pas besoin de `import` :

```vue
<!-- Le composant CommonDataTable est importé automatiquement -->
<template>
  <CommonDataTable :items="items" />
</template>
```

La convention de nommage utilise le chemin du dossier :
- `components/common/DataTable.vue` → `<CommonDataTable />`
- `components/machine/InfoCard.vue` → `<MachineInfoCard />`
- `components/layout/AppNavbar.vue` → `<LayoutAppNavbar />`

### Les composants communs (réutilisables)

#### CommonDataTable

Le composant central pour tous les tableaux avec pagination, tri et recherche :

```vue
<CommonDataTable
  :items="machines"
  :columns="[
    { key: 'name', label: 'Nom', sortable: true },
    { key: 'reference', label: 'Référence', sortable: true },
    { key: 'prix', label: 'Prix', sortable: true },
  ]"
  :loading="loading"
  :pagination="pagination"
  @sort="handleSort"
  @page-change="handlePageChange"
/>
```

#### CommonConfirmModal

Modale de confirmation globale (utilisée via `useConfirm()`) :

```vue
<!-- Utilisé dans app.vue, piloté par useConfirm -->
<CommonConfirmModal />
```

#### CommonSearchSelect

Dropdown avec recherche intégrée :

```vue
<CommonSearchSelect
  v-model="selectedSiteId"
  :options="sites"
  label-key="name"
  value-key="id"
  placeholder="Choisir un site"
/>
```

#### CommonPagination

Composant de pagination :

```vue
<CommonPagination
  :current-page="pagination.currentPage"
  :total-pages="pagination.totalPages"
  @page-change="handlePageChange"
/>
```

### Les composants machine

| Composant | Rôle |
|-----------|------|
| `MachineDetailHeader` | En-tête avec boutons (éditer, imprimer) |
| `MachineInfoCard` | Carte avec les infos de la machine (nom, référence, constructeur) |
| `MachineDocumentsCard` | Upload et prévisualisation des documents |
| `MachineComponentsCard` | Liste des composants liés (hiérarchique) |
| `MachinePiecesCard` | Liste des pièces liées |
| `MachineProductsCard` | Liste des produits liés |
| `AddEntityToMachineModal` | Modale pour ajouter un composant/pièce/produit |

### Les composants sites

| Composant | Rôle |
|-----------|------|
| `SiteCard` | Carte d'affichage d'un site |
| `SiteCreateModal` | Modale de création de site |
| `SiteEditModal` | Modale d'édition de site |
| `SiteContactFormFields` | Champs de formulaire contact (réutilisable) |

### Les composants model-types

| Composant | Rôle |
|-----------|------|
| `ManagementView` | Page principale de gestion des types |
| `Table` | Tableau des types/catégories |
| `Toolbar` | Barre de recherche + filtres |
| `ModelTypeForm` | Formulaire d'édition/création |
| `ConversionModal` | Modale de conversion de catégorie |

### Communication entre composants

Le projet utilise **exclusivement Props + Events** (pas de `provide/inject`) :

```vue
<!-- Parent (page) -->
<template>
  <MachineInfoCard
    :machine="machine"           <!-- Props : données passées à l'enfant -->
    :edit-mode="isEditing"
    @save="handleSave"           <!-- Events : l'enfant notifie le parent -->
    @cancel="isEditing = false"
  />
</template>

<!-- Enfant (composant) -->
<script setup lang="ts">
// Déclarer les props reçues
const props = defineProps<{
  machine: Machine
  editMode: boolean
}>()

// Déclarer les événements émis
const emit = defineEmits<{
  save: [data: Partial<Machine>]
  cancel: []
}>()

function handleSave() {
  emit('save', { name: newName.value })   // Notifier le parent
}
</script>
```

---

## L'API et les appels HTTP

### Le composable useApi.ts

Tous les appels API passent par `useApi.ts`. Voici ce qu'il fait :

1. **Ajoute le base URL** : `/machines` → `http://localhost:8081/api/machines`
2. **Ajoute les headers** :
   - `Content-Type: application/ld+json` (POST/PUT)
   - `Content-Type: application/merge-patch+json` (PATCH)
3. **Inclut les cookies** : `credentials: 'include'`
4. **Gère les erreurs** : parse les erreurs backend et les traduit en français
5. **Retourne un objet standardisé** : `{ success, data, error, status }`

### Les IRIs (Internationalized Resource Identifiers)

L'API utilise des IRIs pour les relations. Au lieu de passer un simple ID, on passe le chemin complet :

```typescript
// ❌ Ne pas faire
{ "site": "cl9z8y7x..." }

// ✅ Faire
{ "site": "/api/sites/cl9z8y7x..." }
```

Le fichier `shared/utils/apiRelations.ts` fournit des helpers :

```typescript
import { toIri, extractRelationId, normalizeRelationIds } from '~/shared/utils/apiRelations'

// Construire un IRI
toIri('sites', 'cl9z8y7x...')    // → "/api/sites/cl9z8y7x..."

// Extraire l'ID d'un IRI
extractRelationId('/api/sites/cl9z8y7x...')    // → "cl9z8y7x..."

// Convertir les IDs locaux en IRIs dans un payload
normalizeRelationIds({
  siteId: 'cl9z8y7x...',
  constructeurIds: ['cl111...', 'cl222...'],
})
// → { site: "/api/sites/cl9z8y7x...", constructeurs: ["/api/constructeurs/cl111...", ...] }
```

### Les collections API (pagination)

Les réponses de collection suivent le format Hydra :

```json
{
  "hydra:totalItems": 42,
  "hydra:member": [
    { "id": "cl...", "name": "Machine 1" },
    { "id": "cl...", "name": "Machine 2" }
  ]
}
```

Le helper `extractCollection()` gère les différents formats :

```typescript
import { extractCollection } from '~/shared/utils/apiHelpers'

const result = await api.get('/machines?page=1')
const machines = extractCollection(result.data)    // → Machine[]
```

### Les Content-Types

| Opération | Content-Type | Quand |
|-----------|-------------|-------|
| POST | `application/ld+json` | Créer une ressource |
| PUT | `application/ld+json` | Remplacer une ressource |
| PATCH | `application/merge-patch+json` | Modifier partiellement |
| Upload fichier | `multipart/form-data` | Envoyer un fichier |

---

## L'authentification côté frontend

### Le flux complet

```
1. L'utilisateur ouvre l'application
   ↓
2. Le middleware profile.global.ts s'exécute
   ↓
3. Il appelle ensureSession() → GET /api/session/profile
   ↓
4a. Si la session est valide → l'utilisateur voit la page demandée
4b. Si pas de session → redirection vers /profiles (page de login)
   ↓
5. L'utilisateur choisit son profil et entre son mot de passe
   ↓
6. POST /api/session/profile → le backend crée la session
   ↓
7. Redirection vers la page d'accueil
```

### Le middleware global

```typescript
// middleware/profile.global.ts
export default defineNuxtRouteMiddleware(async (to) => {
  // Ne pas vérifier la session si on est déjà sur la page de login
  if (to.path === '/profiles') return

  const { ensureSession, activeProfile } = useProfileSession()
  await ensureSession()

  // Pas de session → page de login
  if (!activeProfile.value) {
    return navigateTo('/profiles')
  }

  // Routes admin → vérifier le rôle
  if (to.path.startsWith('/admin') && !isAdmin.value) {
    return navigateTo('/')
  }
})
```

### Les permissions dans les templates

```vue
<script setup>
const { isAdmin, canEdit, canView } = usePermissions()
</script>

<template>
  <!-- Bouton visible uniquement pour les gestionnaires et admins -->
  <button v-if="canEdit" class="btn btn-primary">
    Modifier
  </button>

  <!-- Section visible uniquement pour les admins -->
  <div v-if="isAdmin">
    <h2>Administration</h2>
    <!-- ... -->
  </div>
</template>
```

---

## Le style

### TailwindCSS 4

TailwindCSS utilise des classes utilitaires au lieu de CSS personnalisé :

```html
<!-- Au lieu d'écrire du CSS personnalisé... -->
<div style="display: flex; padding: 16px; gap: 8px; background: white; border-radius: 8px;">

<!-- ...on utilise des classes Tailwind -->
<div class="flex p-4 gap-2 bg-white rounded-lg">
```

**Classes les plus utilisées :**

| Catégorie | Classes | Description |
|-----------|---------|-------------|
| Layout | `flex`, `grid`, `block`, `hidden` | Type d'affichage |
| Espacement | `p-4`, `px-2`, `py-6`, `m-2`, `gap-4` | Padding, margin, gap |
| Taille | `w-full`, `h-10`, `max-w-lg`, `min-h-screen` | Largeur, hauteur |
| Texte | `text-lg`, `text-sm`, `font-bold`, `text-gray-500` | Taille, poids, couleur |
| Fond | `bg-white`, `bg-blue-100`, `bg-base-200` | Couleur de fond |
| Bordure | `border`, `rounded-lg`, `border-gray-300` | Bordures |
| Responsive | `md:flex`, `lg:hidden`, `sm:p-2` | Styles conditionnels par taille d'écran |

### DaisyUI 5

DaisyUI ajoute des classes de composants par-dessus Tailwind :

```html
<!-- Bouton -->
<button class="btn btn-primary btn-sm md:btn-md">Sauvegarder</button>

<!-- Input -->
<input class="input input-bordered input-sm md:input-md" placeholder="Nom">

<!-- Select -->
<select class="select select-bordered select-sm md:select-md">
  <option>Option 1</option>
</select>

<!-- Textarea -->
<textarea class="textarea textarea-bordered textarea-sm md:textarea-md"></textarea>

<!-- Carte -->
<div class="card bg-base-100 shadow-md">
  <div class="card-body">
    <h2 class="card-title">Titre</h2>
    <p>Contenu</p>
    <div class="card-actions justify-end">
      <button class="btn btn-primary">Action</button>
    </div>
  </div>
</div>

<!-- Modale -->
<dialog class="modal modal-open">
  <div class="modal-box">
    <h3>Titre</h3>
    <p>Contenu</p>
    <div class="modal-action">
      <button class="btn">Fermer</button>
    </div>
  </div>
</dialog>

<!-- Badge -->
<span class="badge badge-primary badge-sm">Actif</span>

<!-- Tableau -->
<table class="table table-sm">
  <thead><tr><th>Nom</th><th>Email</th></tr></thead>
  <tbody><tr><td>Jean</td><td>jean@mail.com</td></tr></tbody>
</table>

<!-- Loading -->
<span class="loading loading-spinner loading-lg"></span>
```

### Les icônes Lucide

Le projet utilise les icônes Lucide via `unplugin-icons`. Elles sont auto-importées avec le préfixe `Icon` :

```vue
<IconLucidePlus class="w-5 h-5" />
<IconLucideTrash class="w-4 h-4 text-error" />
<IconLucideEdit class="w-4 h-4" />
<IconLucideChevronDown class="w-4 h-4" />
<IconLucideFactory class="w-6 h-6" />
```

---

## Les utilitaires et types

### Types TypeScript (`shared/types/`)

Les interfaces définissent la forme des données :

```typescript
// shared/types/inventory.ts

interface Machine {
  id: string
  name: string
  reference: string
  prix: string | null
  site: string          // IRI du site
  constructeurs: string[] // IRIs des constructeurs
  createdAt: string
  updatedAt: string
}

interface Site {
  id: string
  name: string
  contactName: string | null
  contactPhone: string | null
  contactAddress: string | null
  contactPostalCode: string | null
  contactCity: string | null
}

// Types de colonnes pour DataTable
interface DataTableSort {
  field: string
  direction: 'asc' | 'desc'
}

interface DataTablePagination {
  currentPage: number
  totalPages: number
  totalItems: number
  perPage: number
}
```

### Utilitaires (`shared/utils/`)

| Fichier | Rôle |
|---------|------|
| `apiRelations.ts` | Conversion ID ↔ IRI pour les relations API |
| `apiHelpers.ts` | Extraction de collections API (hydra) |
| `errorMessages.ts` | Traduction des erreurs backend en français |
| `customFieldUtils.ts` | Formatage et logique des champs personnalisés |
| `documentDisplayUtils.ts` | Prévisualisation, icônes, tailles de fichiers |
| `productDisplayUtils.ts` | Affichage produit (référence, prix, fournisseurs) |
| `deleteImpactUtils.ts` | Analyse d'impact avant suppression |
| `historyDisplayUtils.ts` | Formatage du journal d'audit |

### Validation (`shared/validation/`)

```typescript
// shared/validation/email.ts
export function isValidEmail(email: string): boolean {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
}

// shared/validation/phone.ts
export function isValidPhone(phone: string): boolean {
  return /^[\d\s\-+().]{6,20}$/.test(phone)
}
```

---

## Flux complet d'une fonctionnalité

### Exemple : afficher et modifier une machine

```
1. L'utilisateur clique sur une machine dans le catalogue
   → navigateTo('/machine/cl1a2b3c...')

2. Le middleware profile.global.ts vérifie la session → OK

3. La page machine/[id].vue se monte :
   const route = useRoute()
   const machineId = route.params.id    // → "cl1a2b3c..."

4. Le composable useMachineDetailData(machineId) est appelé :
   → GET /api/machines/cl1a2b3c...          → données de la machine
   → GET /api/machines/cl1a2b3c.../structure → composants, pièces, produits
   → GET /api/documents/by-machine/cl1a2b3c... → documents
   → GET /api/custom-field-values?machine=...  → champs personnalisés

5. Les données arrivent dans des refs réactifs :
   machine.value = { id: "cl1a2b3c...", name: "CNC 01", ... }
   components.value = [...]
   documents.value = [...]

6. Le template affiche les composants :
   <MachineInfoCard :machine="machine" :edit-mode="isEditing" @save="handleSave" />
   <MachineComponentsCard :components="components" />
   <MachineDocumentsCard :documents="documents" @upload="handleUpload" />

7. L'utilisateur clique sur "Modifier" → isEditing = true
   → Les champs deviennent éditables (inputs au lieu de texte)

8. L'utilisateur modifie le nom et clique "Sauvegarder"
   → handleSave({ name: "CNC 02" })
   → PATCH /api/machines/cl1a2b3c... { "name": "CNC 02" }
   → Toast "Machine mise à jour"
   → machine.value.name = "CNC 02"    → l'interface se met à jour
```

---

## Patterns et conventions

### 1. Un composable par domaine

```
useMachines.ts      → CRUD machines
useComposants.ts    → CRUD composants
usePieces.ts        → CRUD pièces
useProducts.ts      → CRUD produits
useSites.ts         → CRUD sites
useConstructeurs.ts → CRUD constructeurs
```

### 2. Props + Events, jamais provide/inject

```vue
<!-- ✅ Correct -->
<ChildComponent :data="myData" @save="handleSave" />

<!-- ❌ Interdit dans ce projet -->
<!-- provide('data', myData)  dans le parent -->
<!-- inject('data')           dans l'enfant -->
```

### 3. État dans les composables, pas dans les composants

```typescript
// ✅ La logique est dans le composable
const { machines, loadMachines } = useMachines()

// ❌ Ne pas faire d'appels API directement dans le template/composant
```

### 4. Pas de Pinia/Vuex

Le projet utilise des refs dans les composables au lieu d'un store global. C'est plus simple et suffisant pour cette application.

### 5. Nommage

| Type | Convention | Exemple |
|------|------------|---------|
| Composable | `use` + PascalCase | `useMachines`, `useProfileSession` |
| Composant | PascalCase | `DataTable`, `MachineInfoCard` |
| Page | kebab-case | `machines/index.vue`, `activity-log.vue` |
| Type/Interface | PascalCase | `Machine`, `DataTableSort` |
| Fonction | camelCase | `loadMachines`, `handleSave` |

### 6. Responsive design

Utiliser les breakpoints Tailwind pour adapter l'affichage :

```html
<!-- Petit sur mobile, normal sur desktop -->
<input class="input input-bordered input-sm md:input-md">
<button class="btn btn-sm md:btn-md btn-primary">

<!-- Caché sur mobile, visible sur desktop -->
<div class="hidden md:block">Visible seulement sur desktop</div>

<!-- Grille responsive -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
```

---

## Les tests

### Tests unitaires (Vitest)

```bash
npm run test          # Lancer tous les tests
npm run test:watch    # Mode watch (relance à chaque modification)
```

Les tests sont dans `tests/` et utilisent Vitest avec happy-dom :

```typescript
import { describe, it, expect } from 'vitest'

describe('extractCollection', () => {
  it('should extract hydra:member', () => {
    const data = { 'hydra:member': [{ id: '1' }] }
    expect(extractCollection(data)).toEqual([{ id: '1' }])
  })
})
```

### Tests E2E (Playwright)

```bash
npm run test:e2e      # Lancer les tests end-to-end
```

Les tests E2E simulent un navigateur réel et testent l'application de bout en bout.

---

## Commandes utiles

| Commande | Description |
|----------|-------------|
| `npm run dev` | Serveur de développement avec rechargement automatique |
| `npm run build` | Build de production |
| `npm run lint:fix` | Corriger les erreurs de style automatiquement |
| `npx nuxi typecheck` | Vérifier les types TypeScript (doit retourner 0 erreur) |
| `npm run test` | Tests unitaires |
| `npm run test:e2e` | Tests E2E |

---

## Résumé des points clés pour un débutant

1. **Nuxt auto-importe tout** : pas besoin d'`import` pour les composants et composables
2. **File-based routing** : chaque fichier dans `pages/` = une URL
3. **Les composables gèrent la logique** : les pages/composants ne font que l'affichage
4. **useApi.ts centralise les appels HTTP** : ne jamais appeler `fetch` directement
5. **Props + Events** pour communiquer entre composants, jamais `provide/inject`
6. **DaisyUI** pour les composants UI, **Tailwind** pour le layout
7. **Les IRIs** (`/api/sites/cl...`) sont utilisées pour les relations API, pas les IDs simples
8. **Le middleware** vérifie automatiquement la session à chaque navigation
9. **TypeScript** est obligatoire : toujours typer les données
10. **Responsive** : toujours penser mobile-first avec les breakpoints `md:`, `lg:`
