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) && (