import { Controls, Canvas, Meta, Source } from '@storybook/addon-docs/blocks';
import * as SyServerTable from './SyServerTable.stories';
import * as RowEditing from './RowEditing.stories';
import * as BulkActions from './BulkActions.stories';
import '@/stories/styles/shared.css';

<Meta of={SyServerTable} />

<div className="header">
  <h1>SyServerTable</h1>
  <p>Le composant `SyServerTable` est utilisé pour afficher des données tabulaires chargées depuis un serveur. Il s'appuie sur le composant `VDataTableServer` de Vuetify et offre des fonctionnalités supplémentaires comme le stockage local des options de tableau et des améliorations d'accessibilité.</p>
</div>

{/* func-version-start */}
<p className="func-version-badge">Dernière mise à jour fonctionnelle : V1.1.4 - 25/08/2026</p>
{/* func-version-end */}
{/* a11y-version-start */}
<p className="a11y-version-badge">Dernière mise à jour accessibilité : V1.1.3 - 22/07/2026</p>
{/* a11y-version-end */}


<Canvas story={{height: '550px'}} of={SyServerTable.Default} />

## API

<Controls story={{height: '550px'}} of={SyServerTable.Default} />

## Fonctionnalités

### Chargement côté serveur

Le composant `SyServerTable` est spécialement conçu pour gérer les données chargées depuis un serveur. Il permet de:
- Paginer les données côté serveur
- Trier les données côté serveur
- Afficher un état de chargement pendant les requêtes

### Stockage local des options

Le composant enregistre automatiquement les options du tableau (tri, pagination, etc.) dans le localStorage du navigateur. Ces options sont restaurées lorsque l'utilisateur revient sur la page.

Pour gérer individuellement le stockage des options pour différents tableaux, utilisez la prop `suffix`.

### Tri côté serveur

Le composant permet de trier les données par colonne en déléguant le tri au serveur.

Le multi-tri est également supporté. Vous pouvez activer cette fonctionnalité en utilisant la prop `multi-sort`.

### Filtres des colonnes

Le composant permet d'appliquer des filtres sur les colonnes. Vous pouvez définir des filtres personnalisés pour chaque colonne en utilisant la prop `show-filters`. Voir d'autres exemples de filtres dans les stories.

### Resize des colonnes

Le composant peut permettre de redimensionner les colonnes en utilisant la prop `resizable-columns`. Vous pouvez activer ou désactiver cette fonctionnalité selon vos besoins.

### Réorganisation des colonnes

Le composant permet de cacher ou réorganiser l'ordre des colonnes en utilisant la prop `enable-column-controls`. Vous pouvez activer ou désactiver cette fonctionnalité selon vos besoins.

### Selection des lignes

Le composant permet de sélectionner des lignes individuellement ou en masse. Vous pouvez activer la sélection en utilisant la prop `show-select`.

Par défaut, la clé utilisée pour identifier chaque ligne lors de la sélection est `id`. Si vos éléments ne possèdent pas de propriété `id`, la valeur sélectionnée correspondra à l'objet complet de la ligne.

Vous pouvez personnaliser cette clé avec la prop `selection-key` pour indiquer quel champ utiliser (ex: `userId`).

Exemple d'utilisation :
<Source dark code={`
<SyServerTable
  v-model="selected"
  :items="users"
  :headers="headers"
  show-select
  selection-key="userId"
/>
`}/>

### Click des lignes

La prop `clickableRow` active le clic sur toute la ligne du tableau. Quand cette prop vaut `true`, un clic sur une ligne émet l'événement `row-click` avec l'item correspondant.

Les lignes deviennent également focusables au clavier afin de pouvoir être activées avec <kbd>Entrée</kbd> ou <kbd>Espace</kbd>.

Les éléments interactifs déjà présents dans la ligne, comme les cases à cocher, liens ou boutons, conservent leur comportement propre et ne déclenchent pas `row-click`.

L'événement `row-click` est  lorsqu'une ligne est activée alors que `clickableRow` est à `true`.

- Payload : l'objet de la ligne cliquée
- Cas pris en charge : clic souris sur la ligne, activation clavier via <kbd>Entrée</kbd> ou <kbd>Espace</kbd>
- Cas exclus : interaction avec un élément interactif imbriqué dans la ligne

Exemple :
<Source dark code={`
<SyServerTable
  :items="items"
  :headers="headers"
  :server-items-length="totalItems"
  clickable-row
  @row-click="handleRowClick"
/>
`}/>

### Édition des lignes

La prop `editable` active l'édition inline. Chaque colonne dont le header porte `editable: true` devient un champ en mode édition (par défaut un `SyTextField`). Le composant **ne mute jamais `items`** : il émet `save`, `cancel` et `delete`, à l'application de persister les changements.

Les boutons d'action se placent dans le slot `#item.actions`, qui expose les helpers `{ item, isEditing, edit, save, cancel, remove }`. Prévoyez une colonne `actions` dans les headers.

<Source dark code={`
<SyServerTable
  editable
  selection-key="id"
  :headers="headers"
  :items="items"
  :server-items-length="totalCount"
  @save="onSave"
  @delete="onDelete"
>
  <template #item.actions="{ isEditing, edit, save, cancel, remove }">
    <template v-if="!isEditing">
      <SyIconButton :icon="mdiPencil" label="Éditer" @click-icon-button="edit" />
      <SyIconButton :icon="mdiDelete" label="Supprimer" @click-icon-button="remove" />
    </template>
    <template v-else>
      <SyIconButton :icon="mdiCheck" label="Valider" @click-icon-button="save" />
      <SyIconButton :icon="mdiClose" label="Annuler" @click-icon-button="cancel" />
    </template>
  </template>
</SyServerTable>
`}/>

L'éditeur d'une cellule peut être personnalisé via le slot `#edit.<columnKey>`. Il reçoit `value` (le brouillon), `update(value)` (pour écrire dans le brouillon) **et les mêmes `cellProps` que le slot de lecture** `#item.<columnKey>` (`item`, `column`, `index`…), pour une API cohérente entre lecture et édition. La valeur passée à `update()` conserve le type que vous lui donnez (pas de conversion).

> ⚠️ L'éditeur par défaut (`SyTextField`) ne gère que les **valeurs primitives** (chaîne, nombre, booléen). Pour une colonne dont la valeur est un **objet, un tableau ou une `Date`**, fournissez un slot `#edit.<columnKey>` avec un éditeur adapté (`SySelect`, `DatePicker`, `<input type="date">`…) — sinon aucun éditeur n'est rendu et un avertissement est émis en développement. Le rendu **hors édition** d'une telle valeur passe par `#item.<columnKey>`. Dans tous les cas, **ne mutez jamais la valeur en place** (ex. `item.role.code = …`) : appelez `update()` avec une **nouvelle** valeur/copie, afin de préserver l'annulation (`cancel`) et l'immutabilité de `items`.

> 💡 **Type préservé** : l'éditeur texte par défaut émet des chaînes ; le composant reconvertit automatiquement la valeur vers le type d'origine de la colonne (`number` → nombre, `boolean` → booléen, champ vidé d'une colonne numérique → `null`). `save(updated)` renvoie donc des types cohérents avec `items`.

**Évènements** : `edit(item)` · `save(updated, original)` · `cancel(item)` · `delete(item)`.

> ⚠️ **La ligne quitte le mode édition dès le clic sur `save`**, avant l'appel à votre API. `save` reçoit `updated` (la ligne modifiée) et `original` (la ligne d'avant). En mode serveur, l'enregistrement échoue parfois : gérez vous-même le retour arrière en réaffichant `original`.
>
> Schéma recommandé : affichez `updated` tout de suite, et **si l'API renvoie une erreur, restaurez `original`** (ou rechargez la page).

<Canvas story={{height: '350px'}} of={RowEditing.Default} />

Édition d'une valeur non primitive (`Date`) via `#edit.<columnKey>` :

<Canvas story={{height: '350px'}} of={RowEditing.NonPrimitiveEditor} />

### Sélection multiple et actions groupées

En s'appuyant sur la sélection (`show-select`), une **barre d'actions groupées** apparaît dès qu'au moins une ligne est cochée **et** qu'un slot `#bulk-actions` fournit les actions.

Le tableau **n'embarque pas** de formulaire d'édition groupée : il expose uniquement la sélection via le slot `#bulk-actions` (`{ selected, count, clearSelection }`). Le **projet** rend ses propres actions (éditer, supprimer, exporter…) et pilote leur UX avec une `DialogBox`, un drawer, une page dédiée, etc. Ce choix garde le composant léger et laisse au projet la maîtrise de l'accessibilité et des types de champs.

<Source dark code={`
<SyServerTable show-select selection-key="id" v-model="selected" :headers="headers" :items="items" :server-items-length="totalCount">
  <template #bulk-actions="{ selected, count, clearSelection }">
    <VBtn @click="openEdit(selected, clearSelection)">Modifier {{ count }}</VBtn>
    <VBtn @click="openDelete(selected, clearSelection)">Supprimer {{ count }}</VBtn>
  </template>
</SyServerTable>
`}/>

Voir la story ci-dessous : édition « appliquer une valeur à la sélection » et suppression, pilotées par des `DialogBox` du Design System (un variant séquentiel avancé est aussi fourni).

<Canvas story={{height: '350px'}} of={BulkActions.Default} />

### Slot item
Le composant permet de personnaliser l'affichage des contenus en utilisant le slot `item`. Vous pouvez définir la structure de chaque contenu en fonction de vos besoins.

### Slot headers
Le composant permet de personnaliser l'affichage des en-têtes de colonnes en utilisant le slot `headers`. Vous pouvez définir la structure de chaque en-tête en fonction de vos besoins.

### Items par page options
Le composant permet de personnaliser le nombre possible d'éléments par page en utilisant la prop `items-per-page-options`. Vous pouvez spécifier un tableau d'options pour permettre de choisir le nombre d'éléments affichés par page.

<div className="header">
  <h1>Accessibilité</h1>
</div>

Le composant améliore l'accessibilité en ajoutant automatiquement:
- Une légende (caption) pour le tableau
- Des attributs ARIA appropriés
- Des attributs scope pour les en-têtes de colonnes

## Exemples d'utilisation

### Tableau avec chargement côté serveur

<Source dark code={`
<template>
  <SyServerTable
    v-model:options="options"
    :items="users"
    :headers="headers"
    :server-items-length="totalUsers"
    :loading="state === StateEnum.PENDING"
    suffix="api-example"
    @update:options="fetchData"
  />
</template>

<script setup lang="ts">
  import { ref, watch } from 'vue'
  import { SyServerTable } from '@cnamts/synapse'
  import { StateEnum } from '@cnamts/synapse/src/components/Tables/common/constants/StateEnum'
  import type { DataOptions } from '@cnamts/synapse/src/components/Tables/common/types'
  
  interface User {
    [key: string]: string
    firstname: string
    lastname: string
    email: string
  }

  interface DataObj {
    items: User[]
    total: number
  }

  const totalUsers = ref(0)
import '../../../stories/styles/shared.css';
  const users = ref<User[]>([])
  const state = ref(StateEnum.IDLE)

  const options = ref({
    itemsPerPage: 5,
    sortBy: [{ key: 'lastname', order: 'asc' }],
    page: 1,
  })

  const headers = [
    { title: 'Nom', key: 'lastname' },
    { title: 'Prénom', key: 'firstname' },
    { title: 'Email', key: 'email' },
  ]

  const fetchData = async (): Promise<void> => {
    const { items, total } = await getDataFromApi(options.value)
    users.value = items
    totalUsers.value = total
  }

  // Simulation d'une API
  const getDataFromApi = async ({ sortBy, page, itemsPerPage }: DataOptions): Promise<DataObj> => {
    state.value = StateEnum.PENDING
    
    // Simuler un délai réseau
    await new Promise(resolve => setTimeout(resolve, 1000))

    return new Promise((resolve) => {
      let items: User[] = [
        { firstname: 'Virginie', lastname: 'Beauchesne', email: 'virginie.beauchesne@example.com' },
        { firstname: 'Simone', lastname: 'Bellefeuille', email: 'simone.bellefeuille@example.com' },
        // ... autres utilisateurs
      ]
      
      const total = items.length

      // Appliquer le tri côté serveur
      if (sortBy && sortBy.length > 0) {
        items = items.sort((a, b) => {
          const key = sortBy[0].key
          const order = sortBy[0].order === 'asc' ? 1 : -1
          return a[key] > b[key] ? order : -order
        })
      }

      // Appliquer la pagination côté serveur
      if (itemsPerPage > 0) {
        items = items.slice((page - 1) * itemsPerPage, page * itemsPerPage)
      }

      resolve({ items, total })
      state.value = StateEnum.RESOLVED
    })
  }
</script>
`} />

### Plusieurs tableaux serveur sur une même page

Pour utiliser plusieurs tableaux serveur sur une même page avec des options indépendantes, utilisez la prop `suffix` pour chaque tableau.

## Bonnes pratiques

- Utilisez la prop `serverItemsLength` pour indiquer le nombre total d'éléments disponibles côté serveur
- Implémentez un état de chargement pendant les requêtes avec la prop `loading`
- Utilisez la prop `suffix` lorsque vous avez plusieurs tableaux sur une même page
- Optimisez les requêtes serveur en utilisant les options de pagination et de tri
- Gérez correctement les erreurs de chargement des données
