2025-11-23 16:10:27 +09:00
|
|
|
'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[]; // 각 컬럼의 이름
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-25 21:07:10 +09:00
|
|
|
// 항목 마스터 (재사용 가능한 항목 템플릿) - MasterFieldResponse와 정확히 일치
|
2025-11-23 16:10:27 +09:00
|
|
|
export interface ItemMasterField {
|
2025-11-25 21:07:10 +09:00
|
|
|
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<string, any> | null; // 검증 규칙
|
|
|
|
|
properties: Record<string, any> | null; // 추가 속성
|
|
|
|
|
created_by: number | null;
|
|
|
|
|
updated_by: number | null;
|
|
|
|
|
created_at: string;
|
|
|
|
|
updated_at: string;
|
2025-11-23 16:10:27 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 조건부 표시 설정
|
|
|
|
|
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<string, any> | null; // 조건부 표시 설정 (displayCondition → display_condition)
|
|
|
|
|
validation_rules?: Record<string, any> | null; // 검증 규칙
|
|
|
|
|
options?: Array<{ label: string; value: string }> | null; // dropdown 옵션
|
|
|
|
|
properties?: Record<string, any> | 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)
|
2025-11-25 21:07:10 +09:00
|
|
|
item_type: 'FG' | 'PT' | 'SM' | 'RM' | 'CS'; // 품목유형
|
2025-11-23 16:10:27 +09:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-25 21:07:10 +09:00
|
|
|
// 템플릿 필드 (로컬 관리용 - 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 요구사항에 맞춤
|
2025-11-23 16:10:27 +09:00
|
|
|
export interface SectionTemplate {
|
2025-11-25 21:07:10 +09:00
|
|
|
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;
|
2025-11-23 16:10:27 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ===== Context Type =====
|
|
|
|
|
interface ItemMasterContextType {
|
|
|
|
|
// 품목 마스터 데이터
|
|
|
|
|
itemMasters: ItemMaster[];
|
|
|
|
|
addItemMaster: (item: ItemMaster) => void;
|
|
|
|
|
updateItemMaster: (id: string, updates: Partial<ItemMaster>) => void;
|
|
|
|
|
deleteItemMaster: (id: string) => void;
|
|
|
|
|
|
|
|
|
|
// 규격 마스터 데이터 (원자재/부자재)
|
|
|
|
|
specificationMasters: SpecificationMaster[];
|
|
|
|
|
addSpecificationMaster: (spec: SpecificationMaster) => void;
|
|
|
|
|
updateSpecificationMaster: (id: string, updates: Partial<SpecificationMaster>) => void;
|
|
|
|
|
deleteSpecificationMaster: (id: string) => void;
|
|
|
|
|
|
|
|
|
|
// 원자재/부자재 품목명 마스터 데이터
|
|
|
|
|
materialItemNames: MaterialItemName[];
|
|
|
|
|
addMaterialItemName: (item: MaterialItemName) => void;
|
|
|
|
|
updateMaterialItemName: (id: string, updates: Partial<MaterialItemName>) => void;
|
|
|
|
|
deleteMaterialItemName: (id: string) => void;
|
|
|
|
|
|
|
|
|
|
// 품목기준관리 - 마스터 항목
|
|
|
|
|
itemMasterFields: ItemMasterField[];
|
2025-11-25 21:07:10 +09:00
|
|
|
loadItemMasterFields: (fields: ItemMasterField[]) => void; // 초기 데이터 로딩용 (API 호출 없음)
|
2025-11-23 16:10:27 +09:00
|
|
|
addItemMasterField: (field: Omit<ItemMasterField, 'id' | 'created_at' | 'updated_at'>) => Promise<void>;
|
|
|
|
|
updateItemMasterField: (id: number, updates: Partial<ItemMasterField>) => Promise<void>;
|
|
|
|
|
deleteItemMasterField: (id: number) => Promise<void>;
|
|
|
|
|
|
|
|
|
|
// 품목기준관리 - 섹션 템플릿
|
|
|
|
|
sectionTemplates: SectionTemplate[];
|
2025-11-25 21:07:10 +09:00
|
|
|
loadSectionTemplates: (templates: SectionTemplate[]) => void; // 초기 데이터 로딩용 (API 호출 없음)
|
2025-11-23 16:10:27 +09:00
|
|
|
addSectionTemplate: (template: Omit<SectionTemplate, 'id' | 'created_at' | 'updated_at'>) => Promise<void>;
|
|
|
|
|
updateSectionTemplate: (id: number, updates: Partial<SectionTemplate>) => Promise<void>;
|
|
|
|
|
deleteSectionTemplate: (id: number) => Promise<void>;
|
|
|
|
|
|
|
|
|
|
// 품목기준관리 계층구조
|
|
|
|
|
itemPages: ItemPage[];
|
2025-11-25 21:07:10 +09:00
|
|
|
loadItemPages: (pages: ItemPage[]) => void; // 초기 데이터 로딩용 (API 호출 없음)
|
|
|
|
|
addItemPage: (page: Omit<ItemPage, 'id' | 'created_at' | 'updated_at'>) => Promise<ItemPage>;
|
2025-11-23 16:10:27 +09:00
|
|
|
updateItemPage: (id: number, updates: Partial<ItemPage>) => Promise<void>;
|
|
|
|
|
deleteItemPage: (id: number) => Promise<void>;
|
|
|
|
|
reorderPages: (newOrder: Array<{ id: number; order_no: number }>) => Promise<void>;
|
|
|
|
|
addSectionToPage: (pageId: number, sectionData: Omit<ItemSection, 'id' | 'created_at' | 'updated_at'>) => Promise<void>;
|
|
|
|
|
updateSection: (sectionId: number, updates: Partial<ItemSection>) => Promise<void>;
|
|
|
|
|
deleteSection: (sectionId: number) => Promise<void>;
|
|
|
|
|
reorderSections: (pageId: number, sectionIds: number[]) => Promise<void>;
|
|
|
|
|
addFieldToSection: (sectionId: number, fieldData: Omit<ItemField, 'id' | 'created_at' | 'updated_at'>) => Promise<void>;
|
|
|
|
|
updateField: (fieldId: number, updates: Partial<ItemField>) => Promise<void>;
|
|
|
|
|
deleteField: (fieldId: number) => Promise<void>;
|
|
|
|
|
reorderFields: (sectionId: number, fieldIds: number[]) => Promise<void>;
|
|
|
|
|
|
|
|
|
|
// BOM 관리
|
|
|
|
|
addBOMItem: (sectionId: number, bomData: Omit<BOMItem, 'id' | 'created_at' | 'updated_at'>) => Promise<void>;
|
|
|
|
|
updateBOMItem: (bomId: number, updates: Partial<BOMItem>) => Promise<void>;
|
|
|
|
|
deleteBOMItem: (bomId: number) => Promise<void>;
|
|
|
|
|
|
|
|
|
|
// 품목 기준정보 관리
|
|
|
|
|
itemCategories: ItemCategory[];
|
|
|
|
|
itemUnits: ItemUnit[];
|
|
|
|
|
itemMaterials: ItemMaterial[];
|
|
|
|
|
surfaceTreatments: SurfaceTreatment[];
|
|
|
|
|
partTypeOptions: PartTypeOption[];
|
|
|
|
|
partUsageOptions: PartUsageOption[];
|
|
|
|
|
guideRailOptions: GuideRailOption[];
|
|
|
|
|
|
|
|
|
|
addItemCategory: (category: ItemCategory) => void;
|
|
|
|
|
updateItemCategory: (id: string, updates: Partial<ItemCategory>) => void;
|
|
|
|
|
deleteItemCategory: (id: string) => void;
|
|
|
|
|
|
|
|
|
|
addItemUnit: (unit: ItemUnit) => void;
|
|
|
|
|
updateItemUnit: (id: string, updates: Partial<ItemUnit>) => void;
|
|
|
|
|
deleteItemUnit: (id: string) => void;
|
|
|
|
|
|
|
|
|
|
addItemMaterial: (material: ItemMaterial) => void;
|
|
|
|
|
updateItemMaterial: (id: string, updates: Partial<ItemMaterial>) => void;
|
|
|
|
|
deleteItemMaterial: (id: string) => void;
|
|
|
|
|
|
|
|
|
|
addSurfaceTreatment: (treatment: SurfaceTreatment) => void;
|
|
|
|
|
updateSurfaceTreatment: (id: string, updates: Partial<SurfaceTreatment>) => void;
|
|
|
|
|
deleteSurfaceTreatment: (id: string) => void;
|
|
|
|
|
|
|
|
|
|
addPartTypeOption: (option: PartTypeOption) => void;
|
|
|
|
|
updatePartTypeOption: (id: string, updates: Partial<PartTypeOption>) => void;
|
|
|
|
|
deletePartTypeOption: (id: string) => void;
|
|
|
|
|
|
|
|
|
|
addPartUsageOption: (option: PartUsageOption) => void;
|
|
|
|
|
updatePartUsageOption: (id: string, updates: Partial<PartUsageOption>) => void;
|
|
|
|
|
deletePartUsageOption: (id: string) => void;
|
|
|
|
|
|
|
|
|
|
addGuideRailOption: (option: GuideRailOption) => void;
|
|
|
|
|
updateGuideRailOption: (id: string, updates: Partial<GuideRailOption>) => void;
|
|
|
|
|
deleteGuideRailOption: (id: string) => void;
|
|
|
|
|
|
|
|
|
|
// 캐시 및 데이터 초기화
|
|
|
|
|
clearCache: () => void; // TenantAwareCache 정리
|
|
|
|
|
resetAllData: () => void; // 모든 state 초기화
|
2025-11-25 21:07:10 +09:00
|
|
|
|
|
|
|
|
// 테넌트 정보
|
|
|
|
|
tenantId: number | undefined; // 현재 로그인한 사용자의 테넌트 ID
|
2025-11-23 16:10:27 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create context
|
|
|
|
|
const ItemMasterContext = createContext<ItemMasterContextType | undefined>(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<ItemMaster[]>(initialItemMasters);
|
|
|
|
|
const [specificationMasters, setSpecificationMasters] = useState<SpecificationMaster[]>(initialSpecificationMasters);
|
|
|
|
|
const [materialItemNames, setMaterialItemNames] = useState<MaterialItemName[]>(initialMaterialItemNames);
|
|
|
|
|
const [itemCategories, setItemCategories] = useState<ItemCategory[]>(initialItemCategories);
|
|
|
|
|
const [itemUnits, setItemUnits] = useState<ItemUnit[]>(initialItemUnits);
|
|
|
|
|
const [itemMaterials, setItemMaterials] = useState<ItemMaterial[]>(initialItemMaterials);
|
|
|
|
|
const [surfaceTreatments, setSurfaceTreatments] = useState<SurfaceTreatment[]>(initialSurfaceTreatments);
|
|
|
|
|
const [partTypeOptions, setPartTypeOptions] = useState<PartTypeOption[]>(initialPartTypeOptions);
|
|
|
|
|
const [partUsageOptions, setPartUsageOptions] = useState<PartUsageOption[]>(initialPartUsageOptions);
|
|
|
|
|
const [guideRailOptions, setGuideRailOptions] = useState<GuideRailOption[]>(initialGuideRailOptions);
|
|
|
|
|
const [sectionTemplates, setSectionTemplates] = useState<SectionTemplate[]>([]);
|
|
|
|
|
const [itemMasterFields, setItemMasterFields] = useState<ItemMasterField[]>(initialItemMasterFields);
|
|
|
|
|
const [itemPages, setItemPages] = useState<ItemPage[]>(initialItemPages);
|
|
|
|
|
|
|
|
|
|
// ✅ TenantAwareCache에서 초기 데이터 로드
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!cache) return; // tenant.id 없으면 초기 데이터 유지
|
|
|
|
|
|
|
|
|
|
// ItemMasters
|
|
|
|
|
const cachedItemMasters = cache.get<ItemMaster[]>('itemMasters');
|
|
|
|
|
if (cachedItemMasters) setItemMasters(cachedItemMasters);
|
|
|
|
|
|
|
|
|
|
// SpecificationMasters (버전 체크)
|
|
|
|
|
if (cache.isVersionMatch('specificationMasters', '1.0')) {
|
|
|
|
|
const cachedSpecs = cache.get<SpecificationMaster[]>('specificationMasters');
|
|
|
|
|
if (cachedSpecs) setSpecificationMasters(cachedSpecs);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MaterialItemNames (버전 체크)
|
|
|
|
|
if (cache.isVersionMatch('materialItemNames', '1.1')) {
|
|
|
|
|
const cachedMaterials = cache.get<MaterialItemName[]>('materialItemNames');
|
|
|
|
|
if (cachedMaterials) setMaterialItemNames(cachedMaterials);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ItemCategories
|
|
|
|
|
const cachedCategories = cache.get<ItemCategory[]>('itemCategories');
|
|
|
|
|
if (cachedCategories) setItemCategories(cachedCategories);
|
|
|
|
|
|
|
|
|
|
// ItemUnits
|
|
|
|
|
const cachedUnits = cache.get<ItemUnit[]>('itemUnits');
|
|
|
|
|
if (cachedUnits) setItemUnits(cachedUnits);
|
|
|
|
|
|
|
|
|
|
// ItemMaterials
|
|
|
|
|
const cachedMaterials = cache.get<ItemMaterial[]>('itemMaterials');
|
|
|
|
|
if (cachedMaterials) setItemMaterials(cachedMaterials);
|
|
|
|
|
|
|
|
|
|
// SurfaceTreatments
|
|
|
|
|
const cachedTreatments = cache.get<SurfaceTreatment[]>('surfaceTreatments');
|
|
|
|
|
if (cachedTreatments) setSurfaceTreatments(cachedTreatments);
|
|
|
|
|
|
|
|
|
|
// PartTypeOptions
|
|
|
|
|
const cachedPartTypes = cache.get<PartTypeOption[]>('partTypeOptions');
|
|
|
|
|
if (cachedPartTypes) setPartTypeOptions(cachedPartTypes);
|
|
|
|
|
|
|
|
|
|
// PartUsageOptions
|
|
|
|
|
const cachedPartUsages = cache.get<PartUsageOption[]>('partUsageOptions');
|
|
|
|
|
if (cachedPartUsages) setPartUsageOptions(cachedPartUsages);
|
|
|
|
|
|
|
|
|
|
// GuideRailOptions
|
|
|
|
|
const cachedGuideRails = cache.get<GuideRailOption[]>('guideRailOptions');
|
|
|
|
|
if (cachedGuideRails) setGuideRailOptions(cachedGuideRails);
|
|
|
|
|
|
|
|
|
|
// SectionTemplates
|
|
|
|
|
const cachedTemplates = cache.get<SectionTemplate[]>('sectionTemplates');
|
|
|
|
|
if (cachedTemplates) setSectionTemplates(cachedTemplates);
|
|
|
|
|
|
|
|
|
|
// ItemMasterFields
|
|
|
|
|
const cachedFields = cache.get<ItemMasterField[]>('itemMasterFields');
|
|
|
|
|
if (cachedFields) setItemMasterFields(cachedFields);
|
|
|
|
|
|
|
|
|
|
// ItemPages
|
|
|
|
|
const cachedPages = cache.get<ItemPage[]>('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<ItemMaster>) => {
|
|
|
|
|
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<SpecificationMaster>) => {
|
|
|
|
|
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<MaterialItemName>) => {
|
|
|
|
|
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<ItemCategory>) => {
|
|
|
|
|
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<ItemUnit>) => {
|
|
|
|
|
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<ItemMaterial>) => {
|
|
|
|
|
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<SurfaceTreatment>) => {
|
|
|
|
|
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<PartTypeOption>) => {
|
|
|
|
|
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<PartUsageOption>) => {
|
|
|
|
|
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<GuideRailOption>) => {
|
|
|
|
|
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)
|
2025-11-25 21:07:10 +09:00
|
|
|
/**
|
|
|
|
|
* 초기 데이터 로딩용: API 호출 없이 마스터 필드를 state에 로드 (덮어쓰기)
|
|
|
|
|
*/
|
|
|
|
|
const loadItemMasterFields = (fields: ItemMasterField[]) => {
|
|
|
|
|
setItemMasterFields(fields);
|
|
|
|
|
console.log('[ItemMasterContext] 마스터 필드 로드 완료:', fields.length);
|
|
|
|
|
};
|
|
|
|
|
|
2025-11-23 16:10:27 +09:00
|
|
|
const addItemMasterField = async (field: Omit<ItemMasterField, 'id' | 'created_at' | 'updated_at'>) => {
|
|
|
|
|
try {
|
|
|
|
|
// API 호출
|
|
|
|
|
const response = await itemMasterApi.masterFields.create({
|
|
|
|
|
field_name: field.field_name,
|
|
|
|
|
field_type: field.field_type,
|
2025-11-25 21:07:10 +09:00
|
|
|
category: field.category ?? undefined,
|
|
|
|
|
description: field.description ?? undefined,
|
2025-11-23 16:10:27 +09:00
|
|
|
is_common: field.is_common,
|
2025-11-25 21:07:10 +09:00
|
|
|
default_value: field.default_value ?? undefined,
|
|
|
|
|
options: field.options ?? undefined,
|
|
|
|
|
validation_rules: field.validation_rules ?? undefined,
|
|
|
|
|
properties: field.properties ?? undefined,
|
2025-11-23 16:10:27 +09:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
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,
|
2025-11-25 21:07:10 +09:00
|
|
|
created_by: response.data.created_by,
|
|
|
|
|
updated_by: response.data.updated_by,
|
2025-11-23 16:10:27 +09:00
|
|
|
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<ItemMasterField>) => {
|
|
|
|
|
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
|
2025-11-25 21:07:10 +09:00
|
|
|
/**
|
|
|
|
|
* 초기 데이터 로딩용: API 호출 없이 섹션 템플릿을 state에 로드 (덮어쓰기)
|
|
|
|
|
*/
|
|
|
|
|
const loadSectionTemplates = (templates: SectionTemplate[]) => {
|
|
|
|
|
setSectionTemplates(templates);
|
|
|
|
|
console.log('[ItemMasterContext] 섹션 템플릿 로드 완료:', templates.length);
|
|
|
|
|
};
|
|
|
|
|
|
2025-11-23 16:10:27 +09:00
|
|
|
const addSectionTemplate = async (template: Omit<SectionTemplate, 'id' | 'created_at' | 'updated_at'>) => {
|
|
|
|
|
try {
|
2025-11-25 21:07:10 +09:00
|
|
|
// 프론트엔드 형식 → API 형식 변환
|
|
|
|
|
// template_name → title, section_type → type
|
|
|
|
|
const apiType = template.section_type === 'BOM' ? 'bom' : 'fields';
|
|
|
|
|
|
2025-11-23 16:10:27 +09:00
|
|
|
const response = await itemMasterApi.templates.create({
|
2025-11-25 21:07:10 +09:00
|
|
|
title: template.template_name,
|
|
|
|
|
type: apiType,
|
|
|
|
|
description: template.description ?? undefined,
|
|
|
|
|
is_default: false, // 기본값
|
2025-11-23 16:10:27 +09:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!response.success || !response.data) {
|
|
|
|
|
throw new Error(response.message || '섹션 템플릿 생성 실패');
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-25 21:07:10 +09:00
|
|
|
// API 응답 → 프론트엔드 형식 변환
|
|
|
|
|
// title → template_name, type → section_type
|
|
|
|
|
const SECTION_TYPE_MAP: Record<string, 'BASIC' | 'BOM' | 'CUSTOM'> = {
|
|
|
|
|
fields: 'BASIC',
|
|
|
|
|
bom: 'BOM',
|
|
|
|
|
};
|
|
|
|
|
|
2025-11-23 16:10:27 +09:00
|
|
|
const newTemplate: SectionTemplate = {
|
|
|
|
|
id: response.data.id,
|
|
|
|
|
tenant_id: response.data.tenant_id,
|
2025-11-25 21:07:10 +09:00
|
|
|
template_name: response.data.title,
|
|
|
|
|
section_type: SECTION_TYPE_MAP[response.data.type] || 'BASIC',
|
2025-11-23 16:10:27 +09:00
|
|
|
description: response.data.description,
|
2025-11-25 21:07:10 +09:00
|
|
|
default_fields: null,
|
|
|
|
|
category: template.category,
|
|
|
|
|
fields: template.fields,
|
|
|
|
|
bomItems: template.bomItems,
|
|
|
|
|
created_by: response.data.created_by,
|
|
|
|
|
updated_by: response.data.updated_by,
|
2025-11-23 16:10:27 +09:00
|
|
|
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<SectionTemplate>) => {
|
|
|
|
|
try {
|
2025-11-25 21:07:10 +09:00
|
|
|
// 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<string, 'BASIC' | 'BOM' | 'CUSTOM'> = {
|
|
|
|
|
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);
|
2025-11-23 16:10:27 +09:00
|
|
|
}
|
|
|
|
|
|
2025-11-25 21:07:10 +09:00
|
|
|
// 로컬 전용 필드 업데이트 (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)));
|
|
|
|
|
}
|
2025-11-23 16:10:27 +09:00
|
|
|
} 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
|
2025-11-25 21:07:10 +09:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 초기 데이터 로딩용: API 호출 없이 페이지 데이터를 state에 로드 (덮어쓰기)
|
|
|
|
|
* (이미 백엔드에서 받아온 데이터를 로드할 때 사용)
|
|
|
|
|
*/
|
|
|
|
|
const loadItemPages = (pages: ItemPage[]) => {
|
|
|
|
|
setItemPages(pages); // 덮어쓰기 (append가 아님!)
|
|
|
|
|
console.log('[ItemMasterContext] 페이지 데이터 로드 완료:', pages.length);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 새 페이지 생성: API 호출 + state 업데이트
|
|
|
|
|
* (사용자가 새 페이지를 만들 때 사용)
|
|
|
|
|
* @returns 생성된 페이지 반환
|
|
|
|
|
*/
|
|
|
|
|
const addItemPage = async (pageData: Omit<ItemPage, 'id' | 'created_at' | 'updated_at'>): Promise<ItemPage> => {
|
2025-11-23 16:10:27 +09:00
|
|
|
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 업데이트
|
2025-11-25 21:07:10 +09:00
|
|
|
const newPage = transformPageResponse(response);
|
2025-11-23 16:10:27 +09:00
|
|
|
setItemPages(prev => [...prev, newPage]);
|
|
|
|
|
|
|
|
|
|
console.log('[ItemMasterContext] 페이지 생성 성공:', newPage);
|
2025-11-25 21:07:10 +09:00
|
|
|
return newPage;
|
2025-11-23 16:10:27 +09:00
|
|
|
} catch (error) {
|
|
|
|
|
const errorMessage = getErrorMessage(error);
|
|
|
|
|
console.error('[ItemMasterContext] 페이지 생성 실패:', errorMessage);
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const updateItemPage = async (id: number, updates: Partial<ItemPage>) => {
|
|
|
|
|
try {
|
|
|
|
|
// API 요청 데이터 변환
|
|
|
|
|
const requestData: Partial<ItemPageRequest> = {};
|
|
|
|
|
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<ItemSection, 'id' | 'created_at' | 'updated_at'>) => {
|
|
|
|
|
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<ItemSection>) => {
|
|
|
|
|
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, {
|
2025-11-25 21:07:10 +09:00
|
|
|
section_orders: sectionIds.map((id, index) => ({ id, order_no: index }))
|
2025-11-23 16:10:27 +09:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
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<ItemField, 'id' | 'created_at' | 'updated_at'>) => {
|
|
|
|
|
try {
|
2025-11-25 21:07:10 +09:00
|
|
|
// API 호출 (null → undefined 변환)
|
2025-11-23 16:10:27 +09:00
|
|
|
const response = await itemMasterApi.fields.create(sectionId, {
|
|
|
|
|
field_name: fieldData.field_name,
|
|
|
|
|
field_type: fieldData.field_type,
|
|
|
|
|
is_required: fieldData.is_required,
|
2025-11-25 21:07:10 +09:00
|
|
|
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,
|
2025-11-23 16:10:27 +09:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
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<ItemField>) => {
|
|
|
|
|
try {
|
2025-11-25 21:07:10 +09:00
|
|
|
// API 호출 (null → undefined 변환)
|
|
|
|
|
// 주의: display_condition은 현재 백엔드에서 지원하는 형식과 프론트엔드 형식이 다르므로 API에 전송하지 않음
|
|
|
|
|
// 백엔드 형식: {"field_id": "1", "operator": "equals", "value": "true"}
|
|
|
|
|
// 프론트엔드 형식: {"targetType": "field", "fieldConditions": [...], "sectionIds": [...]}
|
2025-11-23 16:10:27 +09:00
|
|
|
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;
|
2025-11-25 21:07:10 +09:00
|
|
|
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;
|
2025-11-23 16:10:27 +09:00
|
|
|
|
|
|
|
|
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, {
|
2025-11-25 21:07:10 +09:00
|
|
|
field_orders: fieldIds.map((id, index) => ({ id, order_no: index }))
|
2025-11-23 16:10:27 +09:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
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<BOMItem, 'id' | 'created_at' | 'updated_at'>) => {
|
|
|
|
|
try {
|
2025-11-25 21:07:10 +09:00
|
|
|
// API 호출 (null → undefined 변환)
|
2025-11-23 16:10:27 +09:00
|
|
|
const response = await itemMasterApi.bomItems.create(sectionId, {
|
2025-11-25 21:07:10 +09:00
|
|
|
item_code: bomData.item_code ?? undefined,
|
2025-11-23 16:10:27 +09:00
|
|
|
item_name: bomData.item_name,
|
|
|
|
|
quantity: bomData.quantity,
|
2025-11-25 21:07:10 +09:00
|
|
|
unit: bomData.unit ?? undefined,
|
|
|
|
|
unit_price: bomData.unit_price ?? undefined,
|
|
|
|
|
total_price: bomData.total_price ?? undefined,
|
|
|
|
|
spec: bomData.spec ?? undefined,
|
|
|
|
|
note: bomData.note ?? undefined,
|
2025-11-23 16:10:27 +09:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
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<BOMItem>) => {
|
|
|
|
|
try {
|
2025-11-25 21:07:10 +09:00
|
|
|
// API 호출 (null → undefined 변환)
|
2025-11-23 16:10:27 +09:00
|
|
|
const requestData: any = {};
|
2025-11-25 21:07:10 +09:00
|
|
|
if (updates.item_code !== undefined) requestData.item_code = updates.item_code ?? undefined;
|
2025-11-23 16:10:27 +09:00
|
|
|
if (updates.item_name !== undefined) requestData.item_name = updates.item_name;
|
|
|
|
|
if (updates.quantity !== undefined) requestData.quantity = updates.quantity;
|
2025-11-25 21:07:10 +09:00
|
|
|
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;
|
2025-11-23 16:10:27 +09:00
|
|
|
|
|
|
|
|
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,
|
2025-11-25 21:07:10 +09:00
|
|
|
loadItemMasterFields,
|
2025-11-23 16:10:27 +09:00
|
|
|
addItemMasterField,
|
|
|
|
|
updateItemMasterField,
|
|
|
|
|
deleteItemMasterField,
|
|
|
|
|
|
|
|
|
|
sectionTemplates,
|
2025-11-25 21:07:10 +09:00
|
|
|
loadSectionTemplates,
|
2025-11-23 16:10:27 +09:00
|
|
|
addSectionTemplate,
|
|
|
|
|
updateSectionTemplate,
|
|
|
|
|
deleteSectionTemplate,
|
|
|
|
|
|
|
|
|
|
itemPages,
|
2025-11-25 21:07:10 +09:00
|
|
|
loadItemPages,
|
2025-11-23 16:10:27 +09:00
|
|
|
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,
|
2025-11-25 21:07:10 +09:00
|
|
|
|
|
|
|
|
tenantId,
|
2025-11-23 16:10:27 +09:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<ItemMasterContext.Provider value={value}>
|
|
|
|
|
{children}
|
|
|
|
|
</ItemMasterContext.Provider>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Custom hook
|
|
|
|
|
export function useItemMaster() {
|
|
|
|
|
const context = useContext(ItemMasterContext);
|
|
|
|
|
if (context === undefined) {
|
|
|
|
|
throw new Error('useItemMaster must be used within an ItemMasterProvider');
|
|
|
|
|
}
|
|
|
|
|
return context;
|
|
|
|
|
}
|