From 7a11ff4e8576ce13ffe012662468d498cec2a6b9 Mon Sep 17 00:00:00 2001 From: Corentin Date: Mon, 18 May 2026 14:31:50 +0000 Subject: [PATCH] feat(database-view): add row creation The client only supported editing existing rows (use-update-row = PATCH only); a freshly created table had no rows and no way to add one. Add useCreateRow (POST /api/v1/tables/:tableId/rows, bridge already supports it) and an 'Ajouter une ligne' button in the grid renderer, gated by canWriteRows, with view-cache invalidation. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../database-view/hooks/use-create-row.ts | 43 +++++++++++++++++++ .../renderers/table-renderer.tsx | 15 +++++++ 2 files changed, 58 insertions(+) create mode 100644 apps/client/src/features/acadenice/database-view/hooks/use-create-row.ts diff --git a/apps/client/src/features/acadenice/database-view/hooks/use-create-row.ts b/apps/client/src/features/acadenice/database-view/hooks/use-create-row.ts new file mode 100644 index 00000000..8ae15029 --- /dev/null +++ b/apps/client/src/features/acadenice/database-view/hooks/use-create-row.ts @@ -0,0 +1,43 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { getBridgeClient, resolveBridgeUrl } from "../services/bridge-client"; +import type { BridgeRow } from "../types/database-view.types"; +import { VIEW_DATA_QUERY_KEY } from "./use-view-data"; + +interface UseCreateRowOptions { + tableId: string; + viewId: string; + bridgeUrl?: string | null; +} + +/** + * Create a new row in the table via the bridge, then invalidate the view + * cache so the row appears. + * + * Server-first (no optimistic insert): the bridge assigns the row id, and the + * inline editor PATCHes by id afterwards — fabricating a temporary id here + * would desync the first edit. An empty payload creates a blank row (Baserow + * fills defaults); callers may pass initial field values. + */ +export function useCreateRow({ tableId, viewId, bridgeUrl }: UseCreateRowOptions) { + const queryClient = useQueryClient(); + const url = resolveBridgeUrl(bridgeUrl); + + return useMutation | void>({ + mutationFn: async (fields) => { + const client = getBridgeClient(url); + return (await (client.post( + `/api/v1/tables/${tableId}/rows`, + fields ?? {}, + ) as unknown)) as BridgeRow; + }, + + onSettled: () => { + // Reconcile with the server. The bridge also emits an SSE row.created + // event which triggers the same invalidation — idempotent. + queryClient.invalidateQueries({ + queryKey: [VIEW_DATA_QUERY_KEY, viewId], + exact: false, + }); + }, + }); +} diff --git a/apps/client/src/features/acadenice/database-view/renderers/table-renderer.tsx b/apps/client/src/features/acadenice/database-view/renderers/table-renderer.tsx index 2a83f333..343a67ad 100644 --- a/apps/client/src/features/acadenice/database-view/renderers/table-renderer.tsx +++ b/apps/client/src/features/acadenice/database-view/renderers/table-renderer.tsx @@ -23,6 +23,7 @@ import { modals } from "@mantine/modals"; import { useQueryClient } from "@tanstack/react-query"; import { useViewData } from "../hooks/use-view-data"; import { useUpdateRow } from "../hooks/use-update-row"; +import { useCreateRow } from "../hooks/use-create-row"; import { useDatabaseRealtimeUpdates } from "../hooks/use-database-realtime-updates"; import { usePermissions } from "../hooks/use-permissions"; import { useAcadenicePermissions } from "@/features/acadenice/rbac/hooks/use-acadenice-permissions"; @@ -186,6 +187,7 @@ export function TableRenderer({ tableId, viewId, bridgeUrl }: TableRendererProps const canAdminTables = acadenicePerms.hasPermission("tables:write"); const queryClient = useQueryClient(); const updateRow = useUpdateRow({ tableId, viewId, bridgeUrl }); + const createRow = useCreateRow({ tableId, viewId, bridgeUrl }); // Field admin modal state. const [fieldModalOpen, setFieldModalOpen] = useState(false); @@ -352,6 +354,19 @@ export function TableRenderer({ tableId, viewId, bridgeUrl }: TableRendererProps + {canWriteRows && ( + + )} + {/* Pagination — only shown when there is more than one page. */} {(page > 1 || hasNextPage) && (