Record workspace
Read a record, edit it, inspect activity, undo, and save a view.
Copy the shared mutation store and adapter, the product form catalog, and the details and view files below. This example requires the current registry or the v3.1.0 snapshot, not the older v3.0.0 one.
record-config.ts
import { defineTableConfig } from "@/components/ui/yayaw-table/config/helpers";
import { productConfig } from "./product-config";
export const recordConfig = defineTableConfig({
...productConfig,
table: {
...productConfig.table,
allowEdit: true,
enableViews: true,
allowViewSave: true,
rowClickMode: "default",
},
form: { editFormType: "products" },
});import { defineTableConfig } from "@/components/ui/yayaw-table-vue/config";
import { productConfig } from "./product-config";
export const recordConfig = defineTableConfig({
...productConfig,
table: {
...productConfig.table,
allowEdit: true,
enableViews: true,
allowViewSave: true,
rowClickMode: "default",
},
form: { editFormType: "products" },
});product-details.ts
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);view-actions.ts
import type {
TableView,
TableViewActions,
} from "@/components/ui/yayaw-table/types/view-types";
/** Demo persistence lasts for this module's lifetime. Scope server storage by user and organization. */
const views = new Map<string, TableView>();
const favorites = new Map<string, string | null>();
export const viewActions: TableViewActions = {
list: ({ tableId }) =>
Promise.resolve({
data: [...views.values()].filter((view) => view.tableId === tableId),
}),
create: (input) => {
const view = {
...input,
id: crypto.randomUUID(),
createdById: "demo-user",
};
views.set(view.id, structuredClone(view));
return Promise.resolve({ success: true, data: view });
},
update: (id, input) => {
const current = views.get(id);
if (!current || (input.tableId && input.tableId !== current.tableId)) {
return Promise.resolve({ success: false, error: "View not found." });
}
const view = { ...current, ...input };
views.set(id, structuredClone(view));
return Promise.resolve({ success: true, data: view });
},
delete: (id, { tableId }) => {
if (views.get(id)?.tableId !== tableId) {
return Promise.resolve({ success: false, error: "View not found." });
}
views.delete(id);
if (favorites.get(tableId) === id) {
favorites.delete(tableId);
}
return Promise.resolve({ success: true, data: { id } });
},
getFavorite: ({ tableId }) =>
Promise.resolve({
success: true,
data: { viewId: favorites.get(tableId) ?? null },
}),
setFavorite: (viewId, { tableId }) => {
if (viewId && views.get(viewId)?.tableId !== tableId) {
return Promise.resolve({ success: false, error: "View not found." });
}
favorites.set(tableId, viewId);
return Promise.resolve({ success: true, data: { viewId } });
},
};import type {
TableView,
TableViewActions,
} from "@/components/ui/yayaw-table-vue/types";
/** Demo persistence lasts for this module's lifetime. Scope server storage by user and organization. */
const views = new Map<string, TableView>();
const favorites = new Map<string, string | null>();
export const viewActions: TableViewActions = {
list: ({ tableId }) =>
Promise.resolve({
data: [...views.values()].filter((view) => view.tableId === tableId),
}),
create: (input) => {
const view = {
...input,
id: crypto.randomUUID(),
createdById: "demo-user",
};
views.set(view.id, structuredClone(view));
return Promise.resolve({ success: true, data: view });
},
update: (id, input) => {
const current = views.get(id);
if (!current || (input.tableId && input.tableId !== current.tableId)) {
return Promise.resolve({ success: false, error: "View not found." });
}
const view = { ...current, ...input };
views.set(id, structuredClone(view));
return Promise.resolve({ success: true, data: view });
},
delete: (id, { tableId }) => {
if (views.get(id)?.tableId !== tableId) {
return Promise.resolve({ success: false, error: "View not found." });
}
views.delete(id);
if (favorites.get(tableId) === id) {
favorites.delete(tableId);
}
return Promise.resolve({ success: true });
},
getFavorite: ({ tableId }) =>
Promise.resolve({
success: true,
data: { viewId: favorites.get(tableId) ?? null },
}),
setFavorite: (viewId, { tableId }) => {
if (viewId && views.get(viewId)?.tableId !== tableId) {
return Promise.resolve({ success: false, error: "View not found." });
}
favorites.set(tableId, viewId);
return Promise.resolve({ success: true, data: { viewId } });
},
};Entry component
Sample data. Changes stay in this preview.
"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>
);
}Sample data. Changes stay in this preview.
<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>Verify the result
Open Oak desk and read its typed fields.
Edit stock to 15 and save.
Open Activity: the original 12 → 15 change appears.
Undo it: stock returns to 12 and a new undo event references the original event.
Verify a second undo is unavailable.
Change visible columns, save a view, set it as your favorite and remount the table: the view restores. Reloading the browser resets this in-memory store.
Use the existing playground to explore the library’s controls.