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.
This commit is contained in:
parent
a1cb0b8372
commit
8542f07783
@ -192,9 +192,7 @@ export function DataTable<TData, TValue>({
|
||||
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>(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<Record<string, boolean>>({});
|
||||
const [showColumnsMenu, setShowColumnsMenu] = useState(false);
|
||||
|
||||
@ -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<string, Record<string, string>>;
|
||||
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<Record<string, boolean>>({});
|
||||
const [showAddModal, setShowAddModal] = useState(false);
|
||||
const [localEntries, setLocalEntries] = useState<EntryRow[]>(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({
|
||||
<>
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={entries as EntryRow[]}
|
||||
data={localEntries}
|
||||
enableSorting
|
||||
enableGlobalFilter
|
||||
enableRowSelection
|
||||
|
||||
@ -2223,6 +2223,10 @@ function ObjectView({
|
||||
const [serverSearch, setServerSearch] = useState("");
|
||||
const [sortRules, _setSortRules] = useState<SortRule[] | undefined>(undefined);
|
||||
const searchTimerRef = useRef<ReturnType<typeof setTimeout> | 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<string[] | undefined>(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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user