# Spec Design — Vue Détail : Cible vs Existant
> Comparaison Image 1 (mock cible) vs Image 2 (implémentation actuelle)
> À destination de Claude Code pour correction du composant `DetailPanel.vue`

---

## 1 — Header : Avatar + Titre lisible

### Cible
- **Avatar** : cercle coloré (couleur primaire du module) avec les initiales de l'objet — ex: cercle violet avec "E" pour un état de vente
- **Titre** : label métier formaté et lisible — ex: `EDV-2026-04-001254`
- **Sous-titre** : `{module_label} · ID: {id}` — ex: `etatsdeventes · ID: 1254`
- **Bouton ✕** : aligné tout à droite, seul, sans chevauchement

### Existant (à corriger)
- Cercle gris avec un index numérique (`2`) sans signification métier
- Titre = slug technique brut (`20251231_2986701144`)
- Icônes crayon ✏️ et ✕ se superposent dans le header

### Fix
```vue
<template>
  <div class="panel-header">
    <div class="header-left">
      <!-- Avatar initiales coloré -->
      <div class="avatar-circle" :style="{ background: moduleColor }">
        {{ initiales(row) }}
      </div>
      <div>
        <div class="header-title">{{ formatLabel(row) }}</div>
        <div class="header-sub">{{ moduleLabel }} · ID: {{ row.id }}</div>
      </div>
    </div>
    <!-- Bouton fermeture seul à droite, sans chevauchement -->
    <v-btn icon size="small" variant="text" @click="closePanel">
      <v-icon>tabler-x</v-icon>
    </v-btn>
  </div>
</template>

<style scoped>
.panel-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 12px 16px;
  border-bottom: 1px solid #e0e0e0;
  position: sticky;
  top: 0;
  background: white;
  z-index: 10;
}
.header-left { display: flex; align-items: center; gap: 12px; }
.avatar-circle {
  width: 40px; height: 40px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-weight: 600; font-size: 14px; color: white;
  flex-shrink: 0;
}
.header-title { font-weight: 600; font-size: 15px; }
.header-sub { font-size: 12px; color: #82868b; }
</style>
```

**Règle pour `formatLabel(row)` :** utiliser en priorité `ModuleMetadata.labelField` (ex: `name`, `reference`, `numero`). Si absent, formatter le champ `name` avec un préfixe module + date si disponible.

---

## 2 — Tags sous le titre : Métier, pas technique

### Cible
Tags colorés affichant les **champs clés métier** de l'objet :
- `Statut: Emis` → badge vert
- `Type: Billet` → badge gris
- `GDS: Amadeus` → badge bleu

### Existant (à corriger)
- Tag `Name: 20251231_2986701144` → redondant avec le titre
- Tag `UUID: 6559b85c-...` → information technique sans valeur UI

### Fix
```typescript
// Dans ModuleMetadata, définir les headerFields (côté Chap)
// Exemple pour etatsdeventes :
headerFields: [
  { key: 'status',  label: 'Statut', type: 'badge' },
  { key: 'type',    label: 'Type',   type: 'badge' },
  { key: 'gds',     label: 'GDS',    type: 'text'  },
]

// Dans DetailPanel.vue
<div class="header-tags">
  <v-chip
    v-for="field in headerFields"
    :key="field.key"
    size="small"
    :color="getBadgeColor(field, row[field.key])"
    variant="tonal"
  >
    {{ field.label }}: {{ row[field.key] }}
  </v-chip>
</div>
```

**Règle :** Ne jamais afficher `name`, `uuid`, `id` dans les header tags.

---

## 3 — Barre d'actions : 3 boutons explicites

### Cible
Barre dédiée sous le header avec 3 boutons distincts :
- `✏ Modifier` → violet, variant `outlined`
- `⧉ Dupliquer` → gris, variant `outlined`
- `🗑 Supprimer` → rouge, variant `outlined`

### Existant (à corriger)
- 1 seul crayon ✏️ intégré dans le header, sans libellé
- Pas de bouton Dupliquer
- Pas de bouton Supprimer

### Fix
```vue
<template>
  <div class="action-bar">
    <v-btn
      v-if="permissions.update"
      size="small" variant="outlined" color="primary"
      prepend-icon="tabler-pencil"
      @click="openEditMode"
    >
      Modifier
    </v-btn>
    <v-btn
      v-if="permissions.create"
      size="small" variant="outlined" color="secondary"
      prepend-icon="tabler-copy"
      @click="duplicateRecord"
    >
      Dupliquer
    </v-btn>
    <v-btn
      v-if="permissions.delete"
      size="small" variant="outlined" color="error"
      prepend-icon="tabler-trash"
      @click="confirmDelete"
    >
      Supprimer
    </v-btn>
  </div>
</template>

<style scoped>
.action-bar {
  display: flex;
  gap: 8px;
  padding: 10px 16px;
  border-bottom: 1px solid #e0e0e0;
  flex-wrap: wrap;
}
</style>
```

---

## 4 — Tabs détail : `grow` + labels courts

### Cible
- `<v-tabs grow>` — tabs occupent toute la largeur à parts égales
- Underline violet sur le tab actif
- Labels courts et lisibles : `Général` | `Infos Vol` | `Facturation` | `Historique`

### Existant (à corriger)
- Tabs alignés à gauche sans `grow` — espace blanc à droite
- Labels trop longs : `Informations générales` | `Informations financières` | `Historique & journal`

### Fix
```vue
<v-tabs v-model="activeDetailTab" grow color="primary" class="detail-tabs">
  <v-tab v-for="bloc in detailBlocks" :key="bloc.id" :value="bloc.id">
    {{ bloc.label }}   <!-- Labels courts depuis ModuleMetadata, ≤ 15 chars -->
  </v-tab>
</v-tabs>
```

**Règle pour les labels de blocs :** définir des labels courts dans Chap (ex: `Général` pas `Informations générales`).

---

## 5 — Contenu du tab : 2 colonnes + mise en valeur des montants

### Cible

**Valeurs importantes** (montants) affichées en grand en haut :
```
277 500 XOF                    12 250 XOF
```
(font-size 20px, font-weight 600, sans label au-dessus — ou label très discret)

**Grille 2 colonnes** pour les autres champs :
```
ITINÉRAIRE                     [colonne vide ou 2ème champ]
DSS → CDG → DSS (Aller-Retour)

CLASSE                         GDS
Économique (Y)                 Amadeus

OFFICE ID                      MODE PAIEMENT
DKRXX0100                      Cash
```
- Labels : gris, uppercase, 11px
- Valeurs : 14px, normal weight
- Séparateur visuel discret entre groupes

### Existant (à corriger)
- 1 seul champ par ligne (1 colonne)
- Pas de mise en valeur des montants
- Champs techniques visibles : `air_raw_content`, `air_json`, `is_deleted`, `modified_by`, `estair_json`

### Fix
```vue
<template>
  <div class="tab-content" style="max-height: 45vh; overflow-y: auto; padding: 16px;">

    <!-- Valeurs KPI en grand (champs de type 'currency' ou 'kpi') -->
    <div v-if="kpiFields.length" class="kpi-row">
      <div v-for="f in kpiFields" :key="f.key" class="kpi-item">
        <span class="kpi-value">{{ formatCurrency(row[f.key]) }}</span>
        <span class="kpi-label">{{ f.label }}</span>
      </div>
    </div>

    <!-- Grille 2 colonnes pour les autres champs -->
    <div class="fields-grid">
      <div
        v-for="field in visibleFields"
        :key="field.key"
        :class="field.colSpan === 2 ? 'col-span-2' : 'col-span-1'"
      >
        <div class="field-label">{{ field.label.toUpperCase() }}</div>
        <div class="field-value">
          <FieldRenderer :field="field" :value="row[field.key]" />
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
// Filtrer les champs techniques à ne pas afficher
const HIDDEN_FIELDS = [
  'uuid', 'air_raw_content', 'air_json', 'estair_json',
  'is_deleted', 'modified_by', 'created_by', 'updated_at',
  'created_at',
]

const visibleFields = computed(() =>
  bloc.fields.filter(f =>
    !HIDDEN_FIELDS.includes(f.key) &&
    !f.hidden &&
    f.type !== 'kpi' &&
    f.type !== 'currency_highlight'
  )
)

const kpiFields = computed(() =>
  bloc.fields.filter(f =>
    f.type === 'currency_highlight' || f.highlight === true
  )
)
</script>

<style scoped>
.kpi-row {
  display: flex; gap: 48px;
  padding: 16px 0 20px;
  border-bottom: 1px solid #f0f0f0;
  margin-bottom: 16px;
}
.kpi-value { font-size: 20px; font-weight: 600; display: block; }
.kpi-label { font-size: 11px; color: #82868b; text-transform: uppercase; }

.fields-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px 24px;
}
.col-span-2 { grid-column: 1 / -1; }
.field-label { font-size: 11px; color: #82868b; text-transform: uppercase; margin-bottom: 2px; }
.field-value { font-size: 14px; }
</style>
```

---

## 6 — Tabs contextuels : `grow` + labels métier

### Cible
- `<v-tabs grow>` — même largeur pour chaque tab
- Badge count coloré : violet si > 0, gris si = 0
- Labels métier lisibles : `Segments 3` | `EMDs 0` | `Mouvements 2` | `Documents 5`

### Existant (à corriger)
- Tabs sans `grow` → débordement avec flèche de scroll →
- Labels = noms de tables techniques : `Bmps`, `Bspdetails`, `Financial requests`

### Fix
```vue
<!-- Tabs contextuels avec grow -->
<div class="context-section" style="border-top: 1px solid #e0e0e0; margin-top: 16px;">
  <v-tabs v-model="activeContextTab" grow color="primary">
    <v-tab
      v-for="ctx in contextObjects"
      :key="ctx.moduleId"
      :value="ctx.moduleId"
    >
      {{ ctx.label }}
      <v-chip
        size="x-small"
        :color="ctx.count > 0 ? 'primary' : 'default'"
        variant="tonal"
        class="ms-1"
      >
        {{ ctx.count }}
      </v-chip>
    </v-tab>
  </v-tabs>
</div>
```

**Règle :** `ctx.label` doit venir de `ModuleMetadata.label` (ex: `Segments`, `EMDs`, `Mouvements`) — jamais le nom de table brut (`bmps`, `bspdetails`).

---

## 7 — Toolbar liste contextuelle : complet

### Cible
```
[+ Nouveau]          [🔍 Rechercher...]    [5▼]  [↓ Export]
```

### Existant (à corriger)
Seulement search + page size. Pas de `+ Nouveau`, pas d'export.

### Fix
```vue
<div class="context-toolbar">
  <v-btn
    v-if="ctx.permissions?.create"
    size="small" color="primary" variant="flat"
    prepend-icon="tabler-plus"
    @click="createContextItem(ctx)"
  >
    Nouveau
  </v-btn>

  <v-text-field
    v-model="contextSearch"
    density="compact" variant="outlined" hide-details
    placeholder="Rechercher..."
    prepend-inner-icon="tabler-search"
    style="max-width: 200px;"
  />

  <v-spacer />

  <v-select
    v-model="contextPageSize"
    :items="[5, 10, 25]"
    density="compact" variant="outlined" hide-details
    style="width: 70px;"
  />

  <v-btn icon size="small" variant="text" @click="exportContext">
    <v-icon>tabler-download</v-icon>
  </v-btn>
</div>
```

---

## 8 — Tableau contextuel : colonnes lisibles + badges + actions

### Cible
- Colonnes avec **labels formatés** en majuscules : `#`, `DÉPART`, `ARRIVÉE`, `DATE VOL`, `COMPAGNIE`, `STATUT`, `ACTIONS`
- **Badges statut colorés** : `En attente` orange · `Confirmé` vert · `Annulé` rouge · `Provisoire` bleu
- Colonne `ACTIONS` : icônes 👁 ✏️ 🗑 sur chaque ligne

### Existant (à corriger)
- Colonnes techniques brutes : `OFFICEID BK`, `OFFICEID TK`, `SIGNINBOOKING`, `SIGINTICKETING`
- Pas de badges statut
- Pas de colonne ACTIONS
- Scroll horizontal sans actions visibles

### Fix
```vue
<!-- Utiliser les ColumnDef du ModuleMetadata du module contextuel -->
<v-data-table
  :headers="contextColumns"
  :items="contextItems"
  :items-per-page="contextPageSize"
  hide-default-footer
>
  <!-- Badge statut -->
  <template #[`item.status`]="{ value }">
    <v-chip :color="getStatusColor(value)" size="small" variant="tonal">
      {{ getStatusLabel(value) }}
    </v-chip>
  </template>

  <!-- Actions -->
  <template #[`item.actions`]="{ item }">
    <div style="display: flex; gap: 4px;">
      <v-btn icon size="x-small" variant="text" @click.stop="viewItem(item)">
        <v-icon size="16">tabler-eye</v-icon>
      </v-btn>
      <v-btn icon size="x-small" variant="text" @click.stop="editItem(item)"
             v-if="ctx.permissions?.update">
        <v-icon size="16">tabler-pencil</v-icon>
      </v-btn>
      <v-btn icon size="x-small" variant="text" color="error" @click.stop="deleteItem(item)"
             v-if="ctx.permissions?.delete">
        <v-icon size="16">tabler-trash</v-icon>
      </v-btn>
    </div>
  </template>
</v-data-table>
```

---

## 9 — Pagination contextuelle

### Cible
```
Affichage de 1 à 5 sur 8 entrées          «  ‹  1  2  ›  »
```

### Existant
Format correct mais en anglais (`Affichage de 1 à 1 sur 1 entrées` — déjà partiellement traduit).
Les contrôles `«‹ 1 2 ›»` sont présents. ✅ Peu de correction nécessaire ici.

### Fix mineur
```vue
<div class="context-footer">
  <span class="pagination-info">
    Affichage de {{ from }} à {{ to }} sur {{ total }} entrées
  </span>
  <v-pagination
    v-model="contextPage"
    :length="totalPages"
    :total-visible="4"
    density="compact"
    size="small"
  />
</div>
```

---

## Récapitulatif — Ordre d'implémentation recommandé

| # | Point | Fichier | Effort |
|---|-------|---------|--------|
| 1 | Header avatar + titre lisible | `DetailPanel.vue` | Faible |
| 2 | Supprimer tags Name/UUID, ajouter tags métier | `DetailPanel.vue` | Faible |
| 3 | Barre d'actions 3 boutons | `DetailPanel.vue` | Faible |
| 4 | `v-tabs grow` sur blocs détail | `DetailPanel.vue` | Faible |
| 5 | Grille 2 colonnes + montants en grand | `DetailPanel.vue` | Moyen |
| 6 | `v-tabs grow` sur contextes + labels métier | `ContextSection.vue` | Faible |
| 7 | Toolbar contextuel complet | `ContextListView.vue` | Faible |
| 8 | Colonnes lisibles + badges + actions | `ContextListView.vue` | Moyen |
| 9 | Pagination FR (déjà quasi OK) | `ContextListView.vue` | Faible |
