From a216e73ede6b52a034a8970620845975e2b0dd1c Mon Sep 17 00:00:00 2001 From: iliya Date: Wed, 11 Mar 2026 18:32:08 +0200 Subject: [PATCH] refactor: update Kanban components for improved layout management and persistence - Removed the teamName prop from KanbanGridLayout to streamline component usage. - Renamed GRID_SCOPE_PREFIX to GRID_SCOPE_KEY for clarity in layout management. - Introduced LoadedKanbanGridLayout for better separation of concerns in rendering. - Enhanced the usePersistedGridLayout hook to improve layout persistence handling. - Updated KanbanBoard to utilize the new LoadedKanbanGridLayout, improving task organization. --- .../components/team/kanban/KanbanBoard.tsx | 1 - .../team/kanban/KanbanGridLayout.tsx | 84 ++++++++++++------- .../BrowserGridLayoutRepository.ts | 2 +- 3 files changed, 57 insertions(+), 30 deletions(-) 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;