diff --git a/src/components/business/CEODashboard/CEODashboard.tsx b/src/components/business/CEODashboard/CEODashboard.tsx index 6cd85e2d..0cd39c5e 100644 --- a/src/components/business/CEODashboard/CEODashboard.tsx +++ b/src/components/business/CEODashboard/CEODashboard.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useState, useCallback } from 'react'; +import { useState, useCallback, useEffect } from 'react'; import { Loader2, LayoutDashboard, Settings } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { PageLayout } from '@/components/organisms/PageLayout'; @@ -377,20 +377,19 @@ export function CEODashboard() { // 항목 설정 모달 상태 const [isSettingsModalOpen, setIsSettingsModalOpen] = useState(false); - const [dashboardSettings, setDashboardSettings] = useState(() => { - // localStorage에서 설정 불러오기 - if (typeof window !== 'undefined') { - const saved = localStorage.getItem('ceo-dashboard-settings'); - if (saved) { - try { - return JSON.parse(saved); - } catch { - return DEFAULT_DASHBOARD_SETTINGS; - } + const [dashboardSettings, setDashboardSettings] = useState(DEFAULT_DASHBOARD_SETTINGS); + + // 클라이언트에서만 localStorage에서 설정 불러오기 (hydration 에러 방지) + useEffect(() => { + const saved = localStorage.getItem('ceo-dashboard-settings'); + if (saved) { + try { + setDashboardSettings(JSON.parse(saved)); + } catch { + // 파싱 실패 시 기본값 유지 } } - return DEFAULT_DASHBOARD_SETTINGS; - }); + }, []); // 항목 설정 클릭 const handleSettingClick = useCallback(() => { diff --git a/src/components/outbound/ShipmentManagement/ShipmentList.tsx b/src/components/outbound/ShipmentManagement/ShipmentList.tsx index 8a2d07ee..bd1bfbd1 100644 --- a/src/components/outbound/ShipmentManagement/ShipmentList.tsx +++ b/src/components/outbound/ShipmentManagement/ShipmentList.tsx @@ -311,11 +311,16 @@ export function ShipmentList() { count: shipmentStats?.totalCount || 0, }; - const statusTabs: TabOption[] = Object.entries(statusStats).map(([key, stat]) => ({ - value: key, - label: stat.label, - count: stat.count, - })); + const statusTabs: TabOption[] = Object.entries(statusStats).map(([key, stat]) => { + // stat이 객체가 아니거나 label이 없는 경우 방어 + const label = typeof stat?.label === 'string' ? stat.label : key; + const count = typeof stat?.count === 'number' ? stat.count : 0; + return { + value: key, + label, + count, + }; + }); return [allTab, ...statusTabs]; }, [statusStats, shipmentStats?.totalCount]); diff --git a/src/layouts/AuthenticatedLayout.tsx b/src/layouts/AuthenticatedLayout.tsx index 9e5c9655..21010cd5 100644 --- a/src/layouts/AuthenticatedLayout.tsx +++ b/src/layouts/AuthenticatedLayout.tsx @@ -229,16 +229,15 @@ export default function AuthenticatedLayout({ children }: AuthenticatedLayoutPro setActiveMenu(result.menuId); // 부모 메뉴가 있으면 자동으로 확장 - if (result.parentId && !expandedMenus.includes(result.parentId)) { - setExpandedMenus(prev => [...prev, result.parentId!]); - } - } else { - // 대시보드 등 어떤 메뉴에도 속하지 않는 페이지일 경우 모든 메뉴 닫기 - if (expandedMenus.length > 0) { - setExpandedMenus([]); + if (result.parentId) { + setExpandedMenus(prev => + prev.includes(result.parentId!) ? prev : [...prev, result.parentId!] + ); } } - }, [pathname, menuItems, setActiveMenu, expandedMenus]); + // 대시보드 등 메뉴에 매칭되지 않아도 expandedMenus 유지 + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [pathname, menuItems, setActiveMenu]); const handleMenuClick = (menuId: string, path: string) => { setActiveMenu(menuId);