Champs collection
Modifier des tableaux avec identités stables, champs imbriqués et validation.
Un champ collection conserve un tableau dans le formulaire parent. Il permet d’ajouter, modifier, supprimer et réordonner des éléments en ligne ou dans une boîte de dialogue.
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 { FormConfig } from "@/components/ui/yayaw-table/components/forms/types";
import { customers, orderSchema } from "../shared/orders";
import { customerConfig } from "./order-config";
import { renderProductSelect } from "./product-select";
export const orderForm: FormConfig = {
id: "orders",
title: "Edit order",
presentation: "modal",
width: "64rem",
schema: orderSchema,
fields: [
{ name: "reference", label: "Reference", type: "text", bulkEdit: false },
{
name: "customerId",
label: "Customer",
type: "tablePicker",
tablePicker: {
tableType: "customers",
config: customerConfig,
data: customers,
getRowId: (row) => String(row.id),
multiple: false,
syncUrl: false,
maxHeight: "18rem",
},
},
{
name: "lines",
label: "Order lines",
type: "collection",
collectionMode: "dialog",
addLabel: "Add line",
getItemKey: (line) => String(line.id),
createItem: () => ({
id: crypto.randomUUID(),
productId: "",
quantity: 1,
allocations: [],
}),
columns: [
{ id: "productId", header: "Product" },
{ id: "quantity", header: "Quantity" },
],
validateItem: (line) =>
Number(line.quantity) > 0 && line.productId
? []
: ["Choose a product and a positive quantity."],
validateItems: (lines) =>
lines.length ? [] : ["Add at least one line."],
itemFields: [
{
name: "productId",
label: "Product",
type: "custom",
renderField: renderProductSelect,
},
{
name: "quantity",
label: "Quantity",
type: "number",
min: 1,
step: 1,
},
{
name: "allocations",
label: "Warehouse allocations",
type: "collection",
collectionMode: "inline",
getItemKey: (allocation) => String(allocation.id),
createItem: () => ({
id: crypto.randomUUID(),
warehouse: "",
quantity: 1,
}),
itemFields: [
{ name: "warehouse", label: "Warehouse", type: "text" },
{ name: "quantity", label: "Quantity", type: "number", min: 1 },
],
},
],
},
],
};import type { FormConfig } from "@/components/ui/yayaw-table-vue/types";
import { customers, orderSchema } from "../shared/orders";
import { products } from "../shared/products";
import { customerConfig } from "./order-config";
export const orderForm: FormConfig = {
id: "orders",
title: "Edit order",
presentation: "modal",
width: "64rem",
schema: orderSchema,
fields: [
{ name: "reference", label: "Reference", type: "text", bulkEdit: false },
{
name: "customerId",
label: "Customer",
type: "tablePicker",
tablePicker: {
tableType: "customers",
config: customerConfig,
data: customers,
getRowId: (row) => String(row.id),
multiple: false,
syncUrl: false,
maxHeight: "18rem",
},
},
{
name: "lines",
label: "Order lines",
type: "collection",
collectionMode: "dialog",
addLabel: "Add line",
getItemKey: (line) => String(line.id),
createItem: () => ({
id: crypto.randomUUID(),
productId: "",
quantity: 1,
allocations: [],
}),
columns: [
{ id: "productId", header: "Product" },
{ id: "quantity", header: "Quantity" },
],
validateItem: (line) =>
Number(line.quantity) > 0 && line.productId
? []
: ["Choose a product and a positive quantity."],
validateItems: (lines) =>
lines.length ? [] : ["Add at least one line."],
itemFields: [
{
name: "productId",
label: "Product",
type: "select",
options: products.map((product) => ({
value: product.id,
label: product.name,
})),
},
{
name: "quantity",
label: "Quantity",
type: "number",
min: 1,
step: 1,
},
{
name: "allocations",
label: "Warehouse allocations",
type: "collection",
collectionMode: "inline",
getItemKey: (allocation) => String(allocation.id),
createItem: () => ({
id: crypto.randomUUID(),
warehouse: "",
quantity: 1,
}),
itemFields: [
{ name: "warehouse", label: "Warehouse", type: "text" },
{ name: "quantity", label: "Quantity", type: "number", min: 1 },
],
},
],
},
],
};Identité stable des éléments
Générez un identifiant dans createItem, puis utilisez-le dans getItemKey. L’index change lors d’un déplacement. createActions peut proposer plusieurs modèles d’éléments. columns définit leur résumé ; itemFields définit l’éditeur. Utilisez renderItemForm pour un éditeur métier personnalisé.
Valider à chaque niveau
validateItem vérifie un élément, validateItems la collection et le schéma du formulaire le document soumis. Les itemFields peuvent contenir une autre collection. Dans la recette commande, les lignes contiennent des allocations par entrepôt. La soumission enregistre la valeur imbriquée complète ; le tableau ne définit pas votre schéma relationnel.
Tester l’annulation
Ouvrez un élément, modifiez-le puis annulez : la valeur parent doit rester intacte. Réordonnez deux éléments et vérifiez que leurs identifiants les suivent. Testez une collection vide, un élément imbriqué invalide et un échec de soumission parent.
Essayer dans la page
Ouvrez la commande existante, puis ajoutez, réordonnez ou retirez ses lignes avant l’enregistrement.
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 { getFormConfig } from "./get-order-form";
import { getOrderActions } from "./order-actions";
import { orderConfig } from "./order-config";
import type { ExamplePresentation } from "./presentation";
const getConfig = () => orderConfig;
export default function OrderRecipe(presentation: ExamplePresentation = {}) {
const [queryClient] = useState(() => new QueryClient());
return (
<QueryClientProvider client={queryClient}>
<NuqsAdapter>
<DataTable
{...presentation}
getFormConfig={getFormConfig}
getRowId={(row) => String(row.id)}
getTableActions={getOrderActions}
getTableConfig={getConfig}
tableId="recipe-order"
tableType="orders"
title={presentation.title ?? "Order"}
/>
<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 { getFormConfig } from "./get-order-form";
import { getOrderActions } from "./order-actions";
import { orderConfig } from "./order-config";
const getRowId = (row: Record<string, unknown>) => String(row.id);
</script>
<template>
<YayawDataTable v-bind="$props" table-type="orders" table-id="recipe-order" :title="title ?? 'Order'"
:config="orderConfig" :get-table-actions="getOrderActions" :get-row-id="getRowId"
:get-form-config="getFormConfig" />
</template>