- claudedocs 폴더 구조 재정리: archive/sessions, guides/migration·mobile·universal-list, refactoring 분류 - 오래된 세션 컨텍스트/체크리스트 문서 정리 (아카이브 이동 또는 삭제) - AuthContext → authStore(Zustand) 전환 시작, RootProvider 간소화 - GenericCRUDDialog 공통 다이얼로그 컴포넌트 추가 - PermissionDialog 삭제 → GenericCRUDDialog로 대체 - RankDialog/TitleDialog GenericCRUDDialog 기반으로 리팩토링 - toast-utils.ts 삭제 (미사용) - fileDownload.ts 개선, excel-download.ts 정리 - menuStore/themeStore Zustand 셀렉터 최적화 - useColumnSettings/useTableColumnStore 기능 보강 - 세금계산서/견적/작업자화면/결재 등 소규모 개선 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
84 lines
2.4 KiB
TypeScript
84 lines
2.4 KiB
TypeScript
import { create } from 'zustand';
|
|
import { persist } from 'zustand/middleware';
|
|
import type { LucideIcon } from 'lucide-react';
|
|
|
|
// localStorage 저장용 (icon을 문자열로 저장)
|
|
export interface SerializableMenuItem {
|
|
id: string;
|
|
label: string;
|
|
iconName: string; // 문자열로 저장 (예: 'dashboard', 'folder')
|
|
path: string;
|
|
children?: SerializableMenuItem[];
|
|
}
|
|
|
|
// 실제 사용용 (icon을 컴포넌트로 사용)
|
|
export interface MenuItem {
|
|
id: string;
|
|
label: string;
|
|
icon: LucideIcon;
|
|
path: string;
|
|
component?: React.ComponentType;
|
|
children?: MenuItem[];
|
|
}
|
|
|
|
interface MenuState {
|
|
activeMenu: string;
|
|
menuItems: MenuItem[];
|
|
sidebarCollapsed: boolean;
|
|
_hasHydrated: boolean;
|
|
setActiveMenu: (menuId: string) => void;
|
|
setMenuItems: (items: MenuItem[]) => void;
|
|
toggleSidebar: () => void;
|
|
setSidebarCollapsed: (collapsed: boolean) => void;
|
|
setHasHydrated: (hydrated: boolean) => void;
|
|
}
|
|
|
|
export const useMenuStore = create<MenuState>()(
|
|
persist(
|
|
(set) => ({
|
|
activeMenu: 'dashboard',
|
|
menuItems: [],
|
|
sidebarCollapsed: false,
|
|
_hasHydrated: false,
|
|
|
|
setActiveMenu: (menuId: string) => set({ activeMenu: menuId }),
|
|
|
|
setMenuItems: (items: MenuItem[]) => set({ menuItems: items }),
|
|
|
|
toggleSidebar: () => set((state) => ({ sidebarCollapsed: !state.sidebarCollapsed })),
|
|
|
|
setSidebarCollapsed: (collapsed: boolean) => set({ sidebarCollapsed: collapsed }),
|
|
|
|
setHasHydrated: (hydrated: boolean) => set({ _hasHydrated: hydrated }),
|
|
}),
|
|
{
|
|
name: 'sam-menu',
|
|
// menuItems는 함수(icon)를 포함하므로 localStorage에서 제외
|
|
partialize: (state) => ({
|
|
activeMenu: state.activeMenu,
|
|
sidebarCollapsed: state.sidebarCollapsed,
|
|
}),
|
|
onRehydrateStorage: () => (state) => {
|
|
state?.setHasHydrated(true);
|
|
},
|
|
}
|
|
)
|
|
);
|
|
|
|
// ===== 셀렉터 훅 =====
|
|
|
|
/** 사이드바 접힘 상태만 구독 */
|
|
export const useSidebarCollapsed = () =>
|
|
useMenuStore((state) => state.sidebarCollapsed);
|
|
|
|
/** 활성 메뉴 ID만 구독 */
|
|
export const useActiveMenu = () =>
|
|
useMenuStore((state) => state.activeMenu);
|
|
|
|
/** 메뉴 아이템 목록만 구독 */
|
|
export const useMenuItems = () =>
|
|
useMenuStore((state) => state.menuItems);
|
|
|
|
/** 하이드레이션 완료 여부만 구독 */
|
|
export const useMenuHydrated = () =>
|
|
useMenuStore((state) => state._hasHydrated); |