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: `
{{ item.birthdate.toLocaleDateString('fr-FR') }}
update(strToDate(s))" />
`,
}),
parameters: {
sourceCode: [
{
name: 'Template',
code: `
{{ item.birthdate.toLocaleDateString('fr-FR') }}
update(strToDate(s))" />
`,
},
{
name: 'Script',
code: `
`,
},
],
},
}