diff --git a/src/renderer/components/team/kanban/KanbanBoard.tsx b/src/renderer/components/team/kanban/KanbanBoard.tsx index 811d1f20..270def53 100644 --- a/src/renderer/components/team/kanban/KanbanBoard.tsx +++ b/src/renderer/components/team/kanban/KanbanBoard.tsx @@ -544,7 +544,6 @@ export const KanbanBoard = ({ {viewMode === 'grid' ? ( column.id)} columns={visibleColumns.map((column) => { const columnTasks = groupedOrdered.get(column.id) ?? []; diff --git a/src/renderer/components/team/kanban/KanbanGridLayout.tsx b/src/renderer/components/team/kanban/KanbanGridLayout.tsx index 9e53c782..329c1e64 100644 --- a/src/renderer/components/team/kanban/KanbanGridLayout.tsx +++ b/src/renderer/components/team/kanban/KanbanGridLayout.tsx @@ -1,5 +1,5 @@ /* eslint-disable tailwindcss/no-custom-classname -- this adapter needs stable non-Tailwind class hooks for react-grid-layout handles. */ -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useCallback, useMemo, useState } from 'react'; import ReactGridLayout, { WidthProvider } from 'react-grid-layout/legacy'; import { usePersistedGridLayout } from '@renderer/hooks/usePersistedGridLayout'; @@ -24,7 +24,7 @@ const DEFAULT_ITEM_HEIGHT = Math.max( ); const DEFAULT_MIN_HEIGHT = 10; const DEFAULT_MIN_WIDTH = 3; -const GRID_SCOPE_PREFIX = 'kanban-grid-layout:v2'; +const GRID_SCOPE_KEY = 'kanban-grid-layout:global'; const RESIZE_HANDLES: ResizeHandleAxis[] = ['s', 'w', 'e', 'n', 'sw', 'nw', 'se', 'ne']; const WidthAwareGridLayout = WidthProvider(ReactGridLayout); @@ -39,11 +39,16 @@ export interface KanbanGridColumn { } interface KanbanGridLayoutProps { - teamName: string; columns: KanbanGridColumn[]; allColumnIds: KanbanColumnId[]; } +interface LoadedKanbanGridLayoutProps { + readonly columns: KanbanGridColumn[]; + readonly visibleItems: PersistedGridLayoutItem[]; + readonly onPersistLayout: (layout: Layout, options?: { persist?: boolean }) => void; +} + function buildDefaultItems(itemIds: string[]): PersistedGridLayoutItem[] { return itemIds.map((id, index) => ({ id, @@ -94,49 +99,31 @@ function renderResizeHandle(axis: ResizeHandleAxis, ref: Ref): Reac ); } -export const KanbanGridLayout = ({ - teamName, +const LoadedKanbanGridLayout = ({ columns, - allColumnIds, -}: KanbanGridLayoutProps): React.JSX.Element => { + visibleItems, + onPersistLayout, +}: Readonly): ReactElement => { const columnMap = useMemo(() => new Map(columns.map((column) => [column.id, column])), [columns]); - const visibleColumnIds = useMemo(() => columns.map((column) => column.id), [columns]); - const { visibleItems, applyVisibleItems, isLoaded } = usePersistedGridLayout({ - scopeKey: `${GRID_SCOPE_PREFIX}:${teamName}`, - allItemIds: allColumnIds, - visibleItemIds: visibleColumnIds, - cols: GRID_COLS, - repository: browserGridLayoutRepository, - buildDefaultItems, - }); - const [renderLayout, setRenderLayout] = useState(() => visibleItems.map(toReactGridLayoutItem) ); - useEffect(() => { - if (!isLoaded) return; - setRenderLayout(visibleItems.map(toReactGridLayoutItem)); - }, [isLoaded, visibleItems]); - const applyReactGridLayout = useCallback( (layout: Layout, options?: { persist?: boolean }) => { setRenderLayout(layout); if (options?.persist) { - applyVisibleItems(fromReactGridLayout(layout), options); + onPersistLayout(layout, options); } }, - [applyVisibleItems] + [onPersistLayout] ); - if (!isLoaded) { - return
; - } - return (
); }; + +export const KanbanGridLayout = ({ + columns, + allColumnIds, +}: KanbanGridLayoutProps): React.JSX.Element => { + const visibleColumnIds = useMemo(() => columns.map((column) => column.id), [columns]); + const { visibleItems, applyVisibleItems, isLoaded } = usePersistedGridLayout({ + scopeKey: GRID_SCOPE_KEY, + allItemIds: allColumnIds, + visibleItemIds: visibleColumnIds, + cols: GRID_COLS, + repository: browserGridLayoutRepository, + buildDefaultItems, + }); + + const applyReactGridLayout = useCallback( + (layout: Layout, options?: { persist?: boolean }) => { + if (options?.persist) { + applyVisibleItems(fromReactGridLayout(layout), options); + } + }, + [applyVisibleItems] + ); + + if (!isLoaded) { + return
; + } + + const gridKey = visibleItems + .map((item) => `${item.id}:${item.x}:${item.y}:${item.w}:${item.h}`) + .join('|'); + + return ( + + ); +}; /* eslint-enable tailwindcss/no-custom-classname -- stable class hooks remain scoped to this file. */ diff --git a/src/renderer/services/layout-system/BrowserGridLayoutRepository.ts b/src/renderer/services/layout-system/BrowserGridLayoutRepository.ts index 27b07ae6..629f13a1 100644 --- a/src/renderer/services/layout-system/BrowserGridLayoutRepository.ts +++ b/src/renderer/services/layout-system/BrowserGridLayoutRepository.ts @@ -38,7 +38,7 @@ function removeLocalStorage(key: string): void { } function pickNewestState( - ...states: Array + ...states: (PersistedGridLayoutState | null | undefined)[] ): PersistedGridLayoutState | null { return states.reduce((latest, current) => { if (!current) return latest;