From 06e5f252a462173cb2221ab5753b84a1e0c1cdf3 Mon Sep 17 00:00:00 2001 From: "IGNY8 VPS (Salman)" Date: Mon, 15 Dec 2025 10:44:22 +0000 Subject: [PATCH] column visibility fixed in this --- frontend/src/store/columnVisibilityStore.ts | 184 ++++--------------- frontend/src/templates/TablePageTemplate.tsx | 71 ++----- 2 files changed, 44 insertions(+), 211 deletions(-) diff --git a/frontend/src/store/columnVisibilityStore.ts b/frontend/src/store/columnVisibilityStore.ts index 09ef47fd..0e8906e1 100644 --- a/frontend/src/store/columnVisibilityStore.ts +++ b/frontend/src/store/columnVisibilityStore.ts @@ -1,171 +1,53 @@ /** * Column Visibility Store (Zustand) - * Manages column visibility settings per page with localStorage persistence - * Uses the same pattern as siteStore and sectorStore - * Stores preferences per user for multi-user support - * Column preferences expire after 30 days + * Simple localStorage-based column visibility per page per user */ import { create } from 'zustand'; -import { persist, createJSONStorage, StateStorage } from 'zustand/middleware'; - -interface ColumnPreference { - columns: string[]; - timestamp: number; -} interface ColumnVisibilityState { - // Map of page pathname to column preference with timestamp - pageColumns: Record; - - // Hydration flag to know when persist has loaded - _hasHydrated: boolean; - setHasHydrated: (state: boolean) => void; - - // Actions - setPageColumns: (pathname: string, columnKeys: string[]) => void; - getPageColumns: (pathname: string) => string[]; - toggleColumn: (pathname: string, columnKey: string) => void; - resetPageColumns: (pathname: string) => void; + getVisibleColumns: (pathname: string) => string[]; + setVisibleColumns: (pathname: string, columns: string[]) => void; } -// Helper function to get user ID directly from localStorage (synchronous, no race conditions) -// CRITICAL: We must read directly from localStorage, not from useAuthStore.getState().user -// because the auth store might not be hydrated yet when this storage is accessed -const getUserIdFromStorage = (): string => { +// Get user ID from auth storage +const getUserId = (): string => { try { const authData = localStorage.getItem('auth-storage'); if (authData) { const parsed = JSON.parse(authData); - const userId = parsed?.state?.user?.id; - if (userId) { - return String(userId); - } + return String(parsed?.state?.user?.id || 'anonymous'); } - } catch (error) { - // Silent fail - will use anonymous + } catch (e) { + // ignore } return 'anonymous'; }; -// Custom storage that uses user-specific keys -// IMPORTANT: The persist middleware passes the 'name' param, but we need to append user ID -// CRITICAL FIX: Get user ID directly from localStorage synchronously to avoid race conditions -const userSpecificStorage: StateStorage = { - getItem: (name: string) => { - const userId = getUserIdFromStorage(); - const key = `${name}-user-${userId}`; - const value = localStorage.getItem(key); - if (typeof window !== 'undefined' && window.location.pathname.includes('/writer/')) { - console.log('🔍 STORAGE GET:', { key, hasValue: !!value, valuePreview: value?.substring(0, 50) }); - } - return value; - }, - setItem: (name: string, value: string) => { - const userId = getUserIdFromStorage(); - const key = `${name}-user-${userId}`; - if (typeof window !== 'undefined' && window.location.pathname.includes('/writer/')) { - console.log('💾 STORAGE SET:', { key, valuePreview: value?.substring(0, 50) }); - } - localStorage.setItem(key, value); - }, - removeItem: (name: string) => { - const userId = getUserIdFromStorage(); - const key = `${name}-user-${userId}`; - localStorage.removeItem(key); - }, +// Build storage key +const getStorageKey = (pathname: string): string => { + return `columns-${getUserId()}-${pathname}`; }; -// 30 days in milliseconds -const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000; - -export const useColumnVisibilityStore = create()( - persist( - (set, get) => ({ - pageColumns: {}, - _hasHydrated: false, - - setHasHydrated: (state: boolean) => { - set({ _hasHydrated: state }); - }, - - setPageColumns: (pathname: string, columnKeys: string[]) => { - if (pathname.includes('/writer/')) { - console.log('📝 setPageColumns:', { pathname, columns: columnKeys, timestamp: new Date().toISOString() }); - } - set((state) => ({ - pageColumns: { - ...state.pageColumns, - [pathname]: { - columns: columnKeys, - timestamp: Date.now(), - }, - }, - })); - }, - - getPageColumns: (pathname: string) => { - const preference = get().pageColumns[pathname]; - if (pathname.includes('/writer/')) { - console.log('📖 getPageColumns:', { pathname, hasPreference: !!preference, columns: preference?.columns }); - } - if (!preference) return []; - - // Check if preference has expired (older than 30 days) - const now = Date.now(); - if (now - preference.timestamp > THIRTY_DAYS_MS) { - // Remove expired preference - set((state) => { - const newPageColumns = { ...state.pageColumns }; - delete newPageColumns[pathname]; - return { pageColumns: newPageColumns }; - }); - return []; - } - - return preference.columns; - }, - - toggleColumn: (pathname: string, columnKey: string) => { - set((state) => { - const preference = state.pageColumns[pathname]; - const currentColumns = preference?.columns || []; - const newColumns = currentColumns.includes(columnKey) - ? currentColumns.filter((key) => key !== columnKey) - : [...currentColumns, columnKey]; - - return { - pageColumns: { - ...state.pageColumns, - [pathname]: { - columns: newColumns, - timestamp: Date.now(), - }, - }, - }; - }); - }, - - resetPageColumns: (pathname: string) => { - set((state) => { - const newPageColumns = { ...state.pageColumns }; - delete newPageColumns[pathname]; - return { pageColumns: newPageColumns }; - }); - }, - }), - { - name: 'igny8-column-visibility', - storage: createJSONStorage(() => userSpecificStorage), - partialize: (state) => ({ - pageColumns: state.pageColumns, - }), - onRehydrateStorage: () => (state) => { - if (state && typeof window !== 'undefined' && window.location.pathname.includes('/writer/')) { - console.log('💧 REHYDRATED:', { pageColumns: state.pageColumns }); - } - state?.setHasHydrated(true); - }, +export const useColumnVisibilityStore = create((set, get) => ({ + getVisibleColumns: (pathname: string) => { + try { + const key = getStorageKey(pathname); + const stored = localStorage.getItem(key); + if (stored) { + return JSON.parse(stored); + } + } catch (e) { + // ignore } - ) -); - + return []; + }, + + setVisibleColumns: (pathname: string, columns: string[]) => { + try { + const key = getStorageKey(pathname); + localStorage.setItem(key, JSON.stringify(columns)); + } catch (e) { + // ignore + } + }, +})); diff --git a/frontend/src/templates/TablePageTemplate.tsx b/frontend/src/templates/TablePageTemplate.tsx index 564dabbf..cc93335e 100644 --- a/frontend/src/templates/TablePageTemplate.tsx +++ b/frontend/src/templates/TablePageTemplate.tsx @@ -240,71 +240,22 @@ export default function TablePageTemplate({ const { setMetrics } = useHeaderMetrics(); const toast = useToast(); const { pageSize, setPageSize } = usePageSizeStore(); - const { setPageColumns, getPageColumns, _hasHydrated } = useColumnVisibilityStore(); + const { getVisibleColumns, setVisibleColumns: saveVisibleColumns } = useColumnVisibilityStore(); - // Column visibility state management with Zustand store - // Start with defaults, then update once hydration completes + // Column visibility - load from localStorage on mount, save on change const [visibleColumns, setVisibleColumns] = useState>(() => { - // Always start with defaults on initial render (before hydration) - return new Set( - columns - .filter(col => col.defaultVisible !== false) - .map(col => col.key) - ); + const saved = getVisibleColumns(location.pathname); + if (saved.length > 0) { + return new Set(saved); + } + // Default: all columns with defaultVisible !== false + return new Set(columns.filter(col => col.defaultVisible !== false).map(col => col.key)); }); - // Track if we've already loaded saved columns for this page to prevent loops - const hasLoadedSavedColumns = useRef(false); - - // Load saved columns from store after hydration completes (one-time per page) + // Save to localStorage whenever columns change useEffect(() => { - console.log('🔄 LOAD EFFECT:', { hasHydrated: _hasHydrated, hasLoaded: hasLoadedSavedColumns.current, pathname: location.pathname }); - if (!_hasHydrated || hasLoadedSavedColumns.current) return; - - // Mark as loaded FIRST to prevent save effect from firing during this update - hasLoadedSavedColumns.current = true; - console.log('✅ Marked hasLoaded = true'); - - const savedColumnKeys = getPageColumns(location.pathname); - console.log('📥 LOADING saved columns:', savedColumnKeys); - - if (savedColumnKeys && savedColumnKeys.length > 0) { - const savedSet = new Set(savedColumnKeys); - // Validate that all saved columns still exist in current columns config - const validColumns = columns.filter(col => savedSet.has(col.key)); - - if (validColumns.length > 0) { - // Add any new columns with defaultVisible !== false - const newColumns = columns - .filter(col => !savedSet.has(col.key) && col.defaultVisible !== false) - .map(col => col.key); - const mergedColumns = [...Array.from(validColumns.map(col => col.key)), ...newColumns]; - console.log('✅ Setting visible columns to:', mergedColumns); - setVisibleColumns(new Set(mergedColumns)); - return; - } - } - - console.log('⚠️ No valid saved columns, keeping defaults'); - }, [_hasHydrated, location.pathname]); - // NOTE: NOT including 'getPageColumns' or 'columns' - would cause unnecessary re-runs - - // Reset loaded flag when pathname changes (navigating to different page) - useEffect(() => { - hasLoadedSavedColumns.current = false; - }, [location.pathname]); - - // Save to store whenever visibleColumns changes (only after hydration to avoid saving defaults) - useEffect(() => { - console.log('💾 SAVE EFFECT:', { - hasHydrated: _hasHydrated, - hasLoaded: hasLoadedSavedColumns.current, - willSave: _hasHydrated && hasLoadedSavedColumns.current, - columns: Array.from(visibleColumns) - }); - if (!_hasHydrated || !hasLoadedSavedColumns.current) return; // Don't save until hydration completes AND saved columns have been loaded - setPageColumns(location.pathname, Array.from(visibleColumns)); - }, [_hasHydrated, visibleColumns, location.pathname, setPageColumns]); + saveVisibleColumns(location.pathname, Array.from(visibleColumns)); + }, [visibleColumns, location.pathname, saveVisibleColumns]); // Filter columns based on visibility const visibleColumnsList = useMemo(() => {