// API 응답 데이터 변환 헬퍼 // API 응답 (snake_case + 특정 값) ↔ Frontend State (snake_case + 변환된 값) import type { ItemPageResponse, ItemSectionResponse, ItemFieldResponse, BomItemResponse, SectionTemplateResponse, MasterFieldResponse, UnitOptionResponse, CustomTabResponse, } from '@/types/item-master-api'; import type { ItemPage, ItemSection, ItemField, BOMItem, SectionTemplate, ItemMasterField, } from '@/contexts/ItemMasterContext'; // ============================================ // 타입 값 변환 매핑 // ============================================ /** * API section type → Frontend section_type 변환 * API: 'fields' | 'bom' * Frontend: 'BASIC' | 'BOM' | 'CUSTOM' */ const SECTION_TYPE_MAP: Record = { fields: 'BASIC', bom: 'BOM', }; /** * Frontend section_type → API section type 변환 */ const SECTION_TYPE_REVERSE_MAP: Record = { BASIC: 'fields', BOM: 'bom', CUSTOM: 'fields', // CUSTOM은 fields로 매핑 }; // 2025-11-26: field_type은 API와 Frontend가 동일한 값을 사용하므로 변환 불필요 // API & Frontend: 'textbox' | 'number' | 'dropdown' | 'checkbox' | 'date' | 'textarea' type FieldType = 'textbox' | 'number' | 'dropdown' | 'checkbox' | 'date' | 'textarea'; /** * field_type 기본값 반환 (알 수 없는 값일 경우) */ const getFieldType = (type: string): FieldType => { const validTypes: FieldType[] = ['textbox', 'number', 'dropdown', 'checkbox', 'date', 'textarea']; return validTypes.includes(type as FieldType) ? (type as FieldType) : 'textbox'; }; // ============================================ // API Response → Frontend State 변환 // ============================================ /** * ItemPageResponse → ItemPage 변환 */ export const transformPageResponse = ( response: ItemPageResponse ): ItemPage => { return { id: response.id, tenant_id: response.tenant_id, page_name: response.page_name, item_type: response.item_type as 'FG' | 'PT' | 'SM' | 'RM' | 'CS', description: response.description ?? null, // 2025-11-26 추가 absolute_path: response.absolute_path || '', // null일 경우 빈 문자열 is_active: response.is_active, order_no: response.order_no ?? 0, // 2025-11-26 추가 sections: response.sections?.map(transformSectionResponse) || [], created_by: response.created_by, updated_by: response.updated_by, created_at: response.created_at, updated_at: response.updated_at, }; }; /** * ItemSectionResponse → ItemSection 변환 * 주요 변환: type → section_type, 값 변환 (fields → BASIC, bom → BOM) * 2025-11-26: group_id, is_template, is_default, description 추가 (section_templates 통합) */ export const transformSectionResponse = ( response: ItemSectionResponse ): ItemSection => { return { id: response.id, tenant_id: response.tenant_id, group_id: response.group_id, // 2025-11-26 추가: 독립 섹션 그룹화용 page_id: response.page_id, // null이면 독립 섹션 title: response.title, section_type: SECTION_TYPE_MAP[response.type] || 'BASIC', // 타입 값 변환 description: response.description, // 2025-11-26 추가 order_no: response.order_no, is_template: response.is_template, // 2025-11-26 추가: 템플릿 여부 (section_templates 통합) is_default: response.is_default, // 2025-11-26 추가: 기본 템플릿 여부 fields: response.fields?.map(transformFieldResponse) || [], // 2025-11-27: 백엔드가 bom_items (snake_case)로 반환하므로 둘 다 체크 bom_items: (response.bom_items || response.bomItems)?.map(transformBomItemResponse) || [], created_by: response.created_by, updated_by: response.updated_by, created_at: response.created_at, updated_at: response.updated_at, }; }; /** * ItemFieldResponse → ItemField 변환 * 2025-11-26: field_type은 API와 동일한 값 사용 (변환 불필요) */ export const transformFieldResponse = ( response: ItemFieldResponse ): ItemField => { return { id: response.id, tenant_id: response.tenant_id, section_id: response.section_id, field_name: response.field_name, field_key: response.field_key, // 2025-11-28: field_key 추가 (형식: {ID}_{사용자입력}) field_type: getFieldType(response.field_type), // API와 동일한 타입 order_no: response.order_no, is_required: response.is_required, placeholder: response.placeholder, default_value: response.default_value, display_condition: response.display_condition, validation_rules: response.validation_rules, options: response.options, properties: response.properties, created_by: response.created_by, updated_by: response.updated_by, created_at: response.created_at, updated_at: response.updated_at, }; }; /** * BomItemResponse → BOMItem 변환 */ export const transformBomItemResponse = ( response: BomItemResponse ): BOMItem => { return { id: response.id, tenant_id: response.tenant_id, section_id: response.section_id, item_code: response.item_code, item_name: response.item_name, quantity: response.quantity, unit: response.unit, unit_price: response.unit_price, total_price: response.total_price, spec: response.spec, note: response.note, created_by: response.created_by, updated_by: response.updated_by, created_at: response.created_at, updated_at: response.updated_at, }; }; /** * SectionTemplateResponse → SectionTemplate 변환 * 주요 변환: title → template_name, type → section_type, 값 변환 */ export const transformSectionTemplateResponse = ( response: SectionTemplateResponse ): SectionTemplate => { return { id: response.id, tenant_id: response.tenant_id, template_name: response.title, // 필드명 변환 section_type: SECTION_TYPE_MAP[response.type] || 'BASIC', // 타입 값 변환 description: response.description, default_fields: null, // API 응답에 없으므로 null created_by: response.created_by, updated_by: response.updated_by, created_at: response.created_at, updated_at: response.updated_at, }; }; /** * MasterFieldResponse → ItemMasterField 변환 * 2025-11-26: field_type은 API와 동일한 값 사용, 속성명도 API와 통일 */ export const transformMasterFieldResponse = ( response: MasterFieldResponse ): ItemMasterField => { return { id: response.id, tenant_id: response.tenant_id, field_name: response.field_name, field_key: response.field_key ?? null, // 2025-11-28: field_key 추가 (형식: {ID}_{사용자입력}) field_type: getFieldType(response.field_type), // API와 동일한 타입 category: response.category, description: response.description, is_common: response.is_common ?? false, // 공통 필드 여부 default_value: response.default_value ?? null, // 기본값 options: response.options ?? null, // dropdown 옵션 validation_rules: response.validation_rules ?? null, // 검증 규칙 properties: response.properties ?? null, // 추가 속성 created_by: response.created_by, updated_by: response.updated_by, created_at: response.created_at, updated_at: response.updated_at, }; }; // ============================================ // Frontend State → API Request 변환 // ============================================ /** * ItemSection → ItemSectionRequest 변환 * 주요 변환: section_type → type, 값 역변환 (BASIC → fields, BOM → bom) */ export const transformSectionToRequest = ( section: Partial ): { title: string; type: 'fields' | 'bom' } => { return { title: section.title || '', type: section.section_type ? SECTION_TYPE_REVERSE_MAP[section.section_type] || 'fields' : 'fields', }; }; /** * ItemField → ItemFieldRequest 변환 * 2025-11-26: field_type은 API와 동일한 값 사용 (변환 불필요) */ export const transformFieldToRequest = (field: Partial) => { return { field_name: field.field_name || '', field_type: field.field_type || 'textbox', // API와 동일한 타입 is_required: field.is_required ?? false, placeholder: field.placeholder || null, default_value: field.default_value || null, display_condition: field.display_condition || null, validation_rules: field.validation_rules || null, options: field.options || null, properties: field.properties || null, }; }; /** * BOMItem → BomItemRequest 변환 */ export const transformBomItemToRequest = (bomItem: Partial) => { return { item_code: bomItem.item_code || undefined, item_name: bomItem.item_name || '', quantity: bomItem.quantity || 0, unit: bomItem.unit || undefined, unit_price: bomItem.unit_price || undefined, total_price: bomItem.total_price || undefined, spec: bomItem.spec || undefined, note: bomItem.note || undefined, }; }; /** * SectionTemplate → SectionTemplateRequest 변환 * 주요 변환: template_name → title, section_type → type, 값 역변환 */ export const transformSectionTemplateToRequest = ( template: Partial ) => { return { title: template.template_name || '', // 필드명 역변환 type: template.section_type ? SECTION_TYPE_REVERSE_MAP[template.section_type] || 'fields' : 'fields', description: template.description || undefined, is_default: false, // 기본값 }; }; /** * ItemMasterField → MasterFieldRequest 변환 * 2025-11-26: field_type과 속성명 모두 API와 동일하게 통일 */ export const transformMasterFieldToRequest = ( field: Partial ) => { return { field_name: field.field_name || '', field_type: field.field_type || 'textbox', // API와 동일한 타입 category: field.category || undefined, description: field.description || undefined, is_common: field.is_common ?? false, default_value: field.default_value || undefined, options: field.options || undefined, validation_rules: field.validation_rules || undefined, // API와 동일 properties: field.properties || undefined, // API와 동일 }; }; // ============================================ // 배치 변환 헬퍼 // ============================================ /** * 여러 페이지 응답을 한번에 변환 */ export const transformPagesResponse = ( responses: ItemPageResponse[] | undefined | null ): ItemPage[] => { if (!responses || !Array.isArray(responses)) return []; return responses.map(transformPageResponse); }; /** * 여러 섹션 응답을 한번에 변환 */ export const transformSectionsResponse = ( responses: ItemSectionResponse[] | undefined | null ): ItemSection[] => { if (!responses || !Array.isArray(responses)) return []; return responses.map(transformSectionResponse); }; /** * 여러 필드 응답을 한번에 변환 */ export const transformFieldsResponse = ( responses: ItemFieldResponse[] | undefined | null ): ItemField[] => { if (!responses || !Array.isArray(responses)) return []; return responses.map(transformFieldResponse); }; /** * 여러 BOM 아이템 응답을 한번에 변환 */ export const transformBomItemsResponse = ( responses: BomItemResponse[] | undefined | null ): BOMItem[] => { if (!responses || !Array.isArray(responses)) return []; return responses.map(transformBomItemResponse); }; /** * 여러 섹션 템플릿 응답을 한번에 변환 */ export const transformSectionTemplatesResponse = ( responses: SectionTemplateResponse[] | undefined | null ): SectionTemplate[] => { if (!responses || !Array.isArray(responses)) return []; return responses.map(transformSectionTemplateResponse); }; /** * 여러 마스터 필드 응답을 한번에 변환 */ export const transformMasterFieldsResponse = ( responses: MasterFieldResponse[] | undefined | null ): ItemMasterField[] => { if (!responses || !Array.isArray(responses)) return []; return responses.map(transformMasterFieldResponse); }; /** * UnitOptionResponse → MasterOption 변환 (Frontend의 MasterOption 타입에 맞춤) */ export const transformUnitOptionResponse = ( response: UnitOptionResponse ): { id: string; value: string; label: string; isActive: boolean } => { return { id: response.id.toString(), // number → string 변환 value: response.value, label: response.label, isActive: true, // API에 없으므로 기본값 }; }; /** * CustomTabResponse → Frontend customTabs 타입 변환 */ export const transformCustomTabResponse = ( response: CustomTabResponse ): { id: string; label: string; icon: string; isDefault: boolean; order: number } => { return { id: response.id.toString(), // number → string 변환 label: response.label, icon: response.icon || 'FileText', // null이면 기본 아이콘 isDefault: response.is_default, order: response.order_no, }; }; /** * 여러 단위 옵션 응답을 한번에 변환 */ export const transformUnitOptionsResponse = ( responses: UnitOptionResponse[] | undefined | null ) => { if (!responses || !Array.isArray(responses)) return []; return responses.map(transformUnitOptionResponse); }; /** * 여러 커스텀 탭 응답을 한번에 변환 */ export const transformCustomTabsResponse = ( responses: CustomTabResponse[] | undefined | null ) => { if (!responses || !Array.isArray(responses)) return []; return responses.map(transformCustomTabResponse); }; /** * ItemSectionResponse → SectionTemplate 변환 * 2025-11-26: 백엔드가 sectionTemplates 대신 sections를 반환하는 경우 사용 * is_template=true인 섹션을 SectionTemplate 형식으로 변환 */ export const transformSectionTemplateFromSection = ( response: ItemSectionResponse ): SectionTemplate => { return { id: response.id, tenant_id: response.tenant_id, template_name: response.title, // title → template_name section_type: SECTION_TYPE_MAP[response.type] || 'BASIC', // type → section_type description: response.description, default_fields: null, // API 응답에 없으므로 null // 필드 변환은 별도 처리 필요 (fields가 있으면 TemplateField로 변환) // 2025-11-28: fieldKey를 실제 field_key 사용하도록 수정 (기존: field_name에서 생성) fields: response.fields?.map(field => ({ id: field.id.toString(), name: field.field_name, fieldKey: field.field_key || field.field_name.toLowerCase().replace(/\s+/g, '_'), property: { inputType: getFieldType(field.field_type), required: field.is_required, options: field.options?.map((opt: { label: string; value: string }) => opt.label || opt.value), }, description: field.placeholder || undefined, })), bomItems: response.bomItems?.map(transformBomItemResponse), created_by: response.created_by, updated_by: response.updated_by, created_at: response.created_at, updated_at: response.updated_at, }; };