Fiches et activité
Consulter des informations typées et relier un historique applicatif.
Le registre actuel peut ouvrir une fiche en panneau latéral, modale ou consultation intégrée. L’application fournit l’historique et implémente l’annulation.
Avec le rowClickMode par défaut, un clic sur une ligne ouvre cette vue enregistrement dans tous les modes d’affichage. Il n’est pas nécessaire de fournir details pour l’obtenir : sans elle, la vue enregistrement dérive ses champs des colonnes de la table. Passez details={productDetails} (:details="productDetails" en Vue) pour contrôler sections, libellés et formats, ou details={false} (:details="false" en Vue) pour retirer entièrement la vue intégrée — un simple clic n’appelle alors plus que onOpenDetails/onRowActivate si vous en fournissez un. enableRowClickEdit ou rowClickMode: 'edit' ouvrent plutôt le formulaire d’édition, et une colonne de lien de ligne continue de naviguer.
Utilisez TableConfig.presentation à la racine pour une surface commune de consultation, création, édition et édition groupée, avec un choix mobile facultatif. Le défaut est un drawer à droite sur tous les écrans. Consultez la présentation commune des fiches pour la configuration et la migration.
Exemple de configuration
Partez du démarrage React ou du démarrage Vue. Le fichier suivant complète leur catalogue de produits. Passez cette configuration via getTableConfig en React ou config en Vue.
import type {
DetailRevertHandler,
RecordDetailsConfig,
} from "@/components/ui/yayaw-table/utils/record-details";
import { productStore } from "../shared/store";
export const productDetails: RecordDetailsConfig = {
presentation: "drawer",
title: (row) => String(row.name),
updatedAt: (row) => String(row.updatedAt),
sections: [
{
id: "product",
title: "Product",
fields: [
{ id: "name", label: "Name", type: "text" },
{
id: "price",
label: "Price",
type: "number",
numberFormat: { style: "currency", currency: "EUR" },
},
{ id: "stock", label: "Stock", type: "number" },
{ id: "status", label: "Status", type: "text" },
],
},
],
activity: (row) => productStore.activity(String(row.id)),
};
export const revertActivity: DetailRevertHandler = (row, entry) =>
productStore.revert(String(row.id), entry.id);import type {
DetailRevertHandler,
RecordDetailsConfig,
} from "@/components/ui/yayaw-table-vue/record-details";
import { productStore } from "../shared/store";
export const productDetails: RecordDetailsConfig = {
presentation: "drawer",
title: (row) => String(row.name),
updatedAt: (row) => String(row.updatedAt),
sections: [
{
id: "product",
title: "Product",
fields: [
{ id: "name", label: "Name", type: "text" },
{
id: "price",
label: "Price",
type: "number",
numberFormat: { style: "currency", currency: "EUR" },
},
{ id: "stock", label: "Stock", type: "number" },
{ id: "status", label: "Status", type: "text" },
],
},
],
activity: (row) => productStore.activity(String(row.id)),
};
export const revertActivity: DetailRevertHandler = (row, entry) =>
productStore.revert(String(row.id), entry.id);Choisir les champs visibles
Passez details={productDetails} en React ou :details="productDetails" en Vue. Sans sections explicites, les champs sont dérivés des colonnes. Des sections explicites permettent de choisir libellés, formats et champs exclus. La recette fiche fournit le stockage utilisé ci-dessus et branche onRevertActivity.
Annulation avec historique conservé
Chaque activité possède identifiant, acteur, date, action et changements avant/après éventuels. L’annulation ajoute un événement dont reverts pointe vers l’original, qui reste conservé. L’interface empêche les répétitions et conflits connus, mais le backend doit revérifier atomiquement valeurs et autorisations avant l’écriture.
Distribution requise
Les fiches sont présentes dans la distribution courante /r/ et dans l’instantané immuable v3.1.0 utilisés par ces exemples ; elles sont absentes de v3.0.0. Ne combinez pas cet exemple avec l’instantané plus ancien. Consultez la compatibilité.
Annulation clavier et éléments supprimés
Ctrl/Cmd+Z appelle le gestionnaire existant onRevertActivity(row, entry) pour l’événement admissible le plus récent. Il applique les mêmes règles reversible, reverts, de conflit et details.canRevert que l’onglet Activité. L’application conserve l’historique, contrôle les droits et revérifie les valeurs courantes dans une transaction ; la bibliothèque ne crée pas de journal en base et ne rend pas une suppression définitive réversible.
Fournissez details.history: () => records, chaque entrée contenant { row, activity }, pour inclure les éléments supprimés ou hors page. Sinon, l’annulation utilise les lignes chargées et details.activity(row). Fournissez la portée d’annulation souhaitée et marquez comme non réversibles les événements d’autres utilisateurs ou les opérations non prises en charge. Sans callback activity, l’onglet Activité lit également cet historique.
Les événements d’une opération en lot peuvent partager transactionId. Un raccourci annule ses membres admissibles avec le même gestionnaire. Les réussites sont rafraîchies et ne sont pas rejouées après un échec partiel ; les membres en échec restent disponibles. Cette compensation applicative est séquentielle, sans transaction atomique entre toutes les lignes. Renvoyez { success: false, error } en cas d’échec et ajoutez un événement compensateur avec reverts: originalEvent.id en cas de succès.
Les champs, éditeurs de texte, dialogues et menus ouverts gardent leurs raccourcis natifs. Le tableau actif capte les raccourcis ; un tableau visible unique les capte aussi lorsque le focus est sur la page. Avec plusieurs tableaux, une interaction choisit le tableau actif. Un gestionnaire d’annulation n’active pas implicitement le rétablissement ou les mutations par presse-papiers.
Vidéo dans une fiche
Un champ de détail type: "video" accepte une URL ou { url, poster, tracks }. Il affiche les contrôles natifs sans lecture automatique ; les pistes acceptent src, label, srcLang et kind: "captions" | "subtitles". Les URL dangereuses sont rejetées dans les deux frameworks.
Notifications d’annulation
Une annulation réussie affiche une seule notification après le succès de toute l’opération, y compris pour un lot annulé au clavier. Un échec partiel affiche son erreur sans annoncer que toute l’action a été annulée. L’onglet Activité et Ctrl/Cmd+Z utilisent la même phrase traduite.
details.labels.undoSuccess accepte une phrase complète avec le paramètre facultatif {action}. Les valeurs par défaut sont Action « {action} » annulée. en français et Action “{action}” undone. en anglais. Une médiathèque peut fournir Suppression annulée / Deletion undone : traduisez la phrase entière sans concaténer un suffixe ni deviner le genre du libellé d’activité.
Essayer dans la page
Modifiez une fiche, ouvrez ses informations et annulez la modification depuis son historique.
Données de démonstration. Les modifications restent dans cet aperçu.
"use client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { NuqsAdapter } from "nuqs/adapters/react";
import { useState } from "react";
import { Toaster } from "sonner";
import { DataTable } from "@/components/ui/yayaw-table";
import { editableActions } from "./editable-actions";
import { getFormConfig } from "./get-form-config";
import type { ExamplePresentation } from "./presentation";
import { productDetails, revertActivity } from "./product-details";
import { recordConfig } from "./record-config";
import { viewActions } from "./view-actions";
const getConfig = () => recordConfig;
const recordActions = { ...editableActions, views: viewActions };
const getRecordActions = () => recordActions;
export default function RecordRecipe(presentation: ExamplePresentation = {}) {
const [queryClient] = useState(() => new QueryClient());
return (
<QueryClientProvider client={queryClient}>
<NuqsAdapter>
<DataTable
{...presentation}
details={productDetails}
getFormConfig={getFormConfig}
getRowId={(row) => String(row.id)}
getTableActions={getRecordActions}
getTableConfig={getConfig}
onRevertActivity={revertActivity}
tableId="recipe-record"
tableType="products"
title={presentation.title ?? "Record"}
/>
<Toaster />
</NuqsAdapter>
</QueryClientProvider>
);
}Données de démonstration. Les modifications restent dans cet aperçu.
<script setup lang="ts">
import type { ExamplePresentation } from "./presentation";
defineProps<ExamplePresentation>();
import { YayawDataTable } from "@/components/ui/yayaw-table-vue";
import { editableActions } from "./editable-actions";
import { getFormConfig } from "./get-form-config";
import { productDetails, revertActivity } from "./product-details";
import { recordConfig } from "./record-config";
import { viewActions } from "./view-actions";
const recordActions = { ...editableActions, views: viewActions };
const getRecordActions = () => recordActions;
const getRowId = (row: Record<string, unknown>) => String(row.id);
</script>
<template>
<YayawDataTable v-bind="$props" table-type="products" table-id="recipe-record" :title="title ?? 'Record'"
:config="recordConfig" :get-table-actions="getRecordActions" :get-row-id="getRowId"
:get-form-config="getFormConfig" :details="productDetails" :on-revert-activity="revertActivity" />
</template>