Files
sam-react-prod/src/lib/utils/menuTransform.ts
유병철 a38996b751 refactor(WEB): V2 파일 통합, store 구조 정리 및 대시보드 개선
- V2 컴포넌트를 원본에 통합 후 V2 파일 삭제 (InspectionModal, BillDetail, ContractDocumentModal, LaborDetailClient, PricingDetailClient, QuoteRegistration)
- store → stores 디렉토리 이동 및 favoritesStore 추가
- dashboard_type3~5 추가 및 기존 대시보드 차트/훅 분리
- Sidebar 리팩토링 및 HeaderFavoritesBar 추가
- DashboardSwitcher 컴포넌트 추가
- 백업 파일(.v1-backup) 및 불필요 코드 정리
- InspectionPreviewModal 레이아웃 개선

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 15:09:51 +09:00

293 lines
6.2 KiB
TypeScript

import type { MenuItem, SerializableMenuItem } from '@/stores/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,
};
// 역방향 아이콘 맵 (LucideIcon → string name)
const reverseIconMap = new Map<LucideIcon, string>();
for (const [name, icon] of Object.entries(iconMap)) {
// 첫 번째 매핑만 저장 (중복 아이콘 방지)
if (!reverseIconMap.has(icon)) {
reverseIconMap.set(icon, name);
}
}
/**
* LucideIcon 컴포넌트에서 문자열 이름을 조회
*/
export function getIconName(icon: LucideIcon): string {
return reverseIconMap.get(icon) || 'folder';
}
// 기본 즐겨찾기 항목 (최초 사용자용)
import type { FavoriteItem } from '@/stores/favoritesStore';
export const DEFAULT_FAVORITES: FavoriteItem[] = [
{
id: 'default-comprehensive-analysis',
label: '종합분석',
iconName: 'bar-chart-3',
path: '/reports/comprehensive-analysis',
addedAt: 0,
},
{
id: 'default-qms',
label: '품질인정심사',
iconName: 'award',
path: '/quality/qms',
addedAt: 1,
},
];
// 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,
}));
}