'use client'; import { createContext, useContext, useState, useEffect, useMemo, ReactNode } from 'react'; import { useAuth } from './AuthContext'; import { TenantAwareCache } from '@/lib/cache'; import { itemMasterApi } from '@/lib/api/item-master'; import { getErrorMessage } from '@/lib/api/error-handler'; import { transformPageResponse, transformSectionResponse } from '@/lib/api/transformers'; import type { ItemPageRequest } from '@/types/item-master-api'; // ===== Type Definitions ===== // 전개도 상세 정보 export interface BendingDetail { id: string; no: number; // 번호 input: number; // 입력 elongation: number; // 연신율 (기본값 -1) calculated: number; // 연신율 계산 후 sum: number; // 합계 shaded: boolean; // 음영 여부 aAngle?: number; // A각 } // 부품구성표(BOM, Bill of Materials) - 자재 명세서 export interface BOMLine { id: string; childItemCode: string; // 구성 품목 코드 childItemName: string; // 구성 품목명 quantity: number; // 기준 수량 unit: string; // 단위 unitPrice?: number; // 단가 quantityFormula?: string; // 수량 계산식 (예: "W * 2", "H + 100") note?: string; // 비고 // 절곡품 관련 (하위 절곡 부품용) isBending?: boolean; bendingDiagram?: string; // 전개도 이미지 URL bendingDetails?: BendingDetail[]; // 전개도 상세 데이터 } // 규격 마스터 (원자재/부자재용) export interface SpecificationMaster { id: string; specificationCode: string; // 규격 코드 (예: 1.6T x 1219 x 2438) itemType: 'RM' | 'SM'; // 원자재 | 부자재 itemName?: string; // 품목명 (예: SPHC-SD, SPCC-SD) - 품목명별 규격 필터링용 fieldCount: '1' | '2' | '3'; // 너비 입력 개수 thickness: string; // 두께 widthA: string; // 너비A widthB?: string; // 너비B widthC?: string; // 너비C length: string; // 길이 description?: string; // 설명 isActive: boolean; // 활성 여부 createdAt?: string; updatedAt?: string; } // 원자재/부자재 품목명 마스터 export interface MaterialItemName { id: string; itemType: 'RM' | 'SM'; // 원자재 | 부자재 itemName: string; // 품목명 (예: "SPHC-SD", "STS430") category?: string; // 분류 (예: "냉연", "열연", "스테인리스") description?: string; // 설명 isActive: boolean; // 활성 여부 createdAt: string; updatedAt?: string; } // 품목 수정 이력 export interface ItemRevision { revisionNumber: number; // 수정 차수 (1차, 2차, 3차...) revisionDate: string; // 수정일 revisionBy: string; // 수정자 revisionReason?: string; // 수정 사유 previousData: any; // 이전 버전의 전체 데이터 } // 품목 마스터 export interface ItemMaster { id: string; itemCode: string; itemName: string; itemType: 'FG' | 'PT' | 'SM' | 'RM' | 'CS'; // 제품, 부품, 부자재, 원자재, 소모품 productCategory?: 'SCREEN' | 'STEEL'; // 제품 카테고리 (스크린/철재) partType?: 'ASSEMBLY' | 'BENDING' | 'PURCHASED'; // 부품 유형 (조립/절곡/구매) partUsage?: 'GUIDE_RAIL' | 'BOTTOM_FINISH' | 'CASE' | 'DOOR' | 'BRACKET' | 'GENERAL'; // 부품 용도 unit: string; category1?: string; category2?: string; category3?: string; specification?: string; isVariableSize?: boolean; isActive?: boolean; // 품목 활성/비활성 (제품/부품/원자재/부자재만 사용) lotAbbreviation?: string; // 로트 약자 (제품만 사용) purchasePrice?: number; marginRate?: number; processingCost?: number; laborCost?: number; installCost?: number; salesPrice?: number; safetyStock?: number; leadTime?: number; bom?: BOMLine[]; // 부품구성표(BOM) - 자재 명세서 bomCategories?: string[]; // 견적산출용 샘플 제품의 BOM 카테고리 (예: ['motor', 'guide-rail']) // 인정 정보 certificationNumber?: string; // 인정번호 certificationStartDate?: string; // 인정 유효기간 시작일 certificationEndDate?: string; // 인정 유효기간 종료일 specificationFile?: string; // 시방서 파일 (Base64 또는 URL) specificationFileName?: string; // 시방서 파일명 certificationFile?: string; // 인정서 파일 (Base64 또는 URL) certificationFileName?: string; // 인정서 파일명 note?: string; // 비고 (제품만 사용) // 조립 부품 관련 필드 installationType?: string; // 설치 유형 (wall: 벽면형, side: 측면형, steel: 스틸, iron: 철재) assemblyType?: string; // 종류 (M, T, C, D, S, U 등) sideSpecWidth?: string; // 측면 규격 가로 (mm) sideSpecHeight?: string; // 측면 규격 세로 (mm) assemblyLength?: string; // 길이 (2438, 3000, 3500, 4000, 4300 등) // 가이드레일 관련 필드 guideRailModelType?: string; // 가이드레일 모델 유형 guideRailModel?: string; // 가이드레일 모델 // 절곡품 관련 (부품 유형이 BENDING인 경우) bendingDiagram?: string; // 전개도 이미지 URL bendingDetails?: BendingDetail[]; // 전개도 상세 데이터 material?: string; // 재질 (EGI 1.55T, SUS 1.2T 등) length?: string; // 길이/목함 (mm) // 버전 관리 currentRevision: number; // 현재 차수 (0 = 최초, 1 = 1차 수정...) revisions?: ItemRevision[]; // 수정 이력 isFinal: boolean; // 최종 확정 여부 finalizedDate?: string; // 최종 확정일 finalizedBy?: string; // 최종 확정자 createdAt: string; } // 품목 기준정보 관리 (Master Data) export interface ItemCategory { id: string; categoryType: 'PRODUCT' | 'PART' | 'MATERIAL' | 'SUB_MATERIAL'; // 품목 구분 category1: string; // 대분류 category2?: string; // 중분류 category3?: string; // 소분류 code?: string; // 코드 (자동생성 또는 수동입력) description?: string; isActive: boolean; createdAt: string; updatedAt?: string; } export interface ItemUnit { id: string; unitCode: string; // 단위 코드 (EA, SET, M, KG, L 등) unitName: string; // 단위명 description?: string; isActive: boolean; createdAt: string; updatedAt?: string; } export interface ItemMaterial { id: string; materialCode: string; // 재질 코드 materialName: string; // 재질명 (EGI 1.55T, SUS 1.2T 등) materialType: 'STEEL' | 'ALUMINUM' | 'PLASTIC' | 'OTHER'; // 재질 유형 thickness?: string; // 두께 (1.2T, 1.6T 등) description?: string; isActive: boolean; createdAt: string; updatedAt?: string; } export interface SurfaceTreatment { id: string; treatmentCode: string; // 처리 코드 treatmentName: string; // 처리명 (무도장, 파우더도장, 아노다이징 등) treatmentType: 'PAINTING' | 'COATING' | 'PLATING' | 'NONE'; // 처리 유형 description?: string; isActive: boolean; createdAt: string; updatedAt?: string; } export interface PartTypeOption { id: string; partType: 'ASSEMBLY' | 'BENDING' | 'PURCHASED'; // 부품 유형 optionCode: string; // 옵션 코드 optionName: string; // 옵션명 description?: string; isActive: boolean; createdAt: string; updatedAt?: string; } export interface PartUsageOption { id: string; usageCode: string; // 용도 코드 usageName: string; // 용도명 (가이드레일, 하단마감재, 케이스 등) description?: string; isActive: boolean; createdAt: string; updatedAt?: string; } export interface GuideRailOption { id: string; optionType: 'MODEL_TYPE' | 'MODEL' | 'CERTIFICATION' | 'SHAPE' | 'FINISH' | 'LENGTH'; // 옵션 유형 optionCode: string; // 옵션 코드 optionName: string; // 옵션명 parentOption?: string; // 상위 옵션 (종속 관계) description?: string; isActive: boolean; createdAt: string; updatedAt?: string; } // ===== 품목기준관리 계층구조 ===== // 항목 속성 export interface ItemFieldProperty { id?: string; // 속성 ID (properties 배열에서 사용) key?: string; // 속성 키 (properties 배열에서 사용) label?: string; // 속성 라벨 (properties 배열에서 사용) type?: 'textbox' | 'dropdown' | 'checkbox' | 'number' | 'date' | 'textarea'; // 속성 타입 (properties 배열에서 사용) inputType: 'textbox' | 'dropdown' | 'checkbox' | 'number' | 'date' | 'textarea' | 'section'; // 입력방식 required: boolean; // 필수 여부 row: number; // 행 위치 col: number; // 열 위치 options?: string[]; // 드롭다운 옵션 (입력방식이 dropdown일 경우) defaultValue?: string; // 기본값 placeholder?: string; // 플레이스홀더 multiColumn?: boolean; // 다중 컬럼 사용 여부 columnCount?: number; // 컬럼 개수 columnNames?: string[]; // 각 컬럼의 이름 } // 항목 마스터 (재사용 가능한 항목 템플릿) - MasterFieldResponse와 정확히 일치 export interface ItemMasterField { id: number; tenant_id: number; field_name: string; field_type: 'textbox' | 'number' | 'dropdown' | 'checkbox' | 'date' | 'textarea'; // API와 동일 category: string | null; description: string | null; is_common: boolean; // 공통 필드 여부 default_value: string | null; // 기본값 options: Array<{ label: string; value: string }> | null; // dropdown 옵션 validation_rules: Record | null; // 검증 규칙 properties: Record | null; // 추가 속성 created_by: number | null; updated_by: number | null; created_at: string; updated_at: string; } // 조건부 표시 설정 export interface FieldDisplayCondition { targetType: 'field' | 'section'; // 조건 대상 타입 // 일반항목 조건 (여러 개 가능) fieldConditions?: Array<{ fieldKey: string; // 조건이 되는 필드의 키 expectedValue: string; // 예상되는 값 }>; // 섹션 조건 (여러 개 가능) sectionIds?: string[]; // 표시할 섹션 ID 배열 } // 항목 (Field) - API 응답 구조에 맞춰 수정 export interface ItemField { id: number; // 서버 생성 ID (string → number) tenant_id?: number; // 백엔드에서 자동 추가 section_id: number; // 외래키 - 섹션 ID master_field_id?: number | null; // 마스터 항목 ID (마스터에서 가져온 경우) field_name: string; // 항목명 (name → field_name) field_type: 'textbox' | 'number' | 'dropdown' | 'checkbox' | 'date' | 'textarea'; // 필드 타입 order_no: number; // 항목 순서 (order → order_no, required) is_required: boolean; // 필수 여부 placeholder?: string | null; // 플레이스홀더 default_value?: string | null; // 기본값 display_condition?: Record | null; // 조건부 표시 설정 (displayCondition → display_condition) validation_rules?: Record | null; // 검증 규칙 options?: Array<{ label: string; value: string }> | null; // dropdown 옵션 properties?: Record | null; // 추가 속성 created_by?: number | null; // 생성자 ID 추가 updated_by?: number | null; // 수정자 ID 추가 created_at: string; // 생성일 (camelCase → snake_case) updated_at: string; // 수정일 추가 } // BOM 아이템 타입 - API 응답 구조에 맞춰 수정 export interface BOMItem { id: number; // 서버 생성 ID (string → number) tenant_id?: number; // 백엔드에서 자동 추가 section_id: number; // 외래키 - 섹션 ID item_code?: string | null; // 품목 코드 (itemCode → item_code, optional) item_name: string; // 품목명 (itemName → item_name) quantity: number; // 수량 unit?: string | null; // 단위 (optional) unit_price?: number | null; // 단가 추가 total_price?: number | null; // 총액 추가 spec?: string | null; // 규격/사양 추가 note?: string | null; // 비고 (optional) created_by?: number | null; // 생성자 ID 추가 updated_by?: number | null; // 수정자 ID 추가 created_at: string; // 생성일 (createdAt → created_at) updated_at: string; // 수정일 추가 } // 섹션 (Section) - API 응답 구조에 맞춰 수정 export interface ItemSection { id: number; // 서버 생성 ID (string → number) tenant_id?: number; // 백엔드에서 자동 추가 page_id: number; // 외래키 - 페이지 ID section_template_id?: number | null; // 섹션 템플릿 ID (템플릿 기반 섹션인 경우) section_name: string; // 섹션 제목 (title → section_name) section_type: 'BASIC' | 'BOM' | 'CUSTOM'; // 섹션 타입 (type → section_type, 값 변경) description?: string | null; // 설명 order_no: number; // 섹션 순서 (order → order_no) is_collapsible: boolean; // 접기/펼치기 가능 여부 (camelCase → snake_case) is_default_open: boolean; // 기본 열림 상태 (isCollapsed → is_default_open, 의미 반대) created_by?: number | null; // 생성자 ID 추가 updated_by?: number | null; // 수정자 ID 추가 created_at: string; // 생성일 (camelCase → snake_case) updated_at: string; // 수정일 추가 fields?: ItemField[]; // 섹션에 포함된 항목들 (optional로 변경) bomItems?: BOMItem[]; // BOM 타입일 경우 BOM 품목 목록 } // 페이지 (Page) - API 응답 구조에 맞춰 수정 export interface ItemPage { id: number; // 서버 생성 ID (string → number) tenant_id?: number; // 백엔드에서 자동 추가 page_name: string; // 페이지명 (camelCase → snake_case) item_type: 'FG' | 'PT' | 'SM' | 'RM' | 'CS'; // 품목유형 description?: string | null; // 설명 추가 absolute_path: string; // 절대경로 (camelCase → snake_case) is_active: boolean; // 사용 여부 (camelCase → snake_case) order_no: number; // 순서 번호 추가 created_by?: number | null; // 생성자 ID 추가 updated_by?: number | null; // 수정자 ID 추가 created_at: string; // 생성일 (camelCase → snake_case) updated_at: string; // 수정일 (camelCase → snake_case) sections: ItemSection[]; // 페이지에 포함된 섹션들 (Nested) } // 템플릿 필드 (로컬 관리용 - API에서 제공하지 않음) export interface TemplateField { id: string; name: string; fieldKey: string; property: { inputType: 'textbox' | 'number' | 'dropdown' | 'checkbox' | 'date' | 'textarea'; required: boolean; options?: string[]; multiColumn?: boolean; columnCount?: number; columnNames?: string[]; }; description?: string; } // 섹션 템플릿 (재사용 가능한 섹션) - Transformer 출력과 UI 요구사항에 맞춤 export interface SectionTemplate { id: number; tenant_id: number; template_name: string; // transformer가 title → template_name으로 변환 section_type: 'BASIC' | 'BOM' | 'CUSTOM'; // transformer가 type → section_type으로 변환 description: string | null; default_fields: TemplateField[] | null; // 기본 필드 (로컬 관리) category?: string[]; // 적용 카테고리 (로컬 관리) fields?: TemplateField[]; // 템플릿에 포함된 필드 (로컬 관리) bomItems?: BOMItem[]; // BOM 타입일 경우 BOM 품목 (로컬 관리) created_by: number | null; updated_by: number | null; created_at: string; updated_at: string; } // ===== Context Type ===== interface ItemMasterContextType { // 품목 마스터 데이터 itemMasters: ItemMaster[]; addItemMaster: (item: ItemMaster) => void; updateItemMaster: (id: string, updates: Partial) => void; deleteItemMaster: (id: string) => void; // 규격 마스터 데이터 (원자재/부자재) specificationMasters: SpecificationMaster[]; addSpecificationMaster: (spec: SpecificationMaster) => void; updateSpecificationMaster: (id: string, updates: Partial) => void; deleteSpecificationMaster: (id: string) => void; // 원자재/부자재 품목명 마스터 데이터 materialItemNames: MaterialItemName[]; addMaterialItemName: (item: MaterialItemName) => void; updateMaterialItemName: (id: string, updates: Partial) => void; deleteMaterialItemName: (id: string) => void; // 품목기준관리 - 마스터 항목 itemMasterFields: ItemMasterField[]; loadItemMasterFields: (fields: ItemMasterField[]) => void; // 초기 데이터 로딩용 (API 호출 없음) addItemMasterField: (field: Omit) => Promise; updateItemMasterField: (id: number, updates: Partial) => Promise; deleteItemMasterField: (id: number) => Promise; // 품목기준관리 - 섹션 템플릿 sectionTemplates: SectionTemplate[]; loadSectionTemplates: (templates: SectionTemplate[]) => void; // 초기 데이터 로딩용 (API 호출 없음) addSectionTemplate: (template: Omit) => Promise; updateSectionTemplate: (id: number, updates: Partial) => Promise; deleteSectionTemplate: (id: number) => Promise; // 품목기준관리 계층구조 itemPages: ItemPage[]; loadItemPages: (pages: ItemPage[]) => void; // 초기 데이터 로딩용 (API 호출 없음) addItemPage: (page: Omit) => Promise; updateItemPage: (id: number, updates: Partial) => Promise; deleteItemPage: (id: number) => Promise; reorderPages: (newOrder: Array<{ id: number; order_no: number }>) => Promise; addSectionToPage: (pageId: number, sectionData: Omit) => Promise; updateSection: (sectionId: number, updates: Partial) => Promise; deleteSection: (sectionId: number) => Promise; reorderSections: (pageId: number, sectionIds: number[]) => Promise; addFieldToSection: (sectionId: number, fieldData: Omit) => Promise; updateField: (fieldId: number, updates: Partial) => Promise; deleteField: (fieldId: number) => Promise; reorderFields: (sectionId: number, fieldIds: number[]) => Promise; // BOM 관리 addBOMItem: (sectionId: number, bomData: Omit) => Promise; updateBOMItem: (bomId: number, updates: Partial) => Promise; deleteBOMItem: (bomId: number) => Promise; // 품목 기준정보 관리 itemCategories: ItemCategory[]; itemUnits: ItemUnit[]; itemMaterials: ItemMaterial[]; surfaceTreatments: SurfaceTreatment[]; partTypeOptions: PartTypeOption[]; partUsageOptions: PartUsageOption[]; guideRailOptions: GuideRailOption[]; addItemCategory: (category: ItemCategory) => void; updateItemCategory: (id: string, updates: Partial) => void; deleteItemCategory: (id: string) => void; addItemUnit: (unit: ItemUnit) => void; updateItemUnit: (id: string, updates: Partial) => void; deleteItemUnit: (id: string) => void; addItemMaterial: (material: ItemMaterial) => void; updateItemMaterial: (id: string, updates: Partial) => void; deleteItemMaterial: (id: string) => void; addSurfaceTreatment: (treatment: SurfaceTreatment) => void; updateSurfaceTreatment: (id: string, updates: Partial) => void; deleteSurfaceTreatment: (id: string) => void; addPartTypeOption: (option: PartTypeOption) => void; updatePartTypeOption: (id: string, updates: Partial) => void; deletePartTypeOption: (id: string) => void; addPartUsageOption: (option: PartUsageOption) => void; updatePartUsageOption: (id: string, updates: Partial) => void; deletePartUsageOption: (id: string) => void; addGuideRailOption: (option: GuideRailOption) => void; updateGuideRailOption: (id: string, updates: Partial) => void; deleteGuideRailOption: (id: string) => void; // 캐시 및 데이터 초기화 clearCache: () => void; // TenantAwareCache 정리 resetAllData: () => void; // 모든 state 초기화 // 테넌트 정보 tenantId: number | undefined; // 현재 로그인한 사용자의 테넌트 ID } // Create context const ItemMasterContext = createContext(undefined); // Provider component export function ItemMasterProvider({ children }: { children: ReactNode }) { // ===== Initial Data ===== // ✅ Mock 데이터 주석 처리 - API에서 가져올 예정 const initialItemMasters: ItemMaster[] = []; /* ❌ Mock 데이터 주석 처리 - 백엔드 API 연동 후 제거 const initialSpecificationMasters: SpecificationMaster[] = [ // SPHC-SD 규격 { id: 'spec-001', specificationCode: '1219*1200', itemType: 'RM', itemName: 'SPHC-SD', fieldCount: '1', thickness: '1.6', widthA: '1219', length: '1200', isActive: true, createdAt: new Date().toISOString() }, { id: 'spec-002', specificationCode: '1219*2438', itemType: 'RM', itemName: 'SPHC-SD', fieldCount: '1', thickness: '1.6', widthA: '1219', length: '2438', isActive: true, createdAt: new Date().toISOString() }, { id: 'spec-003', specificationCode: '1219*3000', itemType: 'RM', itemName: 'SPHC-SD', fieldCount: '1', thickness: '1.6', widthA: '1219', length: '3000', isActive: true, createdAt: new Date().toISOString() }, // SPCC-SD 규격 { id: 'spec-004', specificationCode: '1219*1200', itemType: 'RM', itemName: 'SPCC-SD', fieldCount: '1', thickness: '1.6', widthA: '1219', length: '1200', isActive: true, createdAt: new Date().toISOString() }, { id: 'spec-005', specificationCode: '1219*2438', itemType: 'RM', itemName: 'SPCC-SD', fieldCount: '1', thickness: '1.6', widthA: '1219', length: '2438', isActive: true, createdAt: new Date().toISOString() }, { id: 'spec-006', specificationCode: '1219*3000', itemType: 'RM', itemName: 'SPCC-SD', fieldCount: '1', thickness: '1.6', widthA: '1219', length: '3000', isActive: true, createdAt: new Date().toISOString() }, // SECC 규격 { id: 'spec-007', specificationCode: '1219*1200', itemType: 'RM', itemName: 'SECC', fieldCount: '1', thickness: '1.6', widthA: '1219', length: '1200', isActive: true, createdAt: new Date().toISOString() }, { id: 'spec-008', specificationCode: '1219*2438', itemType: 'RM', itemName: 'SECC', fieldCount: '1', thickness: '1.6', widthA: '1219', length: '2438', isActive: true, createdAt: new Date().toISOString() }, { id: 'spec-009', specificationCode: '1219*3000', itemType: 'RM', itemName: 'SECC', fieldCount: '1', thickness: '1.6', widthA: '1219', length: '3000', isActive: true, createdAt: new Date().toISOString() }, // SGCC 규격 { id: 'spec-010', specificationCode: '1219*1200', itemType: 'RM', itemName: 'SGCC', fieldCount: '1', thickness: '1.6', widthA: '1219', length: '1200', isActive: true, createdAt: new Date().toISOString() }, { id: 'spec-011', specificationCode: '1219*2438', itemType: 'RM', itemName: 'SGCC', fieldCount: '1', thickness: '1.6', widthA: '1219', length: '2438', isActive: true, createdAt: new Date().toISOString() }, { id: 'spec-012', specificationCode: '1219*3000', itemType: 'RM', itemName: 'SGCC', fieldCount: '1', thickness: '1.6', widthA: '1219', length: '3000', isActive: true, createdAt: new Date().toISOString() }, // SPHD 규격 { id: 'spec-013', specificationCode: '1219*1200', itemType: 'RM', itemName: 'SPHD', fieldCount: '1', thickness: '2.0', widthA: '1219', length: '1200', isActive: true, createdAt: new Date().toISOString() }, { id: 'spec-014', specificationCode: '1219*2438', itemType: 'RM', itemName: 'SPHD', fieldCount: '1', thickness: '2.0', widthA: '1219', length: '2438', isActive: true, createdAt: new Date().toISOString() }, { id: 'spec-015', specificationCode: '1219*3000', itemType: 'RM', itemName: 'SPHD', fieldCount: '1', thickness: '2.0', widthA: '1219', length: '3000', isActive: true, createdAt: new Date().toISOString() }, // SS330 규격 { id: 'spec-016', specificationCode: '1219*1200', itemType: 'RM', itemName: 'SS330', fieldCount: '1', thickness: '2.3', widthA: '1219', length: '1200', isActive: true, createdAt: new Date().toISOString() }, { id: 'spec-017', specificationCode: '1219*2438', itemType: 'RM', itemName: 'SS330', fieldCount: '1', thickness: '2.3', widthA: '1219', length: '2438', isActive: true, createdAt: new Date().toISOString() }, { id: 'spec-018', specificationCode: '1219*3000', itemType: 'RM', itemName: 'SS330', fieldCount: '1', thickness: '2.3', widthA: '1219', length: '3000', isActive: true, createdAt: new Date().toISOString() }, // SS400 규격 { id: 'spec-019', specificationCode: '1219*1200', itemType: 'RM', itemName: 'SS400', fieldCount: '1', thickness: '2.3', widthA: '1219', length: '1200', isActive: true, createdAt: new Date().toISOString() }, { id: 'spec-020', specificationCode: '1219*2438', itemType: 'RM', itemName: 'SS400', fieldCount: '1', thickness: '2.3', widthA: '1219', length: '2438', isActive: true, createdAt: new Date().toISOString() }, { id: 'spec-021', specificationCode: '1219*3000', itemType: 'RM', itemName: 'SS400', fieldCount: '1', thickness: '2.3', widthA: '1219', length: '3000', isActive: true, createdAt: new Date().toISOString() }, // STS304 규격 { id: 'spec-022', specificationCode: '1219*1200', itemType: 'RM', itemName: 'STS304', fieldCount: '1', thickness: '1.5', widthA: '1219', length: '1200', isActive: true, createdAt: new Date().toISOString() }, { id: 'spec-023', specificationCode: '1219*2438', itemType: 'RM', itemName: 'STS304', fieldCount: '1', thickness: '1.5', widthA: '1219', length: '2438', isActive: true, createdAt: new Date().toISOString() }, { id: 'spec-024', specificationCode: '1219*3000', itemType: 'RM', itemName: 'STS304', fieldCount: '1', thickness: '1.5', widthA: '1219', length: '3000', isActive: true, createdAt: new Date().toISOString() }, // STS430 규격 { id: 'spec-025', specificationCode: '1219*1200', itemType: 'RM', itemName: 'STS430', fieldCount: '1', thickness: '1.5', widthA: '1219', length: '1200', isActive: true, createdAt: new Date().toISOString() }, { id: 'spec-026', specificationCode: '1219*2438', itemType: 'RM', itemName: 'STS430', fieldCount: '1', thickness: '1.5', widthA: '1219', length: '2438', isActive: true, createdAt: new Date().toISOString() }, { id: 'spec-027', specificationCode: '1219*3000', itemType: 'RM', itemName: 'STS430', fieldCount: '1', thickness: '1.5', widthA: '1219', length: '3000', isActive: true, createdAt: new Date().toISOString() }, ]; const initialMaterialItemNames: MaterialItemName[] = [ // 원자재 (RM) - 철판/강판류 { id: 'mat-001', itemType: 'RM', itemName: 'SPHC-SD', category: '냉연강판', description: '일반냉연강판', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-002', itemType: 'RM', itemName: 'SPCC-SD', category: '냉연강판', description: '냉연강판', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-003', itemType: 'RM', itemName: 'SECC', category: '냉연강판', description: '전기아연도금강판', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-004', itemType: 'RM', itemName: 'SGCC', category: '냉연강판', description: '용융아연도금강판', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-005', itemType: 'RM', itemName: 'SPHD', category: '열연강판', description: '열연강판', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-006', itemType: 'RM', itemName: 'SS330', category: '일반구조용강', description: '일반구조용강재', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-007', itemType: 'RM', itemName: 'SS400', category: '일반구조용강', description: '일반구조용강재', isActive: true, createdAt: new Date().toISOString() }, // 원자재 (RM) - 스테인리스 { id: 'mat-008', itemType: 'RM', itemName: 'STS304', category: '스테인리스', description: '스테인리스강판 304', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-009', itemType: 'RM', itemName: 'STS430', category: '스테인리스', description: '스테인리스강판 430', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-010', itemType: 'RM', itemName: 'STS316', category: '스테인리스', description: '스테인리스강판 316', isActive: true, createdAt: new Date().toISOString() }, // 원자재 (RM) - 알루미늄 { id: 'mat-011', itemType: 'RM', itemName: 'AL5052', category: '알루미늄', description: '알루미늄합금 5052', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-012', itemType: 'RM', itemName: 'AL6063', category: '알루미늄', description: '알루미늄합금 6063', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-013', itemType: 'RM', itemName: 'AL1050', category: '알루미늄', description: '알루미늄 1050', isActive: true, createdAt: new Date().toISOString() }, // 원자재 (RM) - 파이프/각관 { id: 'mat-014', itemType: 'RM', itemName: '각파이프', category: '파이프류', description: '각형강관', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-015', itemType: 'RM', itemName: '원형파이프', category: '파이프류', description: '원형강관', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-016', itemType: 'RM', itemName: 'STS파이프', category: '파이프류', description: '스테인리스파이프', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-017', itemType: 'RM', itemName: '앵글', category: '형강류', description: '등변앵글', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-018', itemType: 'RM', itemName: '채널', category: '형강류', description: 'C형강', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-019', itemType: 'RM', itemName: 'H빔', category: '형강류', description: 'H형강', isActive: true, createdAt: new Date().toISOString() }, // 원자재 (RM) - 기타 금속 { id: 'mat-020', itemType: 'RM', itemName: '황동판', category: '비철금속', description: '황동', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-021', itemType: 'RM', itemName: '구리판', category: '비철금속', description: '구리', isActive: true, createdAt: new Date().toISOString() }, // 부자재 (SM) - 체결부품 { id: 'mat-101', itemType: 'SM', itemName: '육각볼트', category: '체결부품', description: '육각머리볼트', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-102', itemType: 'SM', itemName: '육각너트', category: '체결부품', description: '육각너트', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-103', itemType: 'SM', itemName: '평와셔', category: '체결부품', description: '평와셔', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-104', itemType: 'SM', itemName: '스프링와셔', category: '체결부품', description: '스프링와셔', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-105', itemType: 'SM', itemName: '십자나사', category: '체결부품', description: '십자머리나사', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-106', itemType: 'SM', itemName: '접시나사', category: '체결부품', description: '접시머리나사', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-107', itemType: 'SM', itemName: '렌치볼트', category: '체결부품', description: '육각구멍붙이볼트', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-108', itemType: 'SM', itemName: '탭핑나사', category: '체결부품', description: '태핑나사', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-109', itemType: 'SM', itemName: '리벳', category: '체결부품', description: '리벳', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-110', itemType: 'SM', itemName: '앵커볼트', category: '체결부품', description: '앵커볼트', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-111', itemType: 'SM', itemName: '케미컬앵커', category: '체결부품', description: '케미컬앵커', isActive: true, createdAt: new Date().toISOString() }, // 부자재 (SM) - 용접/접착 { id: 'mat-201', itemType: 'SM', itemName: '용접봉', category: '용접재료', description: '용접봉', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-202', itemType: 'SM', itemName: '용접와이어', category: '용접재료', description: '용접와이어', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-203', itemType: 'SM', itemName: '실리콘', category: '접착/실링', description: '실리콘실란트', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-204', itemType: 'SM', itemName: '우레탄', category: '접착/실링', description: '우레탄실란트', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-205', itemType: 'SM', itemName: '에폭시', category: '접착/실링', description: '에폭시접착제', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-206', itemType: 'SM', itemName: '순간접착제', category: '접착/실링', description: '순간접착제', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-207', itemType: 'SM', itemName: '양면테이프', category: '접착/실링', description: '양면테이프', isActive: true, createdAt: new Date().toISOString() }, // 부자재 (SM) - 도장/표면처리 { id: 'mat-301', itemType: 'SM', itemName: '도료(백색)', category: '도장재료', description: '백색도료', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-302', itemType: 'SM', itemName: '도료(흑색)', category: '도장재료', description: '흑색도료', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-303', itemType: 'SM', itemName: '도료(회색)', category: '도장재료', description: '회색도료', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-304', itemType: 'SM', itemName: '신나', category: '도장재료', description: '희석제', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-305', itemType: 'SM', itemName: '프라이머', category: '도장재료', description: '방청프라이머', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-306', itemType: 'SM', itemName: '분체도료', category: '도장재료', description: '분체도료', isActive: true, createdAt: new Date().toISOString() }, // 부자재 (SM) - 연마/연삭 { id: 'mat-401', itemType: 'SM', itemName: '연마지', category: '연마재', description: '샌드페이퍼', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-402', itemType: 'SM', itemName: '연마석', category: '연마재', description: '연마석', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-403', itemType: 'SM', itemName: '절단석', category: '연마재', description: '절단석', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-404', itemType: 'SM', itemName: '와이어브러쉬', category: '연마재', description: '와이어브러쉬', isActive: true, createdAt: new Date().toISOString() }, // 부자재 (SM) - 기타 부자재 { id: 'mat-501', itemType: 'SM', itemName: '고무패킹', category: '패킹/가스켓', description: '고무패킹', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-502', itemType: 'SM', itemName: '우레탄폼', category: '충진재', description: '우레탄폼', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-503', itemType: 'SM', itemName: '보양테이프', category: '보양재', description: '마스킹테이프', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-504', itemType: 'SM', itemName: '보호필름', category: '보양재', description: '표면보호필름', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-505', itemType: 'SM', itemName: '윤활유', category: '유류', description: '윤활유', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-506', itemType: 'SM', itemName: '절삭유', category: '유류', description: '절삭유', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-507', itemType: 'SM', itemName: '방청유', category: '유류', description: '방청유', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-508', itemType: 'SM', itemName: '탈지제', category: '세정제', description: '탈지제', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-509', itemType: 'SM', itemName: '감기샤프트', category: '샤프트/축', description: '감기샤프트', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-510', itemType: 'SM', itemName: '개폐암', category: '구동부품', description: '개폐암', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-511', itemType: 'SM', itemName: '경첩', category: '체결부품', description: '경첩', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-512', itemType: 'SM', itemName: '고정핀', category: '체결부품', description: '고정핀', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-513', itemType: 'SM', itemName: '고정클램프', category: '체결부품', description: '고정클램프', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-514', itemType: 'SM', itemName: '구동모터', category: '모터/동력', description: '구동모터', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-515', itemType: 'SM', itemName: '기어모터', category: '모터/동력', description: '기어모터', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-516', itemType: 'SM', itemName: '너클', category: '구동부품', description: '너클', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-517', itemType: 'SM', itemName: '드럼', category: '구동부품', description: '드럼', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-518', itemType: 'SM', itemName: '롤러', category: '구동부품', description: '롤러', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-519', itemType: 'SM', itemName: '브라켓', category: '구조부품', description: '브라켓', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-520', itemType: 'SM', itemName: '베어링', category: '베어링류', description: '베어링', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-521', itemType: 'SM', itemName: '볼트', category: '체결부품', description: '볼트', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-522', itemType: 'SM', itemName: '부싱', category: '베어링류', description: '부싱', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-523', itemType: 'SM', itemName: '샤프트', category: '샤프트/축', description: '샤프트', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-524', itemType: 'SM', itemName: '스냅링', category: '체결부품', description: '스냅링', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-525', itemType: 'SM', itemName: '스프링', category: '스프링류', description: '스프링', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-526', itemType: 'SM', itemName: '스프로킷', category: '구동부품', description: '스프로킷', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-527', itemType: 'SM', itemName: '스프링와셔', category: '체결부품', description: '스프링와셔', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-528', itemType: 'SM', itemName: '실', category: '패킹/가스켓', description: '실', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-529', itemType: 'SM', itemName: '안전키', category: '체결부품', description: '안전키', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-530', itemType: 'SM', itemName: '연결핀', category: '체결부품', description: '연결핀', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-531', itemType: 'SM', itemName: '와셔', category: '체결부품', description: '와셔', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-532', itemType: 'SM', itemName: '와이어로프', category: '와이어/체인', description: '와이어로프', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-533', itemType: 'SM', itemName: '체인', category: '와이어/체인', description: '체인', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-534', itemType: 'SM', itemName: '축', category: '샤프트/축', description: '축', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-535', itemType: 'SM', itemName: '커플링', category: '구동부품', description: '커플링', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-536', itemType: 'SM', itemName: '턴버클', category: '체결부품', description: '턴버클', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-537', itemType: 'SM', itemName: '플렌지', category: '구조부품', description: '플렌지', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-538', itemType: 'SM', itemName: '풀리', category: '구동부품', description: '풀리', isActive: true, createdAt: new Date().toISOString() }, { id: 'mat-539', itemType: 'SM', itemName: '핀', category: '체결부품', description: '핀', isActive: true, createdAt: new Date().toISOString() }, // 원자재 (RM) - 추가 품목 { id: 'mat-022', itemType: 'RM', itemName: 'EGI0.8T', category: '냉연강판', description: '전기아연도금강판 0.8T', isActive: true, createdAt: new Date().toISOString() }, ]; const initialItemCategories: ItemCategory[] = [ { id: 'CAT-001', categoryType: 'PRODUCT', category1: '스크린', code: 'SC', description: '스크린 셔터', isActive: true, createdAt: '2025-01-01' }, { id: 'CAT-002', categoryType: 'PRODUCT', category1: '철재문', code: 'SD', description: '철재 방화문', isActive: true, createdAt: '2025-01-01' }, { id: 'CAT-003', categoryType: 'PRODUCT', category1: '블라인드', code: 'BL', description: '블라인드', isActive: true, createdAt: '2025-01-01' }, { id: 'CAT-101', categoryType: 'PART', category1: '가이드레일', code: 'GR', description: '가이드레일 부품', isActive: true, createdAt: '2025-01-01' }, { id: 'CAT-102', categoryType: 'PART', category1: '하단마감재', code: 'BF', description: '하단마감재', isActive: true, createdAt: '2025-01-01' }, { id: 'CAT-103', categoryType: 'PART', category1: '케이스', code: 'CS', description: '케이스 부품', isActive: true, createdAt: '2025-01-01' }, { id: 'CAT-104', categoryType: 'PART', category1: '도어', code: 'DR', description: '도어 부품', isActive: true, createdAt: '2025-01-01' }, { id: 'CAT-105', categoryType: 'PART', category1: '브라켓', code: 'BR', description: '브라켓 부품', isActive: true, createdAt: '2025-01-01' }, { id: 'CAT-201', categoryType: 'MATERIAL', category1: '강판', code: 'SP', description: '강판 원자재', isActive: true, createdAt: '2025-01-01' }, { id: 'CAT-202', categoryType: 'MATERIAL', category1: '알루미늄', code: 'AL', description: '알루미늄 원자재', isActive: true, createdAt: '2025-01-01' }, { id: 'CAT-203', categoryType: 'MATERIAL', category1: '플라스틱', code: 'PL', description: '플라스틱 원자재', isActive: true, createdAt: '2025-01-01' }, { id: 'CAT-301', categoryType: 'SUB_MATERIAL', category1: '볼트너트', code: 'BN', description: '볼트/너트', isActive: true, createdAt: '2025-01-01' }, { id: 'CAT-302', categoryType: 'SUB_MATERIAL', category1: '나사', code: 'SC', description: '나사류', isActive: true, createdAt: '2025-01-01' }, { id: 'CAT-303', categoryType: 'SUB_MATERIAL', category1: '페인트', code: 'PT', description: '도료', isActive: true, createdAt: '2025-01-01' }, ]; const initialItemUnits: ItemUnit[] = [ { id: 'UNIT-001', unitCode: 'EA', unitName: 'EA (개)', description: '낱개 단위', isActive: true, createdAt: '2025-01-01' }, { id: 'UNIT-002', unitCode: 'SET', unitName: 'SET (세트)', description: '세트 단위', isActive: true, createdAt: '2025-01-01' }, { id: 'UNIT-003', unitCode: 'M', unitName: 'M (미터)', description: '길이 단위', isActive: true, createdAt: '2025-01-01' }, { id: 'UNIT-004', unitCode: 'KG', unitName: 'KG (킬로그램)', description: '무게 단위', isActive: true, createdAt: '2025-01-01' }, { id: 'UNIT-005', unitCode: 'L', unitName: 'L (리터)', description: '부피 단위', isActive: true, createdAt: '2025-01-01' }, { id: 'UNIT-006', unitCode: 'M2', unitName: '㎡ (제곱미터)', description: '면적 단위', isActive: true, createdAt: '2025-01-01' }, { id: 'UNIT-007', unitCode: 'BOX', unitName: 'BOX (박스)', description: '박스 단위', isActive: true, createdAt: '2025-01-01' }, ]; const initialItemMaterials: ItemMaterial[] = [ { id: 'MAT-001', materialCode: 'EGI-1.2T', materialName: 'EGI 1.2T', materialType: 'STEEL', thickness: '1.2T', description: '전기아연도금강판', isActive: true, createdAt: '2025-01-01' }, { id: 'MAT-002', materialCode: 'EGI-1.55T', materialName: 'EGI 1.55T', materialType: 'STEEL', thickness: '1.55T', description: '전기아연도금강판', isActive: true, createdAt: '2025-01-01' }, { id: 'MAT-003', materialCode: 'EGI-2.0T', materialName: 'EGI 2.0T', materialType: 'STEEL', thickness: '2.0T', description: '전기아연도금강판', isActive: true, createdAt: '2025-01-01' }, { id: 'MAT-004', materialCode: 'SUS-1.2T', materialName: 'SUS 1.2T', materialType: 'STEEL', thickness: '1.2T', description: '스테인리스 강판', isActive: true, createdAt: '2025-01-01' }, { id: 'MAT-005', materialCode: 'SUS-1.5T', materialName: 'SUS 1.5T', materialType: 'STEEL', thickness: '1.5T', description: '스테인리스 강판', isActive: true, createdAt: '2025-01-01' }, { id: 'MAT-006', materialCode: 'AL-6063', materialName: '알루미늄 6063', materialType: 'ALUMINUM', description: '알루미늄 압출재', isActive: true, createdAt: '2025-01-01' }, { id: 'MAT-007', materialCode: 'AL-6061', materialName: '알루미늄 6061', materialType: 'ALUMINUM', description: '알루미늄 압출재', isActive: true, createdAt: '2025-01-01' }, ]; const initialSurfaceTreatments: SurfaceTreatment[] = [ { id: 'TREAT-001', treatmentCode: 'NONE', treatmentName: '무도장', treatmentType: 'NONE', description: '표면처리 없음', isActive: true, createdAt: '2025-01-01' }, { id: 'TREAT-002', treatmentCode: 'POWDER', treatmentName: '파우더도장', treatmentType: 'PAINTING', description: '분체도장', isActive: true, createdAt: '2025-01-01' }, { id: 'TREAT-003', treatmentCode: 'ANODIZING', treatmentName: '아노다이징', treatmentType: 'COATING', description: '알루미늄 양극산화처리', isActive: true, createdAt: '2025-01-01' }, { id: 'TREAT-004', treatmentCode: 'ZINC', treatmentName: '아연도금', treatmentType: 'PLATING', description: '아연 도금처리', isActive: true, createdAt: '2025-01-01' }, { id: 'TREAT-005', treatmentCode: 'CHROME', treatmentName: '크롬도금', treatmentType: 'PLATING', description: '크롬 도금처리', isActive: true, createdAt: '2025-01-01' }, ]; const initialPartTypeOptions: PartTypeOption[] = [ { id: 'PTYPE-001', partType: 'ASSEMBLY', optionCode: 'ASSY', optionName: '조립품', description: '여러 부품을 조립하는 부품', isActive: true, createdAt: '2025-01-01' }, { id: 'PTYPE-002', partType: 'BENDING', optionCode: 'BEND', optionName: '절곡품', description: '절곡 가공 부품', isActive: true, createdAt: '2025-01-01' }, { id: 'PTYPE-003', partType: 'PURCHASED', optionCode: 'PURCH', optionName: '구매품', description: '외부에서 구매하는 부품', isActive: true, createdAt: '2025-01-01' }, ]; const initialPartUsageOptions: PartUsageOption[] = [ { id: 'USAGE-001', usageCode: 'GUIDE_RAIL', usageName: '가이드레일', description: '가이드레일용 부품', isActive: true, createdAt: '2025-01-01' }, { id: 'USAGE-002', usageCode: 'BOTTOM_FINISH', usageName: '하단마감재', description: '하단마감재용 부품', isActive: true, createdAt: '2025-01-01' }, { id: 'USAGE-003', usageCode: 'CASE', usageName: '케이스', description: '케이스용 부품', isActive: true, createdAt: '2025-01-01' }, { id: 'USAGE-004', usageCode: 'DOOR', usageName: '도어', description: '도어용 부품', isActive: true, createdAt: '2025-01-01' }, { id: 'USAGE-005', usageCode: 'BRACKET', usageName: '브라켓', description: '브라켓 부품', isActive: true, createdAt: '2025-01-01' }, { id: 'USAGE-006', usageCode: 'GENERAL', usageName: '일반', description: '일반 부품', isActive: true, createdAt: '2025-01-01' }, ]; const initialGuideRailOptions: GuideRailOption[] = [ { id: 'GR-001', optionType: 'MODEL_TYPE', optionCode: 'SCREEN', optionName: '스크린용', description: '스크린 셔터용 가이드레일', isActive: true, createdAt: '2025-01-01' }, { id: 'GR-002', optionType: 'MODEL_TYPE', optionCode: 'STEEL', optionName: '철재용', description: '철재문용 가이드레일', isActive: true, createdAt: '2025-01-01' }, { id: 'GR-011', optionType: 'MODEL', optionCode: 'T40', optionName: 'T40', parentOption: 'SCREEN', description: 'T40 모델', isActive: true, createdAt: '2025-01-01' }, { id: 'GR-012', optionType: 'MODEL', optionCode: 'T60', optionName: 'T60', parentOption: 'SCREEN', description: 'T60 모델', isActive: true, createdAt: '2025-01-01' }, { id: 'GR-013', optionType: 'MODEL', optionCode: 'PREMIUM', optionName: '프리미엄', parentOption: 'STEEL', description: '프리미엄 모델', isActive: true, createdAt: '2025-01-01' }, { id: 'GR-021', optionType: 'CERTIFICATION', optionCode: 'KFI', optionName: 'KFI인증', description: 'KFI 인증', isActive: true, createdAt: '2025-01-01' }, { id: 'GR-022', optionType: 'CERTIFICATION', optionCode: 'NONE', optionName: '미인증', description: '인증 없음', isActive: true, createdAt: '2025-01-01' }, { id: 'GR-031', optionType: 'SHAPE', optionCode: 'ROUND', optionName: 'R형', description: '라운드형', isActive: true, createdAt: '2025-01-01' }, { id: 'GR-032', optionType: 'SHAPE', optionCode: 'SQUARE', optionName: 'ㄱ형', description: '사각형', isActive: true, createdAt: '2025-01-01' }, { id: 'GR-041', optionType: 'FINISH', optionCode: 'POWDER', optionName: '파우더도장', description: '파우더 도장 마감', isActive: true, createdAt: '2025-01-01' }, { id: 'GR-042', optionType: 'FINISH', optionCode: 'ANODIZING', optionName: '아노다이징', description: '아노다이징 마감', isActive: true, createdAt: '2025-01-01' }, { id: 'GR-051', optionType: 'LENGTH', optionCode: '3000', optionName: '3000mm', description: '3미터', isActive: true, createdAt: '2025-01-01' }, { id: 'GR-052', optionType: 'LENGTH', optionCode: '4000', optionName: '4000mm', description: '4미터', isActive: true, createdAt: '2025-01-01' }, { id: 'GR-053', optionType: 'LENGTH', optionCode: '5000', optionName: '5000mm', description: '5미터', isActive: true, createdAt: '2025-01-01' }, ]; const initialItemMasterFields: ItemMasterField[] = [ { id: 'MASTER-001', name: '품목코드', fieldKey: 'itemCode', property: { inputType: 'textbox', required: true, row: 1, col: 1 }, category: '공통', description: '품목의 고유 코드', isActive: true, createdAt: '2025-01-01' }, { id: 'MASTER-002', name: '품목명', fieldKey: 'itemName', property: { inputType: 'textbox', required: true, row: 1, col: 1 }, category: '공통', description: '품목의 이름', isActive: true, createdAt: '2025-01-01' }, { id: 'MASTER-003', name: '단위', fieldKey: 'unit', property: { inputType: 'dropdown', required: true, row: 1, col: 1, options: ['EA', 'SET', 'KG', 'M', 'BOX'] }, category: '공통', description: '품목의 단위', isActive: true, createdAt: '2025-01-01' }, { id: 'MASTER-004', name: '재질', fieldKey: 'material', property: { inputType: 'dropdown', required: false, row: 1, col: 1, options: ['EGI 1.2T', 'SUS 1.2T', 'AL 1.5T'] }, category: '부품', description: '부품의 재질', isActive: true, createdAt: '2025-01-01' }, { id: 'MASTER-005', name: '표면처리', fieldKey: 'surfaceTreatment', property: { inputType: 'dropdown', required: false, row: 1, col: 1, options: ['무도장', '파우더도장', '아노다이징'] }, category: '부품', description: '부품의 표면처리 방법', isActive: true, createdAt: '2025-01-01' } ]; const initialItemPages: ItemPage[] = []; */ // ✅ 빈 배열로 초기화 - API에서 데이터 로드 예정 const initialSpecificationMasters: SpecificationMaster[] = []; const initialMaterialItemNames: MaterialItemName[] = []; const initialItemCategories: ItemCategory[] = []; const initialItemUnits: ItemUnit[] = []; const initialItemMaterials: ItemMaterial[] = []; const initialSurfaceTreatments: SurfaceTreatment[] = []; const initialPartTypeOptions: PartTypeOption[] = []; const initialPartUsageOptions: PartUsageOption[] = []; const initialGuideRailOptions: GuideRailOption[] = []; const initialItemMasterFields: ItemMasterField[] = []; const initialItemPages: ItemPage[] = []; // ===== Auth & Cache Setup ===== const { currentUser } = useAuth(); const tenantId = currentUser?.tenant?.id; // ✅ TenantAwareCache 인스턴스 생성 (tenant.id 기반, SSR-safe) const cache = useMemo(() => { // 서버 환경에서는 null 반환 (sessionStorage 없음) if (typeof window === 'undefined') return null; // 클라이언트 환경에서만 캐시 생성 return tenantId ? new TenantAwareCache(tenantId, sessionStorage, 3600000) : null; }, [tenantId]); // ===== State Management (SSR-safe) ===== const [itemMasters, setItemMasters] = useState(initialItemMasters); const [specificationMasters, setSpecificationMasters] = useState(initialSpecificationMasters); const [materialItemNames, setMaterialItemNames] = useState(initialMaterialItemNames); const [itemCategories, setItemCategories] = useState(initialItemCategories); const [itemUnits, setItemUnits] = useState(initialItemUnits); const [itemMaterials, setItemMaterials] = useState(initialItemMaterials); const [surfaceTreatments, setSurfaceTreatments] = useState(initialSurfaceTreatments); const [partTypeOptions, setPartTypeOptions] = useState(initialPartTypeOptions); const [partUsageOptions, setPartUsageOptions] = useState(initialPartUsageOptions); const [guideRailOptions, setGuideRailOptions] = useState(initialGuideRailOptions); const [sectionTemplates, setSectionTemplates] = useState([]); const [itemMasterFields, setItemMasterFields] = useState(initialItemMasterFields); const [itemPages, setItemPages] = useState(initialItemPages); // ✅ TenantAwareCache에서 초기 데이터 로드 useEffect(() => { if (!cache) return; // tenant.id 없으면 초기 데이터 유지 // ItemMasters const cachedItemMasters = cache.get('itemMasters'); if (cachedItemMasters) setItemMasters(cachedItemMasters); // SpecificationMasters (버전 체크) if (cache.isVersionMatch('specificationMasters', '1.0')) { const cachedSpecs = cache.get('specificationMasters'); if (cachedSpecs) setSpecificationMasters(cachedSpecs); } // MaterialItemNames (버전 체크) if (cache.isVersionMatch('materialItemNames', '1.1')) { const cachedMaterials = cache.get('materialItemNames'); if (cachedMaterials) setMaterialItemNames(cachedMaterials); } // ItemCategories const cachedCategories = cache.get('itemCategories'); if (cachedCategories) setItemCategories(cachedCategories); // ItemUnits const cachedUnits = cache.get('itemUnits'); if (cachedUnits) setItemUnits(cachedUnits); // ItemMaterials const cachedMaterials = cache.get('itemMaterials'); if (cachedMaterials) setItemMaterials(cachedMaterials); // SurfaceTreatments const cachedTreatments = cache.get('surfaceTreatments'); if (cachedTreatments) setSurfaceTreatments(cachedTreatments); // PartTypeOptions const cachedPartTypes = cache.get('partTypeOptions'); if (cachedPartTypes) setPartTypeOptions(cachedPartTypes); // PartUsageOptions const cachedPartUsages = cache.get('partUsageOptions'); if (cachedPartUsages) setPartUsageOptions(cachedPartUsages); // GuideRailOptions const cachedGuideRails = cache.get('guideRailOptions'); if (cachedGuideRails) setGuideRailOptions(cachedGuideRails); // SectionTemplates const cachedTemplates = cache.get('sectionTemplates'); if (cachedTemplates) setSectionTemplates(cachedTemplates); // ItemMasterFields const cachedFields = cache.get('itemMasterFields'); if (cachedFields) setItemMasterFields(cachedFields); // ItemPages const cachedPages = cache.get('itemPages'); if (cachedPages) setItemPages(cachedPages); }, [cache]); // ✅ TenantAwareCache 동기화 (상태 변경 시 자동 저장) useEffect(() => { if (cache && itemMasters !== initialItemMasters) { cache.set('itemMasters', itemMasters); } }, [cache, itemMasters]); useEffect(() => { if (cache && specificationMasters !== initialSpecificationMasters) { cache.set('specificationMasters', specificationMasters, '1.0'); } }, [cache, specificationMasters]); useEffect(() => { if (cache && materialItemNames !== initialMaterialItemNames) { cache.set('materialItemNames', materialItemNames, '1.1'); } }, [cache, materialItemNames]); useEffect(() => { if (cache && itemCategories !== initialItemCategories) { cache.set('itemCategories', itemCategories); } }, [cache, itemCategories]); useEffect(() => { if (cache && itemUnits !== initialItemUnits) { cache.set('itemUnits', itemUnits); } }, [cache, itemUnits]); useEffect(() => { if (cache && itemMaterials !== initialItemMaterials) { cache.set('itemMaterials', itemMaterials); } }, [cache, itemMaterials]); useEffect(() => { if (cache && surfaceTreatments !== initialSurfaceTreatments) { cache.set('surfaceTreatments', surfaceTreatments); } }, [cache, surfaceTreatments]); useEffect(() => { if (cache && partTypeOptions !== initialPartTypeOptions) { cache.set('partTypeOptions', partTypeOptions); } }, [cache, partTypeOptions]); useEffect(() => { if (cache && partUsageOptions !== initialPartUsageOptions) { cache.set('partUsageOptions', partUsageOptions); } }, [cache, partUsageOptions]); useEffect(() => { if (cache && guideRailOptions !== initialGuideRailOptions) { cache.set('guideRailOptions', guideRailOptions); } }, [cache, guideRailOptions]); useEffect(() => { if (cache) { cache.set('sectionTemplates', sectionTemplates); } }, [cache, sectionTemplates]); useEffect(() => { if (cache && itemMasterFields !== initialItemMasterFields) { cache.set('itemMasterFields', itemMasterFields); } }, [cache, itemMasterFields]); useEffect(() => { if (cache && itemPages !== initialItemPages) { cache.set('itemPages', itemPages); } }, [cache, itemPages]); // ===== CRUD Functions ===== // ItemMaster CRUD const addItemMaster = (item: ItemMaster) => { setItemMasters(prev => [...prev, item]); }; const updateItemMaster = (id: string, updates: Partial) => { setItemMasters(prev => prev.map(item => item.id === id ? { ...item, ...updates } : item)); }; const deleteItemMaster = (id: string) => { setItemMasters(prev => prev.filter(item => item.id !== id)); }; // SpecificationMaster CRUD const addSpecificationMaster = (spec: SpecificationMaster) => { setSpecificationMasters(prev => [...prev, spec]); }; const updateSpecificationMaster = (id: string, updates: Partial) => { setSpecificationMasters(prev => prev.map(spec => spec.id === id ? { ...spec, ...updates } : spec)); }; const deleteSpecificationMaster = (id: string) => { setSpecificationMasters(prev => prev.filter(spec => spec.id !== id)); }; // MaterialItemName CRUD const addMaterialItemName = (item: MaterialItemName) => { setMaterialItemNames(prev => [...prev, item]); }; const updateMaterialItemName = (id: string, updates: Partial) => { setMaterialItemNames(prev => prev.map(item => item.id === id ? { ...item, ...updates } : item)); }; const deleteMaterialItemName = (id: string) => { setMaterialItemNames(prev => prev.filter(item => item.id !== id)); }; // ItemCategory CRUD const addItemCategory = (category: ItemCategory) => { setItemCategories(prev => [...prev, category]); }; const updateItemCategory = (id: string, updates: Partial) => { setItemCategories(prev => prev.map(cat => cat.id === id ? { ...cat, ...updates } : cat)); }; const deleteItemCategory = (id: string) => { setItemCategories(prev => prev.filter(cat => cat.id !== id)); }; // ItemUnit CRUD const addItemUnit = (unit: ItemUnit) => { setItemUnits(prev => [...prev, unit]); }; const updateItemUnit = (id: string, updates: Partial) => { setItemUnits(prev => prev.map(unit => unit.id === id ? { ...unit, ...updates } : unit)); }; const deleteItemUnit = (id: string) => { setItemUnits(prev => prev.filter(unit => unit.id !== id)); }; // ItemMaterial CRUD const addItemMaterial = (material: ItemMaterial) => { setItemMaterials(prev => [...prev, material]); }; const updateItemMaterial = (id: string, updates: Partial) => { setItemMaterials(prev => prev.map(mat => mat.id === id ? { ...mat, ...updates } : mat)); }; const deleteItemMaterial = (id: string) => { setItemMaterials(prev => prev.filter(mat => mat.id !== id)); }; // SurfaceTreatment CRUD const addSurfaceTreatment = (treatment: SurfaceTreatment) => { setSurfaceTreatments(prev => [...prev, treatment]); }; const updateSurfaceTreatment = (id: string, updates: Partial) => { setSurfaceTreatments(prev => prev.map(treat => treat.id === id ? { ...treat, ...updates } : treat)); }; const deleteSurfaceTreatment = (id: string) => { setSurfaceTreatments(prev => prev.filter(treat => treat.id !== id)); }; // PartTypeOption CRUD const addPartTypeOption = (option: PartTypeOption) => { setPartTypeOptions(prev => [...prev, option]); }; const updatePartTypeOption = (id: string, updates: Partial) => { setPartTypeOptions(prev => prev.map(opt => opt.id === id ? { ...opt, ...updates } : opt)); }; const deletePartTypeOption = (id: string) => { setPartTypeOptions(prev => prev.filter(opt => opt.id !== id)); }; // PartUsageOption CRUD const addPartUsageOption = (option: PartUsageOption) => { setPartUsageOptions(prev => [...prev, option]); }; const updatePartUsageOption = (id: string, updates: Partial) => { setPartUsageOptions(prev => prev.map(opt => opt.id === id ? { ...opt, ...updates } : opt)); }; const deletePartUsageOption = (id: string) => { setPartUsageOptions(prev => prev.filter(opt => opt.id !== id)); }; // GuideRailOption CRUD const addGuideRailOption = (option: GuideRailOption) => { setGuideRailOptions(prev => [...prev, option]); }; const updateGuideRailOption = (id: string, updates: Partial) => { setGuideRailOptions(prev => prev.map(opt => opt.id === id ? { ...opt, ...updates } : opt)); }; const deleteGuideRailOption = (id: string) => { setGuideRailOptions(prev => prev.filter(opt => opt.id !== id)); }; // ItemMasterField CRUD (임시: 로컬 state) /** * 초기 데이터 로딩용: API 호출 없이 마스터 필드를 state에 로드 (덮어쓰기) */ const loadItemMasterFields = (fields: ItemMasterField[]) => { setItemMasterFields(fields); console.log('[ItemMasterContext] 마스터 필드 로드 완료:', fields.length); }; const addItemMasterField = async (field: Omit) => { try { // API 호출 const response = await itemMasterApi.masterFields.create({ field_name: field.field_name, field_type: field.field_type, category: field.category ?? undefined, description: field.description ?? undefined, is_common: field.is_common, default_value: field.default_value ?? undefined, options: field.options ?? undefined, validation_rules: field.validation_rules ?? undefined, properties: field.properties ?? undefined, }); if (!response.success || !response.data) { throw new Error(response.message || '마스터 필드 생성 실패'); } // 응답 데이터 변환 및 state 업데이트 const newField: ItemMasterField = { id: response.data.id, tenant_id: response.data.tenant_id, field_name: response.data.field_name, field_type: response.data.field_type, category: response.data.category, description: response.data.description, is_common: response.data.is_common, default_value: response.data.default_value, options: response.data.options, validation_rules: response.data.validation_rules, properties: response.data.properties, created_by: response.data.created_by, updated_by: response.data.updated_by, created_at: response.data.created_at, updated_at: response.data.updated_at, }; setItemMasterFields(prev => [...prev, newField]); console.log('[ItemMasterContext] 마스터 필드 생성 성공:', newField.id); } catch (error) { const errorMessage = getErrorMessage(error); console.error('[ItemMasterContext] 마스터 필드 생성 실패:', errorMessage); throw error; } }; const updateItemMasterField = async (id: number, updates: Partial) => { try { // API 호출 const requestData: any = {}; if (updates.field_name !== undefined) requestData.field_name = updates.field_name; if (updates.field_type !== undefined) requestData.field_type = updates.field_type; if (updates.category !== undefined) requestData.category = updates.category; if (updates.description !== undefined) requestData.description = updates.description; if (updates.is_common !== undefined) requestData.is_common = updates.is_common; if (updates.default_value !== undefined) requestData.default_value = updates.default_value; if (updates.options !== undefined) requestData.options = updates.options; if (updates.validation_rules !== undefined) requestData.validation_rules = updates.validation_rules; if (updates.properties !== undefined) requestData.properties = updates.properties; const response = await itemMasterApi.masterFields.update(id, requestData); if (!response.success || !response.data) { throw new Error(response.message || '마스터 필드 수정 실패'); } // state 업데이트 setItemMasterFields(prev => prev.map(field => field.id === id ? { ...field, field_name: response.data!.field_name, field_type: response.data!.field_type, category: response.data!.category, description: response.data!.description, is_common: response.data!.is_common, default_value: response.data!.default_value, options: response.data!.options, validation_rules: response.data!.validation_rules, properties: response.data!.properties, updated_at: response.data!.updated_at, } : field )); console.log('[ItemMasterContext] 마스터 필드 수정 성공:', id); } catch (error) { const errorMessage = getErrorMessage(error); console.error('[ItemMasterContext] 마스터 필드 수정 실패:', errorMessage); throw error; } }; const deleteItemMasterField = async (id: number) => { try { // API 호출 const response = await itemMasterApi.masterFields.delete(id); if (!response.success) { throw new Error(response.message || '마스터 필드 삭제 실패'); } // state 업데이트 setItemMasterFields(prev => prev.filter(field => field.id !== id)); console.log('[ItemMasterContext] 마스터 필드 삭제 성공:', id); } catch (error) { const errorMessage = getErrorMessage(error); console.error('[ItemMasterContext] 마스터 필드 삭제 실패:', errorMessage); throw error; } }; // SectionTemplate CRUD with API /** * 초기 데이터 로딩용: API 호출 없이 섹션 템플릿을 state에 로드 (덮어쓰기) */ const loadSectionTemplates = (templates: SectionTemplate[]) => { setSectionTemplates(templates); console.log('[ItemMasterContext] 섹션 템플릿 로드 완료:', templates.length); }; const addSectionTemplate = async (template: Omit) => { try { // 프론트엔드 형식 → API 형식 변환 // template_name → title, section_type → type const apiType = template.section_type === 'BOM' ? 'bom' : 'fields'; const response = await itemMasterApi.templates.create({ title: template.template_name, type: apiType, description: template.description ?? undefined, is_default: false, // 기본값 }); if (!response.success || !response.data) { throw new Error(response.message || '섹션 템플릿 생성 실패'); } // API 응답 → 프론트엔드 형식 변환 // title → template_name, type → section_type const SECTION_TYPE_MAP: Record = { fields: 'BASIC', bom: 'BOM', }; const newTemplate: SectionTemplate = { id: response.data.id, tenant_id: response.data.tenant_id, template_name: response.data.title, section_type: SECTION_TYPE_MAP[response.data.type] || 'BASIC', description: response.data.description, default_fields: null, category: template.category, fields: template.fields, bomItems: template.bomItems, created_by: response.data.created_by, updated_by: response.data.updated_by, created_at: response.data.created_at, updated_at: response.data.updated_at, }; setSectionTemplates(prev => [...prev, newTemplate]); console.log('[ItemMasterContext] 섹션 템플릿 생성 성공:', newTemplate.id); } catch (error) { const errorMessage = getErrorMessage(error); console.error('[ItemMasterContext] 섹션 템플릿 생성 실패:', errorMessage); throw error; } }; const updateSectionTemplate = async (id: number, updates: Partial) => { try { // default_fields, fields, category, bomItems는 로컬에서만 관리 (API 미지원) const localOnlyUpdates = ['default_fields', 'fields', 'category', 'bomItems']; const hasApiUpdates = Object.keys(updates).some(key => !localOnlyUpdates.includes(key)); const hasLocalUpdates = Object.keys(updates).some(key => localOnlyUpdates.includes(key)); // API 호출이 필요한 경우에만 API 요청 if (hasApiUpdates) { // API 요청 형식으로 변환 (frontend → API) // frontend: template_name, section_type // API: title, type const requestData: any = {}; if (updates.template_name !== undefined) requestData.title = updates.template_name; if (updates.section_type !== undefined) { // section_type 변환: 'BASIC' | 'CUSTOM' → 'fields', 'BOM' → 'bom' requestData.type = updates.section_type === 'BOM' ? 'bom' : 'fields'; } if (updates.description !== undefined) requestData.description = updates.description; const response = await itemMasterApi.templates.update(id, requestData); if (!response.success || !response.data) { throw new Error(response.message || '섹션 템플릿 수정 실패'); } // state 업데이트 (API 응답 → frontend 형식으로 변환) // API 응답: title, type ('fields' | 'bom') // Frontend 형식: template_name, section_type ('BASIC' | 'BOM' | 'CUSTOM') const SECTION_TYPE_MAP: Record = { fields: 'BASIC', bom: 'BOM', }; setSectionTemplates(prev => prev.map(template => template.id === id ? { ...template, template_name: response.data!.title, section_type: SECTION_TYPE_MAP[response.data!.type] || 'BASIC', description: response.data!.description, updated_at: response.data!.updated_at, } : template )); console.log('[ItemMasterContext] 섹션 템플릿 수정 성공 (API):', id); } // 로컬 전용 필드 업데이트 (default_fields, fields, category, bomItems) if (hasLocalUpdates) { setSectionTemplates(prev => prev.map(template => { if (template.id !== id) return template; const updatedTemplate = { ...template }; // default_fields 업데이트 시 fields도 같이 업데이트 if (updates.default_fields !== undefined) { updatedTemplate.default_fields = updates.default_fields; updatedTemplate.fields = updates.default_fields as TemplateField[]; } if (updates.fields !== undefined) { updatedTemplate.fields = updates.fields; updatedTemplate.default_fields = updates.fields; } if (updates.category !== undefined) { updatedTemplate.category = updates.category; } if (updates.bomItems !== undefined) { updatedTemplate.bomItems = updates.bomItems; } return updatedTemplate; })); console.log('[ItemMasterContext] 섹션 템플릿 수정 성공 (로컬):', id, Object.keys(updates).filter(k => localOnlyUpdates.includes(k))); } } catch (error) { const errorMessage = getErrorMessage(error); console.error('[ItemMasterContext] 섹션 템플릿 수정 실패:', errorMessage); throw error; } }; const deleteSectionTemplate = async (id: number) => { try { // API 호출 const response = await itemMasterApi.templates.delete(id); if (!response.success) { throw new Error(response.message || '섹션 템플릿 삭제 실패'); } // state 업데이트 setSectionTemplates(prev => prev.filter(template => template.id !== id)); console.log('[ItemMasterContext] 섹션 템플릿 삭제 성공:', id); } catch (error) { const errorMessage = getErrorMessage(error); console.error('[ItemMasterContext] 섹션 템플릿 삭제 실패:', errorMessage); throw error; } }; // ItemPage CRUD with API /** * 초기 데이터 로딩용: API 호출 없이 페이지 데이터를 state에 로드 (덮어쓰기) * (이미 백엔드에서 받아온 데이터를 로드할 때 사용) */ const loadItemPages = (pages: ItemPage[]) => { setItemPages(pages); // 덮어쓰기 (append가 아님!) console.log('[ItemMasterContext] 페이지 데이터 로드 완료:', pages.length); }; /** * 새 페이지 생성: API 호출 + state 업데이트 * (사용자가 새 페이지를 만들 때 사용) * @returns 생성된 페이지 반환 */ const addItemPage = async (pageData: Omit): Promise => { try { // API 요청 데이터 변환 const requestData: ItemPageRequest = { page_name: pageData.page_name, item_type: pageData.item_type, absolute_path: pageData.absolute_path || '', }; // API 호출 const response = await itemMasterApi.pages.create(requestData); // 응답 데이터 변환 및 state 업데이트 const newPage = transformPageResponse(response); setItemPages(prev => [...prev, newPage]); console.log('[ItemMasterContext] 페이지 생성 성공:', newPage); return newPage; } catch (error) { const errorMessage = getErrorMessage(error); console.error('[ItemMasterContext] 페이지 생성 실패:', errorMessage); throw error; } }; const updateItemPage = async (id: number, updates: Partial) => { try { // API 요청 데이터 변환 const requestData: Partial = {}; if (updates.page_name) requestData.page_name = updates.page_name; if (updates.absolute_path !== undefined) requestData.absolute_path = updates.absolute_path; // API 호출 const response = await itemMasterApi.pages.update(id, requestData); if (!response.success || !response.data) { throw new Error(response.message || '페이지 수정 실패'); } // 응답 데이터 변환 및 state 업데이트 const updatedPage = transformPageResponse(response.data); setItemPages(prev => prev.map(page => page.id === id ? updatedPage : page)); console.log('[ItemMasterContext] 페이지 수정 성공:', updatedPage); } catch (error) { const errorMessage = getErrorMessage(error); console.error('[ItemMasterContext] 페이지 수정 실패:', errorMessage); throw error; } }; const deleteItemPage = async (id: number) => { try { // API 호출 const response = await itemMasterApi.pages.delete(id); if (!response.success) { throw new Error(response.message || '페이지 삭제 실패'); } // state 업데이트 setItemPages(prev => prev.filter(page => page.id !== id)); console.log('[ItemMasterContext] 페이지 삭제 성공:', id); } catch (error) { const errorMessage = getErrorMessage(error); console.error('[ItemMasterContext] 페이지 삭제 실패:', errorMessage); throw error; } }; const reorderPages = async (newOrder: Array<{ id: number; order_no: number }>) => { try { // Optimistic UI 업데이트 (즉시 반영) setItemPages(prev => { const updated = [...prev]; updated.sort((a, b) => { const orderA = newOrder.find(o => o.id === a.id)?.order_no ?? 0; const orderB = newOrder.find(o => o.id === b.id)?.order_no ?? 0; return orderA - orderB; }); return updated.map(page => { const newOrderNo = newOrder.find(o => o.id === page.id)?.order_no; return newOrderNo !== undefined ? { ...page, order_no: newOrderNo } : page; }); }); // API 호출 const response = await itemMasterApi.pages.reorder({ page_orders: newOrder }); if (!response.success || !response.data) { throw new Error(response.message || '페이지 순서 변경 실패'); } // API 응답으로 최종 업데이트 const reorderedPages = response.data.map(transformPageResponse); setItemPages(reorderedPages); console.log('[ItemMasterContext] 페이지 순서 변경 성공'); } catch (error) { const errorMessage = getErrorMessage(error); console.error('[ItemMasterContext] 페이지 순서 변경 실패:', errorMessage); // 실패 시 이전 상태로 롤백 // 여기서는 페이지 전체를 다시 로드하는 것이 더 안전할 수 있음 throw error; } }; // Section CRUD with API const addSectionToPage = async (pageId: number, sectionData: Omit) => { try { // API 호출 const response = await itemMasterApi.sections.create(pageId, { title: sectionData.section_name, type: sectionData.section_type === 'BOM' ? 'bom' : 'fields', }); if (!response.success || !response.data) { throw new Error(response.message || '섹션 생성 실패'); } // 응답 데이터 변환 및 state 업데이트 const newSection = transformSectionResponse(response.data); setItemPages(prev => prev.map(page => page.id === pageId ? { ...page, sections: [...page.sections, newSection] } : page )); console.log('[ItemMasterContext] 섹션 생성 성공:', newSection); } catch (error) { const errorMessage = getErrorMessage(error); console.error('[ItemMasterContext] 섹션 생성 실패:', errorMessage); throw error; } }; const updateSection = async (sectionId: number, updates: Partial) => { try { // API 요청 데이터 변환 const requestData: any = {}; if (updates.section_name) requestData.title = updates.section_name; // API 호출 const response = await itemMasterApi.sections.update(sectionId, requestData); if (!response.success || !response.data) { throw new Error(response.message || '섹션 수정 실패'); } // 응답 데이터 변환 및 state 업데이트 const updatedSection = transformSectionResponse(response.data); setItemPages(prev => prev.map(page => ({ ...page, sections: page.sections.map(section => section.id === sectionId ? updatedSection : section ) }))); console.log('[ItemMasterContext] 섹션 수정 성공:', updatedSection); } catch (error) { const errorMessage = getErrorMessage(error); console.error('[ItemMasterContext] 섹션 수정 실패:', errorMessage); throw error; } }; const deleteSection = async (sectionId: number) => { try { // API 호출 const response = await itemMasterApi.sections.delete(sectionId); if (!response.success) { throw new Error(response.message || '섹션 삭제 실패'); } // state 업데이트 setItemPages(prev => prev.map(page => ({ ...page, sections: page.sections.filter(section => section.id !== sectionId) }))); console.log('[ItemMasterContext] 섹션 삭제 성공:', sectionId); } catch (error) { const errorMessage = getErrorMessage(error); console.error('[ItemMasterContext] 섹션 삭제 실패:', errorMessage); throw error; } }; const reorderSections = async (pageId: number, sectionIds: number[]) => { try { // API 호출 const response = await itemMasterApi.sections.reorder(pageId, { section_orders: sectionIds.map((id, index) => ({ id, order_no: index })) }); if (!response.success || !response.data) { throw new Error(response.message || '섹션 순서 변경 실패'); } // 응답 데이터 변환 및 state 업데이트 const reorderedSections = response.data.map(transformSectionResponse); setItemPages(prev => prev.map(page => page.id === pageId ? { ...page, sections: reorderedSections } : page )); console.log('[ItemMasterContext] 섹션 순서 변경 성공'); } catch (error) { const errorMessage = getErrorMessage(error); console.error('[ItemMasterContext] 섹션 순서 변경 실패:', errorMessage); throw error; } }; // Field CRUD with API const addFieldToSection = async (sectionId: number, fieldData: Omit) => { try { // API 호출 (null → undefined 변환) const response = await itemMasterApi.fields.create(sectionId, { field_name: fieldData.field_name, field_type: fieldData.field_type, is_required: fieldData.is_required, default_value: fieldData.default_value ?? undefined, placeholder: fieldData.placeholder ?? undefined, display_condition: fieldData.display_condition ?? undefined, validation_rules: fieldData.validation_rules ?? undefined, options: fieldData.options ?? undefined, properties: fieldData.properties ?? undefined, }); if (!response.success || !response.data) { throw new Error(response.message || '필드 생성 실패'); } // 응답 데이터 변환 및 state 업데이트 const newField: ItemField = { id: response.data.id, tenant_id: response.data.tenant_id, section_id: response.data.section_id, master_field_id: response.data.master_field_id, field_name: response.data.field_name, field_type: response.data.field_type, order_no: response.data.order_no, is_required: response.data.is_required, default_value: response.data.default_value, placeholder: response.data.placeholder, display_condition: response.data.display_condition, validation_rules: response.data.validation_rules, options: response.data.options, properties: response.data.properties, created_at: response.data.created_at, updated_at: response.data.updated_at, }; setItemPages(prev => prev.map(page => ({ ...page, sections: page.sections.map(section => section.id === sectionId ? { ...section, fields: [...(section.fields || []), newField] } : section ) }))); console.log('[ItemMasterContext] 필드 생성 성공:', newField.id); } catch (error) { const errorMessage = getErrorMessage(error); console.error('[ItemMasterContext] 필드 생성 실패:', errorMessage); throw error; } }; const updateField = async (fieldId: number, updates: Partial) => { try { // API 호출 (null → undefined 변환) // 주의: display_condition은 현재 백엔드에서 지원하는 형식과 프론트엔드 형식이 다르므로 API에 전송하지 않음 // 백엔드 형식: {"field_id": "1", "operator": "equals", "value": "true"} // 프론트엔드 형식: {"targetType": "field", "fieldConditions": [...], "sectionIds": [...]} const requestData: any = {}; if (updates.field_name !== undefined) requestData.field_name = updates.field_name; if (updates.field_type !== undefined) requestData.field_type = updates.field_type; if (updates.is_required !== undefined) requestData.is_required = updates.is_required; if (updates.default_value !== undefined) requestData.default_value = updates.default_value ?? undefined; if (updates.placeholder !== undefined) requestData.placeholder = updates.placeholder ?? undefined; // display_condition은 API 형식 불일치로 전송하지 않음 (로컬 상태에서만 관리) if (updates.validation_rules !== undefined) requestData.validation_rules = updates.validation_rules ?? undefined; if (updates.options !== undefined) requestData.options = updates.options ?? undefined; if (updates.properties !== undefined) requestData.properties = updates.properties ?? undefined; const response = await itemMasterApi.fields.update(fieldId, requestData); if (!response.success || !response.data) { throw new Error(response.message || '필드 수정 실패'); } // state 업데이트 setItemPages(prev => prev.map(page => ({ ...page, sections: page.sections.map(section => ({ ...section, fields: (section.fields || []).map(field => field.id === fieldId ? { ...field, field_name: response.data!.field_name, field_type: response.data!.field_type, is_required: response.data!.is_required, default_value: response.data!.default_value, placeholder: response.data!.placeholder, display_condition: response.data!.display_condition, validation_rules: response.data!.validation_rules, options: response.data!.options, properties: response.data!.properties, updated_at: response.data!.updated_at, } : field ) })) }))); console.log('[ItemMasterContext] 필드 수정 성공:', fieldId); } catch (error) { const errorMessage = getErrorMessage(error); console.error('[ItemMasterContext] 필드 수정 실패:', errorMessage); throw error; } }; const deleteField = async (fieldId: number) => { try { // API 호출 const response = await itemMasterApi.fields.delete(fieldId); if (!response.success) { throw new Error(response.message || '필드 삭제 실패'); } // state 업데이트 setItemPages(prev => prev.map(page => ({ ...page, sections: page.sections.map(section => ({ ...section, fields: (section.fields || []).filter(field => field.id !== fieldId) })) }))); console.log('[ItemMasterContext] 필드 삭제 성공:', fieldId); } catch (error) { const errorMessage = getErrorMessage(error); console.error('[ItemMasterContext] 필드 삭제 실패:', errorMessage); throw error; } }; const reorderFields = async (sectionId: number, fieldIds: number[]) => { try { // API 호출 const response = await itemMasterApi.fields.reorder(sectionId, { field_orders: fieldIds.map((id, index) => ({ id, order_no: index })) }); if (!response.success || !response.data) { throw new Error(response.message || '필드 순서 변경 실패'); } // 응답 데이터 변환 및 state 업데이트 const reorderedFields = response.data.map(fieldResponse => ({ id: fieldResponse.id, tenant_id: fieldResponse.tenant_id, section_id: fieldResponse.section_id, master_field_id: fieldResponse.master_field_id, field_name: fieldResponse.field_name, field_type: fieldResponse.field_type, order_no: fieldResponse.order_no, is_required: fieldResponse.is_required, default_value: fieldResponse.default_value, placeholder: fieldResponse.placeholder, display_condition: fieldResponse.display_condition, validation_rules: fieldResponse.validation_rules, options: fieldResponse.options, properties: fieldResponse.properties, created_at: fieldResponse.created_at, updated_at: fieldResponse.updated_at, })); setItemPages(prev => prev.map(page => ({ ...page, sections: page.sections.map(section => section.id === sectionId ? { ...section, fields: reorderedFields } : section ) }))); console.log('[ItemMasterContext] 필드 순서 변경 성공'); } catch (error) { const errorMessage = getErrorMessage(error); console.error('[ItemMasterContext] 필드 순서 변경 실패:', errorMessage); throw error; } }; // BOM CRUD with API const addBOMItem = async (sectionId: number, bomData: Omit) => { try { // API 호출 (null → undefined 변환) const response = await itemMasterApi.bomItems.create(sectionId, { item_code: bomData.item_code ?? undefined, item_name: bomData.item_name, quantity: bomData.quantity, unit: bomData.unit ?? undefined, unit_price: bomData.unit_price ?? undefined, total_price: bomData.total_price ?? undefined, spec: bomData.spec ?? undefined, note: bomData.note ?? undefined, }); if (!response.success || !response.data) { throw new Error(response.message || 'BOM 항목 생성 실패'); } // 응답 데이터 변환 및 state 업데이트 const newBOM: BOMItem = { id: response.data.id, tenant_id: response.data.tenant_id, section_id: response.data.section_id, item_code: response.data.item_code, item_name: response.data.item_name, quantity: response.data.quantity, unit: response.data.unit, unit_price: response.data.unit_price, total_price: response.data.total_price, spec: response.data.spec, note: response.data.note, created_at: response.data.created_at, updated_at: response.data.updated_at, }; setItemPages(prev => prev.map(page => ({ ...page, sections: page.sections.map(section => section.id === sectionId ? { ...section, bomItems: [...(section.bomItems || []), newBOM] } : section ) }))); console.log('[ItemMasterContext] BOM 항목 생성 성공:', newBOM.id); } catch (error) { const errorMessage = getErrorMessage(error); console.error('[ItemMasterContext] BOM 항목 생성 실패:', errorMessage); throw error; } }; const updateBOMItem = async (bomId: number, updates: Partial) => { try { // API 호출 (null → undefined 변환) const requestData: any = {}; if (updates.item_code !== undefined) requestData.item_code = updates.item_code ?? undefined; if (updates.item_name !== undefined) requestData.item_name = updates.item_name; if (updates.quantity !== undefined) requestData.quantity = updates.quantity; if (updates.unit !== undefined) requestData.unit = updates.unit ?? undefined; if (updates.unit_price !== undefined) requestData.unit_price = updates.unit_price ?? undefined; if (updates.total_price !== undefined) requestData.total_price = updates.total_price ?? undefined; if (updates.spec !== undefined) requestData.spec = updates.spec ?? undefined; if (updates.note !== undefined) requestData.note = updates.note ?? undefined; const response = await itemMasterApi.bomItems.update(bomId, requestData); if (!response.success || !response.data) { throw new Error(response.message || 'BOM 항목 수정 실패'); } // state 업데이트 setItemPages(prev => prev.map(page => ({ ...page, sections: page.sections.map(section => ({ ...section, bomItems: (section.bomItems || []).map(bom => bom.id === bomId ? { ...bom, item_code: response.data!.item_code, item_name: response.data!.item_name, quantity: response.data!.quantity, unit: response.data!.unit, unit_price: response.data!.unit_price, total_price: response.data!.total_price, spec: response.data!.spec, note: response.data!.note, updated_at: response.data!.updated_at, } : bom ) })) }))); console.log('[ItemMasterContext] BOM 항목 수정 성공:', bomId); } catch (error) { const errorMessage = getErrorMessage(error); console.error('[ItemMasterContext] BOM 항목 수정 실패:', errorMessage); throw error; } }; const deleteBOMItem = async (bomId: number) => { try { // API 호출 const response = await itemMasterApi.bomItems.delete(bomId); if (!response.success) { throw new Error(response.message || 'BOM 항목 삭제 실패'); } // state 업데이트 setItemPages(prev => prev.map(page => ({ ...page, sections: page.sections.map(section => ({ ...section, bomItems: (section.bomItems || []).filter(bom => bom.id !== bomId) })) }))); console.log('[ItemMasterContext] BOM 항목 삭제 성공:', bomId); } catch (error) { const errorMessage = getErrorMessage(error); console.error('[ItemMasterContext] BOM 항목 삭제 실패:', errorMessage); throw error; } }; // 캐시 및 데이터 초기화 함수 const clearCache = () => { if (cache) { cache.clear(); console.log('[ItemMasterContext] TenantAwareCache cleared'); } }; const resetAllData = () => { // 모든 state를 초기값으로 reset setItemMasters(initialItemMasters); setSpecificationMasters(initialSpecificationMasters); setMaterialItemNames(initialMaterialItemNames); setItemCategories(initialItemCategories); setItemUnits(initialItemUnits); setItemMaterials(initialItemMaterials); setSurfaceTreatments(initialSurfaceTreatments); setPartTypeOptions(initialPartTypeOptions); setPartUsageOptions(initialPartUsageOptions); setGuideRailOptions(initialGuideRailOptions); setSectionTemplates([]); setItemMasterFields(initialItemMasterFields); setItemPages(initialItemPages); // TenantAwareCache도 정리 clearCache(); console.log('[ItemMasterContext] All data reset to initial state'); }; // Context value const value: ItemMasterContextType = { itemMasters, addItemMaster, updateItemMaster, deleteItemMaster, specificationMasters, addSpecificationMaster, updateSpecificationMaster, deleteSpecificationMaster, materialItemNames, addMaterialItemName, updateMaterialItemName, deleteMaterialItemName, itemMasterFields, loadItemMasterFields, addItemMasterField, updateItemMasterField, deleteItemMasterField, sectionTemplates, loadSectionTemplates, addSectionTemplate, updateSectionTemplate, deleteSectionTemplate, itemPages, loadItemPages, addItemPage, updateItemPage, deleteItemPage, reorderPages, addSectionToPage, updateSection, deleteSection, reorderSections, addFieldToSection, updateField, deleteField, reorderFields, addBOMItem, updateBOMItem, deleteBOMItem, itemCategories, itemUnits, itemMaterials, surfaceTreatments, partTypeOptions, partUsageOptions, guideRailOptions, addItemCategory, updateItemCategory, deleteItemCategory, addItemUnit, updateItemUnit, deleteItemUnit, addItemMaterial, updateItemMaterial, deleteItemMaterial, addSurfaceTreatment, updateSurfaceTreatment, deleteSurfaceTreatment, addPartTypeOption, updatePartTypeOption, deletePartTypeOption, addPartUsageOption, updatePartUsageOption, deletePartUsageOption, addGuideRailOption, updateGuideRailOption, deleteGuideRailOption, clearCache, resetAllData, tenantId, }; return ( {children} ); } // Custom hook export function useItemMaster() { const context = useContext(ItemMasterContext); if (context === undefined) { throw new Error('useItemMaster must be used within an ItemMasterProvider'); } return context; }