fix(instances): keep workspace-scoped filters in sync with active workspace

The per-column "Node" filter memoized its option list against TanStack's
Column object, which keeps the same identity across data updates, so the
checklist stayed frozen at whatever nodes were visible when the popover
first mounted instead of reflecting the current (workspace-scoped) rows.

Switching the active workspace also left the server/node/column filters
holding values from the previous workspace, which could filter the newly
scoped VM list down to nothing.
This commit is contained in:
markmorado
2026-07-24 07:00:41 +05:00
parent dfe91964d9
commit 29a5d4381d
2 changed files with 33 additions and 8 deletions

View File

@@ -1,4 +1,3 @@
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import type { Column, FilterFn, RowData } from '@tanstack/react-table'; import type { Column, FilterFn, RowData } from '@tanstack/react-table';
import { ListFilter, X } from 'lucide-react'; import { ListFilter, X } from 'lucide-react';
@@ -49,13 +48,18 @@ export function ColumnFilter<TData>({ column, variant = 'text', formatOption }:
? Array.isArray(filterValue) && filterValue.length > 0 ? Array.isArray(filterValue) && filterValue.length > 0
: !!filterValue; : !!filterValue;
const uniqueValues = useMemo(() => { // Not memoized on `column` — TanStack keeps the same Column object identity
if (variant !== 'select') return []; // across data updates, so a useMemo keyed on it would freeze these options
return Array.from(column.getFacetedUniqueValues().keys()) // at whatever the table's data looked like when this popover first mounted
.filter((v): v is string => v != null && String(v).length > 0) // (e.g. before a workspace switch re-scoped the rows). getFacetedUniqueValues()
.map(String) // is already memoized internally against the actual row data.
.sort(); const uniqueValues =
}, [variant, column]); variant === 'select'
? Array.from(column.getFacetedUniqueValues().keys())
.filter((v): v is string => v != null && String(v).length > 0)
.map(String)
.sort()
: [];
const selected = (Array.isArray(filterValue) ? filterValue : []) as string[]; const selected = (Array.isArray(filterValue) ? filterValue : []) as string[];

View File

@@ -88,6 +88,7 @@ import { formatBytes, vmTypeLabel, formatUptime, formatVmConfig } from '@/lib/fo
import { apiClient } from '@/lib/api-client'; import { apiClient } from '@/lib/api-client';
import { useDeployTasksStore } from '@/stores/deploy-tasks-store'; import { useDeployTasksStore } from '@/stores/deploy-tasks-store';
import { useBulkTasksStore } from '@/stores/bulk-tasks-store'; import { useBulkTasksStore } from '@/stores/bulk-tasks-store';
import { useWorkspaceStore } from '@/stores/workspace-store';
import type { VMInstance } from '@/types'; import type { VMInstance } from '@/types';
import { toast } from 'sonner'; import { toast } from 'sonner';
@@ -365,6 +366,26 @@ export default function InstancesPage() {
}); });
const [rowSelection, setRowSelection] = useState<RowSelectionState>({}); const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
// Reset every filter that can reference a server/node identity when the
// active workspace changes — those values belong to whatever workspace was
// active when they were picked, and silently keeping them can filter the
// new workspace's (now-scoped) VM list down to nothing.
const activeWorkspaceId = useWorkspaceStore((s) => s.activeWorkspaceId);
// Compares against the previous *value* (not a one-shot "first render" flag)
// so React 18 StrictMode's dev-only double effect invocation on mount can't
// misfire this as a real workspace change.
const prevWorkspaceIdRef = useRef(activeWorkspaceId);
useEffect(() => {
if (prevWorkspaceIdRef.current === activeWorkspaceId) return;
prevWorkspaceIdRef.current = activeWorkspaceId;
setServerFilter('all');
localStorage.setItem('instances-server-filter', 'all');
setNodeFilter('all');
localStorage.setItem('instances-node-filter', 'all');
setColumnFilters([]);
setRowSelection({});
}, [activeWorkspaceId]);
// Persist sorting + column filters across reloads // Persist sorting + column filters across reloads
useEffect(() => { useEffect(() => {
localStorage.setItem('instances-sorting', JSON.stringify(sorting)); localStorage.setItem('instances-sorting', JSON.stringify(sorting));