import type { Meta, StoryObj } from '@storybook/vue3-vite' import { ref } from 'vue' import { mdiCheck, mdiClose, mdiDelete, mdiPencil } from '@mdi/js' import type { VDataTable } from 'vuetify/components' import SyTable from './SyTable.vue' import SyIconButton from '@/components/Customs/SyIconButton/SyIconButton.vue' import DatePicker from '@/components/DatePicker/CalendarMode/DatePicker.vue' import { commonTableArgTypes } from '../common/storyArgTypes' const meta = { title: 'Composants/Tableaux/SyTable/Édition/Ligne par ligne', component: SyTable, decorators: [ () => ({ template: '
', }), ], parameters: { layout: 'fullscreen', controls: { hideNoControlsWarning: true }, }, argTypes: { ...commonTableArgTypes, 'item.actions': { description: 'Slot d\'actions par ligne. Reçoit les helpers d\'édition `{ item, isEditing, edit, save, cancel, remove }`.', control: undefined, table: { category: 'slots', type: { summary: 'slot', detail: '{ item, isEditing: boolean, edit: () => void, save: () => void, cancel: () => void, remove: () => void }' }, }, }, 'edit.': { description: 'Personnalise l\'éditeur d\'une cellule en édition inline. Remplacer `` par la clé de la colonne. Par défaut un `SyTextField`.', control: undefined, table: { category: 'slots', type: { summary: 'slot', detail: '{ item, value: unknown, update: (value: unknown) => void }' }, }, }, 'onEdit': { action: 'edit', description: 'Émis à l\'entrée en édition inline d\'une ligne.', table: { category: 'events', type: { summary: '(item: Record) => void' }, }, }, 'onSave': { action: 'save', description: 'Émis à la validation de l\'édition inline. Reçoit la ligne mise à jour et l\'originale.', table: { category: 'events', type: { summary: '(updated: Record, original: Record | null) => void' }, }, }, 'onCancel': { action: 'cancel', description: 'Émis à l\'annulation de l\'édition inline.', table: { category: 'events', type: { summary: '(item: Record | null) => void' }, }, }, 'onDelete': { action: 'delete', description: 'Émis au clic sur l\'action de suppression d\'une ligne.', table: { category: 'events', type: { summary: '(item: Record) => void' }, }, }, }, } satisfies Meta export default meta type Story = StoryObj const headers = [ { title: 'Nom', key: 'lastname', editable: true }, { title: 'Prénom', key: 'firstname', editable: true }, { title: 'Email', key: 'email', editable: true }, { title: 'Actions', key: 'actions', sortable: false, align: 'end' as const }, ] const baseItems = [ { id: 1, firstname: 'Virginie', lastname: 'Beauchesne', email: 'virginie.beauchesne@example.com' }, { id: 2, firstname: 'Étienne', lastname: 'Salois', email: 'etienne.salois@example.com' }, { id: 3, firstname: 'Camille', lastname: 'Tremblay', email: 'camille.tremblay@example.com' }, ] /** * Édition inline + suppression ligne à ligne. * * - `editable` active le moteur ; chaque header `editable: true` devient un champ en mode édition. * - Le slot `#item.actions` reçoit les helpers `{ item, isEditing, edit, save, cancel, remove }`. * - Le composant n'altère jamais `items` : il émet `@save(updated, original)`, `@cancel`, `@delete`. */ export const Default: Story = { args: { suffix: 'row-editing', editable: true, selectionKey: 'id', hideDefaultFooter: true, }, render: args => ({ components: { SyTable, SyIconButton }, setup() { const items = ref([...baseItems]) function onSave(updated: Record) { const index = items.value.findIndex(i => i.id === updated.id) if (index !== -1) { items.value[index] = { ...(items.value[index]), ...updated } as typeof baseItems[number] } } function onDelete(item: Record) { items.value = items.value.filter(i => i.id !== item.id) } return { args, headers, items, onSave, onDelete, mdiPencil, mdiDelete, mdiCheck, mdiClose } }, template: ` `, }), parameters: { sourceCode: [ { name: 'Template', code: ` `, }, { name: 'Script', code: ` `, }, ], }, } /** * Éditeur de cellule personnalisé via le slot `#edit.`. * Ici la colonne « Nom » utilise un `` HTML personnalisé ; les autres gardent l'éditeur par défaut. */ export const CustomEditor: Story = { args: { suffix: 'row-editing-custom', editable: true, selectionKey: 'id', hideDefaultFooter: true, }, render: args => ({ components: { SyTable, SyIconButton }, setup() { const items = ref([...baseItems]) function onSave(updated: Record) { const index = items.value.findIndex(i => i.id === updated.id) if (index !== -1) { items.value[index] = { ...(items.value[index]), ...updated } as typeof baseItems[number] } } return { args, headers, items, onSave, mdiPencil, mdiDelete, mdiCheck, mdiClose } }, template: ` `, }), parameters: { sourceCode: [ { name: 'Template', code: ` `, }, { name: 'Script', code: ` `, }, ], }, } /** * Éditer une colonne dont la valeur est **non primitive** (ici une `Date`), via * le **DatePicker du Design System**. L'éditeur texte par défaut ne gère que les * primitives ; pour un objet / `Date` / tableau, il faut fournir un slot * `#edit.` avec un éditeur adapté (sinon aucun éditeur n'est rendu et un * avertissement est émis en dev). Le rendu **hors édition** passe par `#item.`. * * Le `DatePicker` travaille avec une **chaîne formatée** (`format`) : on convertit * donc `Date` ↔ chaîne dans le slot pour conserver une `Date` en donnée. */ export const NonPrimitiveEditor: Story = { args: { suffix: 'row-editing-nonprimitive', editable: true, selectionKey: 'id', hideDefaultFooter: true, }, render: args => ({ components: { SyTable, SyIconButton, DatePicker }, setup() { const rows = [ { id: 1, firstname: 'Virginie', birthdate: new Date('1985-04-12') }, { id: 2, firstname: 'Étienne', birthdate: new Date('1990-11-30') }, ] const items = ref([...rows]) const editHeaders = [ { title: 'Prénom', key: 'firstname', editable: true }, { title: 'Naissance', key: 'birthdate', editable: true }, { title: 'Actions', key: 'actions', sortable: false, align: 'end' as const }, ] function onSave(updated: Record) { const index = items.value.findIndex(i => i.id === updated.id) if (index !== -1) { items.value[index] = { ...items.value[index], ...updated } as typeof rows[number] } } // Pont Date ↔ chaîne « DD/MM/YYYY » attendue par le DatePicker du DS const dateToStr = (d: unknown): string => d instanceof Date ? [String(d.getDate()).padStart(2, '0'), String(d.getMonth() + 1).padStart(2, '0'), d.getFullYear()].join('/') : '' const strToDate = (s: unknown): Date | null => { if (typeof s !== 'string') return null const [dd, mm, yyyy] = s.split('/').map(Number) if (!dd || !mm || !yyyy) return null return new Date(yyyy, mm - 1, dd) } return { args, editHeaders, items, onSave, dateToStr, strToDate, mdiCheck, mdiClose, mdiPencil } }, template: ` `, }), parameters: { sourceCode: [ { name: 'Template', code: ` `, }, { name: 'Script', code: ` `, }, ], }, }