From 8542f07783d40d4e337ad9fcad2308acb05be70a Mon Sep 17 00:00:00 2001 From: kumarabhirup Date: Tue, 3 Mar 2026 17:58:45 -0800 Subject: [PATCH] refactor(web): improve data handling in DataTable and ObjectTable components This commit refactors the DataTable and ObjectTable components to enhance state management. In DataTable, the column visibility state is now set more efficiently by defaulting to an empty object when no initial visibility is provided. In ObjectTable, local entries are introduced to maintain alignment with server updates, and a new callback for local value changes is added to EditableCell, improving the responsiveness of the UI during data edits. Additionally, the handling of row selection during bulk delete operations is updated to use local entries, ensuring consistency across the component's state. --- .../app/components/workspace/data-table.tsx | 4 +-- .../app/components/workspace/object-table.tsx | 31 ++++++++++++++++--- apps/web/app/workspace/page.tsx | 12 ++++++- 3 files changed, 39 insertions(+), 8 deletions(-) diff --git a/apps/web/app/components/workspace/data-table.tsx b/apps/web/app/components/workspace/data-table.tsx index 988804d1874..9632c29cfb6 100644 --- a/apps/web/app/components/workspace/data-table.tsx +++ b/apps/web/app/components/workspace/data-table.tsx @@ -192,9 +192,7 @@ export function DataTable({ const [columnVisibility, setColumnVisibility] = useState(initialColumnVisibility ?? {}); // Sync column visibility when the prop changes (e.g. loading a saved view) useEffect(() => { - if (initialColumnVisibility) { - setColumnVisibility(initialColumnVisibility); - } + setColumnVisibility(initialColumnVisibility ?? {}); }, [initialColumnVisibility]); const [internalRowSelection, setInternalRowSelection] = useState>({}); const [showColumnsMenu, setShowColumnsMenu] = useState(false); diff --git a/apps/web/app/components/workspace/object-table.tsx b/apps/web/app/components/workspace/object-table.tsx index 13cb93a5c38..6640b05c720 100644 --- a/apps/web/app/components/workspace/object-table.tsx +++ b/apps/web/app/components/workspace/object-table.tsx @@ -180,6 +180,7 @@ function EditableCell({ members, relationLabels, onNavigate, + onLocalValueChange, onSaved, }: { value: unknown; @@ -190,6 +191,7 @@ function EditableCell({ members?: Array<{ id: string; name: string }>; relationLabels?: Record>; onNavigate?: (objectName: string) => void; + onLocalValueChange?: (value: string) => void; onSaved?: () => void; }) { const [editing, setEditing] = useState(false); @@ -212,6 +214,7 @@ function EditableCell({ const isRelation = field.type === "relation" && !!field.related_object_name; const save = useCallback(async (val: string) => { + onLocalValueChange?.(val); try { await fetch(`/api/workspace/objects/${encodeURIComponent(objectName)}/entries/${encodeURIComponent(entryId)}`, { method: "PATCH", @@ -220,7 +223,7 @@ function EditableCell({ }); onSaved?.(); } catch { /* ignore */ } - }, [objectName, entryId, fieldName, onSaved]); + }, [objectName, entryId, fieldName, onLocalValueChange, onSaved]); const handleChange = (val: string) => { setLocalValue(val); @@ -385,6 +388,25 @@ export function ObjectTable({ }: ObjectTableProps) { const [rowSelection, setRowSelection] = useState>({}); const [showAddModal, setShowAddModal] = useState(false); + const [localEntries, setLocalEntries] = useState(entries as EntryRow[]); + + // Keep local rows aligned with server-paginated updates. + useEffect(() => { + setLocalEntries(entries as EntryRow[]); + }, [entries]); + + const updateLocalEntryField = useCallback((entryId: string, fieldName: string, value: string) => { + setLocalEntries((prev) => + prev.map((entry) => { + const eid = entry.entry_id; + const currentEntryId = String( + eid != null && typeof eid === "object" ? JSON.stringify(eid) : (eid ?? ""), + ); + if (currentEntryId !== entryId) {return entry;} + return { ...entry, [fieldName]: value }; + }), + ); + }, []); const activeReverseRelations = useMemo(() => { if (!reverseRelations) {return [];} @@ -440,6 +462,7 @@ export function ObjectTable({ members={members} relationLabels={relationLabels} onNavigate={onNavigateToObject} + onLocalValueChange={(value) => updateLocalEntryField(entryId, field.name, value)} onSaved={onRefresh} /> ); @@ -485,7 +508,7 @@ export function ObjectTable({ const handleBulkDelete = useCallback(async () => { const selectedIds = Object.keys(rowSelection) .filter((k) => rowSelection[k]) - .map((idx) => safeString(entries[Number(idx)]?.entry_id)) + .map((idx) => safeString(localEntries[Number(idx)]?.entry_id)) .filter(Boolean); if (selectedIds.length === 0) {return;} @@ -500,7 +523,7 @@ export function ObjectTable({ setRowSelection({}); onRefresh?.(); } catch { /* ignore */ } - }, [rowSelection, entries, objectName, onRefresh]); + }, [rowSelection, localEntries, objectName, onRefresh]); // Single delete handler const handleDeleteEntry = useCallback(async (entry: EntryRow) => { @@ -574,7 +597,7 @@ export function ObjectTable({ <> (undefined); const searchTimerRef = useRef | null>(null); + const hasActiveServerQuery = + filters.rules.length > 0 || + ((sortRules?.length ?? 0) > 0) || + serverSearch.trim().length > 0; // Column visibility: maps field IDs to boolean (false = hidden) const [viewColumns, setViewColumns] = useState(undefined); @@ -2279,10 +2283,16 @@ function ObjectView({ } }, [serverPage, serverPageSize, filters, sortRules, serverSearch, data.object.name]); - // Sync initial data from props (when parent refreshes via SSE) + // Sync incoming object data. If a server query is active (filters/search/sort), + // re-fetch with the active query instead of showing unfiltered parent entries. useEffect(() => { + if (hasActiveServerQuery) { + void fetchEntries(); + return; + } setEntries(data.entries); setTotalCount(data.totalCount ?? data.entries.length); + // eslint-disable-next-line react-hooks/exhaustive-deps -- only react to parent data updates }, [data.entries, data.totalCount]); // Sync saved views when data changes (e.g. SSE refresh from AI editing .object.yaml)