- 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>
293 lines
6.2 KiB
TypeScript
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,
|
|
}));
|
|
} |