| Numéro du ticket | Titre du ticket | |------------------|-----------------| | | | ## Description de la PR ## Modification du .env ## Check list - [ ] Pas de régression - [x] TU/TI/TF rédigée - [ ] TU/TI/TF OK - [x] CHANGELOG modifié Reviewed-on: #27 Co-authored-by: tristan <tristan@yuno.malio.fr> Co-committed-by: tristan <tristan@yuno.malio.fr>
196 lines
6.4 KiB
Vue
196 lines
6.4 KiB
Vue
<template>
|
|
<Story title="Data/DataTable">
|
|
<Variant title="Avec filtres et pagination">
|
|
<div class="p-4">
|
|
<MalioDataTable
|
|
:columns="columns"
|
|
:items="paginatedItems"
|
|
:total-items="filteredItems.length"
|
|
v-model:page="page"
|
|
v-model:per-page="perPage"
|
|
@row-click="onRowClick"
|
|
>
|
|
<template #header-nom>
|
|
<input
|
|
v-model="filtreNom"
|
|
type="text"
|
|
placeholder="Nom"
|
|
class="w-full border-0 border-b border-black bg-transparent px-0 py-1 text-sm outline-none"
|
|
>
|
|
</template>
|
|
<template #header-ville>
|
|
<select
|
|
:value="filtreVille ?? ''"
|
|
class="w-full appearance-none border-0 border-b border-black bg-transparent px-0 py-1 text-sm outline-none"
|
|
@change="filtreVille = ($event.target as HTMLSelectElement).value || null"
|
|
>
|
|
<option value="">Ville</option>
|
|
<option value="Paris">Paris</option>
|
|
<option value="Lyon">Lyon</option>
|
|
<option value="Marseille">Marseille</option>
|
|
</select>
|
|
</template>
|
|
<template #cell-montant="{ item }">
|
|
<strong>{{ item.montant }} €</strong>
|
|
</template>
|
|
</MalioDataTable>
|
|
</div>
|
|
</Variant>
|
|
|
|
<Variant title="Sans filtres">
|
|
<div class="p-4">
|
|
<MalioDataTable
|
|
:columns="columnsSimple"
|
|
:items="simpleItems"
|
|
:total-items="simpleItems.length"
|
|
v-model:page="pageSimple"
|
|
v-model:per-page="perPageSimple"
|
|
/>
|
|
</div>
|
|
</Variant>
|
|
|
|
<Variant title="État vide">
|
|
<div class="p-4">
|
|
<MalioDataTable
|
|
:columns="columns"
|
|
:items="[]"
|
|
:total-items="0"
|
|
/>
|
|
</div>
|
|
</Variant>
|
|
|
|
<Variant title="Lignes non cliquables">
|
|
<div class="p-4">
|
|
<MalioDataTable
|
|
:columns="columnsSimple"
|
|
:items="simpleItems.slice(0, 3)"
|
|
:total-items="3"
|
|
:row-clickable="false"
|
|
/>
|
|
</div>
|
|
</Variant>
|
|
|
|
<Variant title="Sans filtre ni pagination">
|
|
<div class="p-4">
|
|
<MalioDataTable
|
|
:columns="columnsSimple"
|
|
:items="simpleItems.slice(0, 5)"
|
|
:total-items="0"
|
|
:row-clickable="false"
|
|
/>
|
|
</div>
|
|
</Variant>
|
|
</Story>
|
|
</template>
|
|
|
|
<docs lang="md">
|
|
# MalioDataTable
|
|
|
|
Tableau de données presentational avec pagination, filtres par slots et lignes cliquables.
|
|
|
|
## Props détaillées
|
|
|
|
| Prop | Type | Défaut | Description |
|
|
|------|------|--------|-------------|
|
|
| `id` | `string` | auto-généré | Identifiant HTML |
|
|
| `columns` | `{ key: string, label: string }[]` | **requis** | Définition des colonnes |
|
|
| `items` | `Record<string, any>[]` | **requis** | Données à afficher |
|
|
| `totalItems` | `number` | **requis** | Total pour la pagination |
|
|
| `page` | `number` | `1` | Page courante (v-model) |
|
|
| `perPage` | `number` | `10` | Lignes par page (v-model) |
|
|
| `perPageOptions` | `number[]` | `[10, 25, 50]` | Options du sélecteur de lignes |
|
|
| `rowClickable` | `boolean` | `true` | Lignes cliquables |
|
|
| `tableClass` | `string` | `''` | Classes CSS sur le wrapper (twMerge) |
|
|
| `emptyMessage` | `string` | `'Aucune donnée'` | Message si items vide |
|
|
|
|
## Slots
|
|
|
|
| Slot | Scope | Description |
|
|
|------|-------|-------------|
|
|
| `#header-{key}` | `{ column }` | Filtre dans le `<th>` (placeholder = label). Fallback : texte du label |
|
|
| `#cell-{key}` | `{ item, column }` | Contenu du `<td>`. Fallback : `item[key]` |
|
|
| `#empty` | — | Contenu état vide. Fallback : `emptyMessage` |
|
|
|
|
## Events
|
|
|
|
| Event | Payload | Description |
|
|
|-------|---------|-------------|
|
|
| `update:page` | `number` | Changement de page |
|
|
| `update:per-page` | `number` | Changement du nb de lignes (reset page à 1) |
|
|
| `row-click` | `Record<string, any>` | Clic sur une ligne |
|
|
|
|
## Pagination
|
|
|
|
- ≤ 5 pages : toutes affichées
|
|
- \> 5 pages : page 1 … [voisin] **[courante]** [voisin] … dernière
|
|
- Boutons Prev/Next toujours visibles, désactivés aux extrêmes
|
|
|
|
## Accessibilité
|
|
|
|
- `<th scope="col">` sur chaque en-tête
|
|
- `<nav aria-label="Pagination">` autour de la pagination
|
|
- Page courante avec `aria-current="page"`
|
|
- Lignes cliquables : `tabindex="0"` + Enter/Space
|
|
</docs>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, computed } from 'vue'
|
|
import MalioDataTable from '../../components/malio/datatable/DataTable.vue'
|
|
|
|
defineOptions({ name: 'DataTableStory' })
|
|
|
|
const columns = [
|
|
{ key: 'nom', label: 'Nom' },
|
|
{ key: 'prenom', label: 'Prénom' },
|
|
{ key: 'ville', label: 'Ville' },
|
|
{ key: 'montant', label: 'Montant' },
|
|
]
|
|
|
|
const columnsSimple = [
|
|
{ key: 'nom', label: 'Nom' },
|
|
{ key: 'ville', label: 'Ville' },
|
|
]
|
|
|
|
const allItems = [
|
|
{ id: 1, nom: 'Dupont', prenom: 'Jean', ville: 'Paris', montant: 1200 },
|
|
{ id: 2, nom: 'Martin', prenom: 'Marie', ville: 'Lyon', montant: 850 },
|
|
{ id: 3, nom: 'Bernard', prenom: 'Pierre', ville: 'Marseille', montant: 2100 },
|
|
{ id: 4, nom: 'Petit', prenom: 'Sophie', ville: 'Paris', montant: 950 },
|
|
{ id: 5, nom: 'Robert', prenom: 'Paul', ville: 'Lyon', montant: 1800 },
|
|
{ id: 6, nom: 'Richard', prenom: 'Claire', ville: 'Marseille', montant: 3200 },
|
|
{ id: 7, nom: 'Durand', prenom: 'Luc', ville: 'Paris', montant: 750 },
|
|
{ id: 8, nom: 'Moreau', prenom: 'Anne', ville: 'Lyon', montant: 1100 },
|
|
{ id: 9, nom: 'Simon', prenom: 'Marc', ville: 'Marseille', montant: 2400 },
|
|
{ id: 10, nom: 'Laurent', prenom: 'Julie', ville: 'Paris', montant: 1650 },
|
|
{ id: 11, nom: 'Lefebvre', prenom: 'Thomas', ville: 'Lyon', montant: 900 },
|
|
{ id: 12, nom: 'Leroy', prenom: 'Emma', ville: 'Marseille', montant: 1400 },
|
|
]
|
|
|
|
const simpleItems = allItems.map(i => ({ nom: i.nom, ville: i.ville }))
|
|
|
|
const page = ref(1)
|
|
const perPage = ref(5)
|
|
const filtreNom = ref('')
|
|
const filtreVille = ref<string | number | null>(null)
|
|
|
|
const pageSimple = ref(1)
|
|
const perPageSimple = ref(10)
|
|
|
|
const filteredItems = computed(() => {
|
|
return allItems.filter((item) => {
|
|
if (filtreNom.value && !item.nom.toLowerCase().includes(filtreNom.value.toLowerCase())) return false
|
|
if (filtreVille.value && item.ville !== filtreVille.value) return false
|
|
return true
|
|
})
|
|
})
|
|
|
|
const paginatedItems = computed(() => {
|
|
const start = (page.value - 1) * perPage.value
|
|
return filteredItems.value.slice(start, start + perPage.value)
|
|
})
|
|
|
|
function onRowClick(item: Record<string, unknown>) {
|
|
alert(`Clic sur ${item.nom} ${item.prenom}`)
|
|
}
|
|
</script>
|