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)