- 사이드바 메뉴 3depth 이상 지원 (재귀 컴포넌트) - menuTransform.ts: buildChildrenRecursive 함수 추가 - AuthenticatedLayout.tsx: findMenuRecursive + ancestorIds 배열로 경로 매칭 - Sidebar.tsx: depth별 스타일 (1depth: 아이콘+굵은텍스트, 2depth: 작은아이콘, 3depth: dot+작은텍스트) - CEO 대시보드 상세 모달 및 카드 관리 개선 - 폴더블 기기 레이아웃 가이드 문서 추가 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
257 lines
5.3 KiB
TypeScript
257 lines
5.3 KiB
TypeScript
import type { MenuItem, SerializableMenuItem } from '@/store/menuStore';
|
|
import {
|
|
LayoutDashboard,
|
|
Folder,
|
|
Settings,
|
|
Package,
|
|
Building2,
|
|
FileText,
|
|
Users,
|
|
Lock,
|
|
Building,
|
|
ShoppingCart,
|
|
Receipt,
|
|
Factory,
|
|
DollarSign,
|
|
LucideIcon,
|
|
// 신규 아이콘 추가
|
|
FileCheck,
|
|
FileEdit,
|
|
Inbox,
|
|
Eye,
|
|
LayoutList,
|
|
User,
|
|
Clock,
|
|
CalendarCheck,
|
|
Calendar,
|
|
CalendarDays,
|
|
BarChart3,
|
|
PieChart,
|
|
Headphones,
|
|
Megaphone,
|
|
HelpCircle,
|
|
MessageCircle,
|
|
Bell,
|
|
Award,
|
|
TrendingUp,
|
|
TrendingDown,
|
|
AlertCircle,
|
|
History,
|
|
ArrowDownCircle,
|
|
ArrowUpCircle,
|
|
Landmark,
|
|
CreditCard,
|
|
Shield,
|
|
Database,
|
|
Sliders,
|
|
Box,
|
|
Archive,
|
|
Wrench,
|
|
ClipboardCheck,
|
|
Code,
|
|
Calculator,
|
|
Palette,
|
|
MapPin,
|
|
CheckSquare,
|
|
CheckCircle,
|
|
Warehouse,
|
|
Layers,
|
|
Truck,
|
|
XCircle,
|
|
Car,
|
|
Activity,
|
|
Server,
|
|
Layout,
|
|
} from 'lucide-react';
|
|
|
|
// 아이콘 매핑 (string → component)
|
|
export const iconMap: Record<string, LucideIcon> = {
|
|
// 기본 아이콘
|
|
dashboard: LayoutDashboard,
|
|
folder: Folder,
|
|
settings: Settings,
|
|
inventory: Package,
|
|
business: Building2,
|
|
assignment: FileText,
|
|
people: Users,
|
|
lock: Lock,
|
|
corporate_fare: Building,
|
|
|
|
// 판매관리 관련 아이콘
|
|
shopping_cart: ShoppingCart,
|
|
sales: ShoppingCart,
|
|
receipt: Receipt,
|
|
quote: Receipt,
|
|
customers: Building2,
|
|
|
|
// 생산관리 관련 아이콘
|
|
factory: Factory,
|
|
production: Factory,
|
|
|
|
// 단가관리 관련 아이콘
|
|
dollar: DollarSign,
|
|
pricing: DollarSign,
|
|
|
|
// ===== 신규 아이콘 (kebab-case) =====
|
|
// 대시보드
|
|
'layout-dashboard': LayoutDashboard,
|
|
|
|
// 결재관리
|
|
'file-check': FileCheck,
|
|
'file-edit': FileEdit,
|
|
inbox: Inbox,
|
|
eye: Eye,
|
|
|
|
// 게시판
|
|
'layout-list': LayoutList,
|
|
|
|
// 품목관리
|
|
package: Package,
|
|
database: Database,
|
|
|
|
// 인사관리
|
|
users: Users,
|
|
user: User,
|
|
building: Building,
|
|
clock: Clock,
|
|
'calendar-check': CalendarCheck,
|
|
calendar: Calendar,
|
|
'calendar-days': CalendarDays,
|
|
'dollar-sign': DollarSign,
|
|
|
|
// 리포트
|
|
'bar-chart-3': BarChart3,
|
|
'pie-chart': PieChart,
|
|
|
|
// 고객센터
|
|
headphones: Headphones,
|
|
megaphone: Megaphone,
|
|
'help-circle': HelpCircle,
|
|
'message-circle': MessageCircle,
|
|
|
|
// 설정
|
|
shield: Shield,
|
|
award: Award,
|
|
bell: Bell,
|
|
layout: Layout,
|
|
|
|
// 회계관리
|
|
'building-2': Building2,
|
|
'file-text': FileText,
|
|
'trending-up': TrendingUp,
|
|
'trending-down': TrendingDown,
|
|
'alert-circle': AlertCircle,
|
|
history: History,
|
|
'arrow-down-circle': ArrowDownCircle,
|
|
'arrow-up-circle': ArrowUpCircle,
|
|
landmark: Landmark,
|
|
'credit-card': CreditCard,
|
|
|
|
// 기준정보
|
|
sliders: Sliders,
|
|
box: Box,
|
|
archive: Archive,
|
|
wrench: Wrench,
|
|
'clipboard-check': ClipboardCheck,
|
|
code: Code,
|
|
calculator: Calculator,
|
|
palette: Palette,
|
|
|
|
// 영업/구매
|
|
'map-pin': MapPin,
|
|
'shopping-cart': ShoppingCart,
|
|
briefcase: ShoppingCart,
|
|
|
|
// 품질관리
|
|
'check-square': CheckSquare,
|
|
'check-circle': CheckCircle,
|
|
|
|
// 자재관리
|
|
warehouse: Warehouse,
|
|
layers: Layers,
|
|
truck: Truck,
|
|
'x-circle': XCircle,
|
|
|
|
// 차량관리
|
|
car: Car,
|
|
|
|
// 시스템
|
|
activity: Activity,
|
|
server: Server,
|
|
};
|
|
|
|
// API 메뉴 데이터 타입
|
|
interface ApiMenu {
|
|
id: number;
|
|
parent_id: number | null;
|
|
name: string;
|
|
url: string;
|
|
icon: string;
|
|
sort_order: number;
|
|
is_external: number;
|
|
external_url: string | null;
|
|
}
|
|
|
|
/**
|
|
* 재귀적으로 자식 메뉴를 찾아서 트리 구조로 변환 (3depth 이상 지원)
|
|
*/
|
|
function buildChildrenRecursive(parentId: number, allMenus: ApiMenu[]): SerializableMenuItem[] {
|
|
const children = allMenus
|
|
.filter((menu) => menu.parent_id === parentId)
|
|
.sort((a, b) => a.sort_order - b.sort_order)
|
|
.map((menu) => {
|
|
const grandChildren = buildChildrenRecursive(menu.id, allMenus);
|
|
return {
|
|
id: menu.id.toString(),
|
|
label: menu.name,
|
|
iconName: menu.icon || 'folder',
|
|
path: menu.url || '#',
|
|
children: grandChildren.length > 0 ? grandChildren : undefined,
|
|
};
|
|
});
|
|
|
|
return children;
|
|
}
|
|
|
|
/**
|
|
* API 메뉴 데이터를 SerializableMenuItem 구조로 변환 (localStorage 저장용)
|
|
* 3depth 이상의 메뉴 구조 지원
|
|
*/
|
|
export function transformApiMenusToMenuItems(apiMenus: ApiMenu[]): SerializableMenuItem[] {
|
|
if (!apiMenus || !Array.isArray(apiMenus)) {
|
|
return [];
|
|
}
|
|
|
|
// parent_id가 null인 최상위 메뉴만 추출
|
|
const rootMenus = apiMenus
|
|
.filter((menu) => menu.parent_id === null)
|
|
.sort((a, b) => a.sort_order - b.sort_order);
|
|
|
|
// 각 루트 메뉴에 대해 재귀적으로 자식 메뉴 찾기
|
|
const menuItems: SerializableMenuItem[] = rootMenus.map((rootMenu) => {
|
|
const children = buildChildrenRecursive(rootMenu.id, apiMenus);
|
|
|
|
return {
|
|
id: rootMenu.id.toString(),
|
|
label: rootMenu.name,
|
|
iconName: rootMenu.icon || 'folder',
|
|
path: rootMenu.url || '#',
|
|
children: children.length > 0 ? children : undefined,
|
|
};
|
|
});
|
|
|
|
return menuItems;
|
|
}
|
|
|
|
/**
|
|
* SerializableMenuItem을 MenuItem으로 변환 (icon 문자열 → 컴포넌트)
|
|
*/
|
|
export function deserializeMenuItems(serializedMenus: SerializableMenuItem[]): MenuItem[] {
|
|
return serializedMenus.map((item) => ({
|
|
id: item.id,
|
|
label: item.label,
|
|
icon: iconMap[item.iconName] || Folder,
|
|
path: item.path,
|
|
children: item.children ? deserializeMenuItems(item.children) : undefined,
|
|
}));
|
|
} |