Administration groupée
Conserver la sélection entre pages et soumettre les champs choisis.
Ce que vous allez construire
Combinez sélection sur plusieurs pages, modifications groupées validées, permissions par ligne et action Archiver fournie par l’application.
Partez de l’installation React ou Vue. Assemblez les fichiers complets ci-dessous et les modules partagés. Le stockage mémoire se réinitialise au rechargement ; une API doit conserver les mêmes contrats.
Fichiers nécessaires : shared/products.ts, shared/store.ts, shared/admin-mutations.ts, product-config.ts, list-records.ts, view-actions.ts, editable-actions.ts, admin-actions.ts, product-form.ts, get-form-config.ts, admin-config.ts, recipe-admin.
Copiez le stockage et l’adaptateur communs et le catalogue de formulaire produit. La recette affiche volontairement deux lignes par page pour rendre la sélection entre pages visible.
admin-config.ts
import { defineTableConfig } from "@/components/ui/yayaw-table/config/helpers";
import { productConfig } from "./product-config";
export const adminConfig = defineTableConfig({
...productConfig,
table: {
...productConfig.table,
layoutPreset: "admin",
defaultPageSize: 2,
pageSizeOptions: [2, 10, 20],
enableRowSelection: true,
enableMultiRowSelection: true,
preserveSelectionOnQuery: true,
allowEdit: true,
allowBulkEdit: true,
canEditRow: (row) => row.status !== "archived",
canSelectRow: (row) => row.status !== "archived",
bulkExport: true,
},
form: { editFormType: "products" },
});import { defineTableConfig } from "@/components/ui/yayaw-table-vue/config";
import { productConfig } from "./product-config";
export const adminConfig = defineTableConfig({
...productConfig,
table: {
...productConfig.table,
layoutPreset: "admin",
defaultPageSize: 2,
pageSizeOptions: [2, 10, 20],
enableRowSelection: true,
enableMultiRowSelection: true,
preserveSelectionOnQuery: true,
allowEdit: true,
allowBulkEdit: true,
canEditRow: (row) => row.status !== "archived",
canSelectRow: (row) => row.status !== "archived",
bulkExport: true,
},
form: { editFormType: "products" },
});Composant d’entrée
Données de démonstration. Les modifications restent dans cet aperçu.
"use client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { Archive, type LucideProps } from "lucide-react";
import { NuqsAdapter } from "nuqs/adapters/react";
import { useState } from "react";
import { Toaster } from "sonner";
import { DataTable } from "@/components/ui/yayaw-table";
import { adminMutations } from "../shared/admin-mutations";
import { getAdminActions } from "./admin-actions";
import { adminConfig } from "./admin-config";
import { getFormConfig } from "./get-form-config";
import type { ExamplePresentation } from "./presentation";
const getConfig = () => adminConfig;
// Custom bulk actions accept a function component for their icon.
const ArchiveIcon = (props: LucideProps) => <Archive {...props} />;
export default function AdminRecipe(presentation: ExamplePresentation = {}) {
const [queryClient] = useState(() => new QueryClient());
return (
<QueryClientProvider client={queryClient}>
<NuqsAdapter>
<DataTable
{...presentation}
customBulkActions={(selection) => [
{
id: "archive-products",
icon: ArchiveIcon,
label: "Archive",
confirm: {
title: "Archive selected products?",
description:
"Archived products remain visible but cannot be edited or selected.",
confirmLabel: "Archive",
},
onClick: async () => {
const result = adminMutations.bulkUpdate(
selection.selectedOriginalRows.map((row) => String(row.id)),
{ status: "archived" }
);
await queryClient.invalidateQueries({
queryKey: ["tableData", "recipe-admin"],
});
return {
...result,
clearSelection: result.success,
closeMenu: result.success,
};
},
},
]}
getFormConfig={getFormConfig}
getRowId={(row) => String(row.id)}
getTableActions={getAdminActions}
getTableConfig={getConfig}
tableId="recipe-admin"
tableType="products"
title={presentation.title ?? "Admin"}
/>
<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 { Archive } from "lucide-vue-next";
import { YayawDataTable } from "@/components/ui/yayaw-table-vue";
import type { BulkAction } from "@/components/ui/yayaw-table-vue/types";
import { adminMutations } from "../shared/admin-mutations";
import { getAdminActions } from "./admin-actions";
import { adminConfig } from "./admin-config";
import { getFormConfig } from "./get-form-config";
const customBulkActions: BulkAction[] = [
{
id: "archive-products",
label: "Archive",
icon: Archive,
confirm: {
title: "Archive selected products?",
description:
"Archived products remain visible but cannot be edited or selected.",
confirmLabel: "Archive",
},
handler: async (context) => {
const result = adminMutations.bulkUpdate(context.selectedIds, {
status: "archived",
});
await context.refresh();
return {
...result,
clearSelection: result.success,
closeMenu: result.success,
};
},
},
];
const getRowId = (row: Record<string, unknown>) => String(row.id);
</script>
<template>
<YayawDataTable v-bind="$props" table-type="products" table-id="recipe-admin" :title="title ?? 'Admin'"
:config="adminConfig" :get-table-actions="getAdminActions" :custom-bulk-actions="customBulkActions" :get-row-id="getRowId"
:get-form-config="getFormConfig" />
</template>Vérifier le résultat
Sélectionnez une fiche sur la première page.
Allez à la deuxième page et sélectionnez-en une autre : deux identifiants sont sélectionnés.
Ouvrez l’édition groupée et choisissez uniquement Status.
Choisissez Archived et validez : les deux fiches changent ; stock et prix restent intacts.
Appelez
bulkUpdateavec un identifiant absent :failedIdsle signale, les succès restent enregistrés.Vérifiez que Name est exclu grâce à
bulkEdit: false.
Utilisez le playground existant pour explorer les contrôles de la bibliothèque.
Vérifier la politique des lignes dans les mutations
La même règle est vérifiée au moment de l’écriture. Cette démonstration ne remplace pas l’authentification : ajoutez les vérifications de l’utilisateur et de l’organisation sur votre serveur. Les cibles inconnues ou archivées sont signalées dans failedIds ; l’éditeur intégré ne renvoie pas les écritures déjà réussies.
import { type createProductStore, productStore } from "./store";
/** Demo record policy. A real server must also authenticate and scope every request. */
export function createAdminMutations(
store: ReturnType<typeof createProductStore>
) {
const update = (id: string, patch: Record<string, unknown>) => {
if (store.rows.find((row) => row.id === id)?.status === "archived") {
return { success: false, error: "Archived products are read-only." };
}
return store.update(id, patch);
};
const bulkUpdate = (ids: string[], patch: Record<string, unknown>) => {
const failedIds: string[] = [];
for (const id of new Set(ids)) {
if (!update(id, patch).success) {
failedIds.push(id);
}
}
return {
success: failedIds.length === 0,
failedIds,
...(failedIds.length
? {
error:
"Some products cannot be edited. Retry the remaining records.",
}
: {}),
};
};
return { update, bulkUpdate };
}
export const adminMutations = createAdminMutations(productStore);import type { TableActions } from "@/components/ui/yayaw-table/providers/table-provider";
import { adminMutations } from "../shared/admin-mutations";
import { editableActions } from "./editable-actions";
const adminActions: TableActions = {
...editableActions,
update: (id, patch) => Promise.resolve(adminMutations.update(id, patch)),
bulkUpdate: (ids, patch) =>
Promise.resolve(adminMutations.bulkUpdate(ids, patch)),
};
export const getAdminActions = (tableType: string) =>
tableType === "products" ? adminActions : undefined;import type { TableActions } from "@/components/ui/yayaw-table-vue/types";
import { adminMutations } from "../shared/admin-mutations";
import { editableActions } from "./editable-actions";
const adminActions: TableActions = {
...editableActions,
update: (id, patch) => Promise.resolve(adminMutations.update(id, patch)),
bulkUpdate: (ids, patch) =>
Promise.resolve(adminMutations.bulkUpdate(ids, patch)),
};
export const getAdminActions = (tableType: string) =>
tableType === "products" ? adminActions : undefined;Vérifier le résultat pas à pas
| Action | Résultat attendu |
|---|---|
| Sélectionnez deux produits et ouvrez Modifier. | La liste de propriétés est vide et Appliquer est désactivé. |
Ajoutez Stock, saisissez 24, puis appliquez. | Seul Stock change sur ces deux cibles ; noms, prix et troisième produit restent inchangés. |
| Ajoutez puis retirez Prix avant d’appliquer une autre propriété. | Prix est absent de la modification envoyée. |
| Sélectionnez un produit, choisissez Archiver et confirmez. | Son statut devient Archived ; la ligne ne peut plus être sélectionnée ni modifiée. |
| Exportez les lignes sélectionnées avant de les archiver. | Le CSV contient les lignes sélectionnées, pas seulement la page courante. |
Sur mobile, utilisez les Réglages de la vue pour la présentation, le menu Données pour l’export, et le bouton de recherche de la barre d’outils. Réinitialiser une vue n’annule pas les écritures. Testez aussi le clavier.