fix: 품목기준관리 실시간 동기화 수정
- BOM 항목 추가/수정/삭제 시 섹션탭 즉시 반영 - 섹션 복제 시 UI 즉시 업데이트 (null vs undefined 이슈 해결) - 항목 수정 기능 추가 (useTemplateManagement) - 실시간 동기화 문서 추가 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -44,36 +44,16 @@ const SECTION_TYPE_REVERSE_MAP: Record<string, 'fields' | 'bom'> = {
|
||||
CUSTOM: 'fields', // CUSTOM은 fields로 매핑
|
||||
};
|
||||
|
||||
/**
|
||||
* API field_type → Frontend field_type 변환
|
||||
* API: 'textbox' | 'number' | 'dropdown' | 'checkbox' | 'date' | 'textarea'
|
||||
* Frontend: 'TEXT' | 'NUMBER' | 'DATE' | 'SELECT' | 'TEXTAREA' | 'CHECKBOX'
|
||||
*/
|
||||
const FIELD_TYPE_MAP: Record<
|
||||
string,
|
||||
'TEXT' | 'NUMBER' | 'DATE' | 'SELECT' | 'TEXTAREA' | 'CHECKBOX'
|
||||
> = {
|
||||
textbox: 'TEXT',
|
||||
number: 'NUMBER',
|
||||
dropdown: 'SELECT',
|
||||
checkbox: 'CHECKBOX',
|
||||
date: 'DATE',
|
||||
textarea: 'TEXTAREA',
|
||||
};
|
||||
// 2025-11-26: field_type은 API와 Frontend가 동일한 값을 사용하므로 변환 불필요
|
||||
// API & Frontend: 'textbox' | 'number' | 'dropdown' | 'checkbox' | 'date' | 'textarea'
|
||||
type FieldType = 'textbox' | 'number' | 'dropdown' | 'checkbox' | 'date' | 'textarea';
|
||||
|
||||
/**
|
||||
* Frontend field_type → API field_type 변환
|
||||
* field_type 기본값 반환 (알 수 없는 값일 경우)
|
||||
*/
|
||||
const FIELD_TYPE_REVERSE_MAP: Record<
|
||||
string,
|
||||
'textbox' | 'number' | 'dropdown' | 'checkbox' | 'date' | 'textarea'
|
||||
> = {
|
||||
TEXT: 'textbox',
|
||||
NUMBER: 'number',
|
||||
SELECT: 'dropdown',
|
||||
CHECKBOX: 'checkbox',
|
||||
DATE: 'date',
|
||||
TEXTAREA: 'textarea',
|
||||
const getFieldType = (type: string): FieldType => {
|
||||
const validTypes: FieldType[] = ['textbox', 'number', 'dropdown', 'checkbox', 'date', 'textarea'];
|
||||
return validTypes.includes(type as FieldType) ? (type as FieldType) : 'textbox';
|
||||
};
|
||||
|
||||
// ============================================
|
||||
@@ -91,8 +71,10 @@ export const transformPageResponse = (
|
||||
tenant_id: response.tenant_id,
|
||||
page_name: response.page_name,
|
||||
item_type: response.item_type as 'FG' | 'PT' | 'SM' | 'RM' | 'CS',
|
||||
absolute_path: response.absolute_path,
|
||||
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,
|
||||
@@ -104,6 +86,7 @@ export const transformPageResponse = (
|
||||
/**
|
||||
* 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
|
||||
@@ -111,12 +94,17 @@ export const transformSectionResponse = (
|
||||
return {
|
||||
id: response.id,
|
||||
tenant_id: response.tenant_id,
|
||||
page_id: response.page_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) || [],
|
||||
bom_items: response.bomItems?.map(transformBomItemResponse) || [],
|
||||
// 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,
|
||||
@@ -126,7 +114,7 @@ export const transformSectionResponse = (
|
||||
|
||||
/**
|
||||
* ItemFieldResponse → ItemField 변환
|
||||
* 주요 변환: field_type 값 변환 (textbox → TEXT, dropdown → SELECT 등)
|
||||
* 2025-11-26: field_type은 API와 동일한 값 사용 (변환 불필요)
|
||||
*/
|
||||
export const transformFieldResponse = (
|
||||
response: ItemFieldResponse
|
||||
@@ -136,7 +124,7 @@ export const transformFieldResponse = (
|
||||
tenant_id: response.tenant_id,
|
||||
section_id: response.section_id,
|
||||
field_name: response.field_name,
|
||||
field_type: FIELD_TYPE_MAP[response.field_type] || 'TEXT', // 타입 값 변환
|
||||
field_type: getFieldType(response.field_type), // API와 동일한 타입
|
||||
order_no: response.order_no,
|
||||
is_required: response.is_required,
|
||||
placeholder: response.placeholder,
|
||||
@@ -200,7 +188,7 @@ export const transformSectionTemplateResponse = (
|
||||
|
||||
/**
|
||||
* MasterFieldResponse → ItemMasterField 변환
|
||||
* 주요 변환: field_type 값 변환 (textbox → TEXT, dropdown → SELECT 등)
|
||||
* 2025-11-26: field_type은 API와 동일한 값 사용, 속성명도 API와 통일
|
||||
*/
|
||||
export const transformMasterFieldResponse = (
|
||||
response: MasterFieldResponse
|
||||
@@ -209,11 +197,14 @@ export const transformMasterFieldResponse = (
|
||||
id: response.id,
|
||||
tenant_id: response.tenant_id,
|
||||
field_name: response.field_name,
|
||||
field_type: FIELD_TYPE_MAP[response.field_type] || 'TEXT', // 타입 값 변환
|
||||
field_type: getFieldType(response.field_type), // API와 동일한 타입
|
||||
category: response.category,
|
||||
description: response.description,
|
||||
default_validation: response.validation_rules, // 필드명 매핑
|
||||
default_properties: response.properties, // 필드명 매핑
|
||||
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,
|
||||
@@ -242,14 +233,12 @@ export const transformSectionToRequest = (
|
||||
|
||||
/**
|
||||
* ItemField → ItemFieldRequest 변환
|
||||
* 주요 변환: field_type 값 역변환 (TEXT → textbox, SELECT → dropdown 등)
|
||||
* 2025-11-26: field_type은 API와 동일한 값 사용 (변환 불필요)
|
||||
*/
|
||||
export const transformFieldToRequest = (field: Partial<ItemField>) => {
|
||||
return {
|
||||
field_name: field.field_name || '',
|
||||
field_type: field.field_type
|
||||
? FIELD_TYPE_REVERSE_MAP[field.field_type] || 'textbox'
|
||||
: 'textbox',
|
||||
field_type: field.field_type || 'textbox', // API와 동일한 타입
|
||||
is_required: field.is_required ?? false,
|
||||
placeholder: field.placeholder || null,
|
||||
default_value: field.default_value || null,
|
||||
@@ -295,23 +284,21 @@ export const transformSectionTemplateToRequest = (
|
||||
|
||||
/**
|
||||
* ItemMasterField → MasterFieldRequest 변환
|
||||
* 주요 변환: field_type 값 역변환, default_validation/properties 필드명 변환
|
||||
* 2025-11-26: field_type과 속성명 모두 API와 동일하게 통일
|
||||
*/
|
||||
export const transformMasterFieldToRequest = (
|
||||
field: Partial<ItemMasterField>
|
||||
) => {
|
||||
return {
|
||||
field_name: field.field_name || '',
|
||||
field_type: field.field_type
|
||||
? FIELD_TYPE_REVERSE_MAP[field.field_type] || 'textbox'
|
||||
: 'textbox',
|
||||
field_type: field.field_type || 'textbox', // API와 동일한 타입
|
||||
category: field.category || undefined,
|
||||
description: field.description || undefined,
|
||||
is_common: false, // 기본값
|
||||
default_value: undefined,
|
||||
options: undefined,
|
||||
validation_rules: field.default_validation || undefined, // 필드명 역변환
|
||||
properties: field.default_properties || 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와 동일
|
||||
};
|
||||
};
|
||||
|
||||
@@ -323,8 +310,9 @@ export const transformMasterFieldToRequest = (
|
||||
* 여러 페이지 응답을 한번에 변환
|
||||
*/
|
||||
export const transformPagesResponse = (
|
||||
responses: ItemPageResponse[]
|
||||
responses: ItemPageResponse[] | undefined | null
|
||||
): ItemPage[] => {
|
||||
if (!responses || !Array.isArray(responses)) return [];
|
||||
return responses.map(transformPageResponse);
|
||||
};
|
||||
|
||||
@@ -332,8 +320,9 @@ export const transformPagesResponse = (
|
||||
* 여러 섹션 응답을 한번에 변환
|
||||
*/
|
||||
export const transformSectionsResponse = (
|
||||
responses: ItemSectionResponse[]
|
||||
responses: ItemSectionResponse[] | undefined | null
|
||||
): ItemSection[] => {
|
||||
if (!responses || !Array.isArray(responses)) return [];
|
||||
return responses.map(transformSectionResponse);
|
||||
};
|
||||
|
||||
@@ -341,8 +330,9 @@ export const transformSectionsResponse = (
|
||||
* 여러 필드 응답을 한번에 변환
|
||||
*/
|
||||
export const transformFieldsResponse = (
|
||||
responses: ItemFieldResponse[]
|
||||
responses: ItemFieldResponse[] | undefined | null
|
||||
): ItemField[] => {
|
||||
if (!responses || !Array.isArray(responses)) return [];
|
||||
return responses.map(transformFieldResponse);
|
||||
};
|
||||
|
||||
@@ -350,8 +340,9 @@ export const transformFieldsResponse = (
|
||||
* 여러 BOM 아이템 응답을 한번에 변환
|
||||
*/
|
||||
export const transformBomItemsResponse = (
|
||||
responses: BomItemResponse[]
|
||||
responses: BomItemResponse[] | undefined | null
|
||||
): BOMItem[] => {
|
||||
if (!responses || !Array.isArray(responses)) return [];
|
||||
return responses.map(transformBomItemResponse);
|
||||
};
|
||||
|
||||
@@ -359,8 +350,9 @@ export const transformBomItemsResponse = (
|
||||
* 여러 섹션 템플릿 응답을 한번에 변환
|
||||
*/
|
||||
export const transformSectionTemplatesResponse = (
|
||||
responses: SectionTemplateResponse[]
|
||||
responses: SectionTemplateResponse[] | undefined | null
|
||||
): SectionTemplate[] => {
|
||||
if (!responses || !Array.isArray(responses)) return [];
|
||||
return responses.map(transformSectionTemplateResponse);
|
||||
};
|
||||
|
||||
@@ -368,8 +360,9 @@ export const transformSectionTemplatesResponse = (
|
||||
* 여러 마스터 필드 응답을 한번에 변환
|
||||
*/
|
||||
export const transformMasterFieldsResponse = (
|
||||
responses: MasterFieldResponse[]
|
||||
responses: MasterFieldResponse[] | undefined | null
|
||||
): ItemMasterField[] => {
|
||||
if (!responses || !Array.isArray(responses)) return [];
|
||||
return responses.map(transformMasterFieldResponse);
|
||||
};
|
||||
|
||||
@@ -406,8 +399,9 @@ export const transformCustomTabResponse = (
|
||||
* 여러 단위 옵션 응답을 한번에 변환
|
||||
*/
|
||||
export const transformUnitOptionsResponse = (
|
||||
responses: UnitOptionResponse[]
|
||||
responses: UnitOptionResponse[] | undefined | null
|
||||
) => {
|
||||
if (!responses || !Array.isArray(responses)) return [];
|
||||
return responses.map(transformUnitOptionResponse);
|
||||
};
|
||||
|
||||
@@ -415,7 +409,43 @@ export const transformUnitOptionsResponse = (
|
||||
* 여러 커스텀 탭 응답을 한번에 변환
|
||||
*/
|
||||
export const transformCustomTabsResponse = (
|
||||
responses: CustomTabResponse[]
|
||||
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로 변환)
|
||||
fields: response.fields?.map(field => ({
|
||||
id: field.id.toString(),
|
||||
name: field.field_name,
|
||||
fieldKey: 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,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user