feat: API 프록시 추가 및 품목기준관리 기능 개선

- HttpOnly 쿠키 기반 API 프록시 라우트 추가 (/api/proxy/[...path])
- 품목기준관리 컴포넌트 개선 (섹션, 필드, 다이얼로그)
- ItemMasterContext API 연동 강화
- mock-data 제거 및 실제 API 연동
- 문서 명명규칙 정리 ([TYPE-DATE] 형식)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
byeongcheolryu
2025-11-25 21:07:10 +09:00
parent 5b2f8adc87
commit 593644922a
37 changed files with 5897 additions and 3267 deletions

View File

@@ -4,7 +4,7 @@ import { useState, useEffect } from 'react';
import { PageLayout } from '@/components/organisms/PageLayout';
import { PageHeader } from '@/components/organisms/PageHeader';
import { useItemMaster } from '@/contexts/ItemMasterContext';
import type { ItemPage, ItemSection, ItemField, FieldDisplayCondition, ItemMasterField, ItemFieldProperty, SectionTemplate, BOMItem } from '@/contexts/ItemMasterContext';
import type { ItemPage, ItemSection, ItemField, FieldDisplayCondition, ItemMasterField, ItemFieldProperty, SectionTemplate, BOMItem, TemplateField } from '@/contexts/ItemMasterContext';
import { MasterFieldTab, HierarchyTab, SectionsTab } from './ItemMasterDataManagement/tabs';
import { FieldDialog } from './ItemMasterDataManagement/dialogs/FieldDialog';
import { type ConditionalFieldConfig } from './ItemMasterDataManagement/components/ConditionalDisplayUI';
@@ -82,6 +82,7 @@ const INPUT_TYPE_OPTIONS = [
export function ItemMasterDataManagement() {
const {
itemPages,
loadItemPages,
addItemPage,
updateItemPage,
deleteItemPage,
@@ -93,14 +94,17 @@ export function ItemMasterDataManagement() {
deleteField,
reorderFields,
itemMasterFields,
loadItemMasterFields,
addItemMasterField,
updateItemMasterField,
deleteItemMasterField,
sectionTemplates,
loadSectionTemplates,
addSectionTemplate,
updateSectionTemplate,
deleteSectionTemplate,
resetAllData
resetAllData,
tenantId
} = useItemMaster();
console.log('ItemMasterDataManagement: Current sectionTemplates', sectionTemplates);
@@ -134,23 +138,17 @@ export function ItemMasterDataManagement() {
const data = await itemMasterApi.init();
// 페이지 데이터 로드 (context의 addItemPage 사용)
data.pages.forEach(page => {
const transformed = transformPagesResponse([page])[0];
addItemPage(transformed);
});
// 페이지 데이터 로드 (이미 존재하는 데이터를 state에 로드 - API 호출 없음)
const transformedPages = transformPagesResponse(data.pages);
loadItemPages(transformedPages);
// 섹션 템플릿 로드
data.sectionTemplates.forEach(template => {
const transformed = transformSectionTemplatesResponse([template])[0];
addSectionTemplate(transformed);
});
// 섹션 템플릿 로드 (덮어쓰기 - API 호출 없음!)
const transformedTemplates = transformSectionTemplatesResponse(data.sectionTemplates);
loadSectionTemplates(transformedTemplates);
// 마스터 필드 로드
data.masterFields.forEach(field => {
const transformed = transformMasterFieldsResponse([field])[0];
addItemMasterField(transformed);
});
// 마스터 필드 로드 (덮어쓰기 - API 호출 없음!)
const transformedFields = transformMasterFieldsResponse(data.masterFields);
loadItemMasterFields(transformedFields);
// 커스텀 탭 로드 (local state)
if (data.customTabs && data.customTabs.length > 0) {
@@ -207,12 +205,8 @@ export function ItemMasterDataManagement() {
const [activeTab, setActiveTab] = useState('hierarchy');
// 속성 하위 탭 관리
const [attributeSubTabs, setAttributeSubTabs] = useState<Array<{id: string; label: string; key: string; isDefault: boolean; order: number}>>([
{ id: 'units', label: '단위', key: 'units', isDefault: true, order: 0 },
{ id: 'materials', label: '재질', key: 'materials', isDefault: true, order: 1 },
{ id: 'surface', label: '표면처리', key: 'surface', isDefault: true, order: 2 }
]);
// 속성 하위 탭 관리 (API에서 로드)
const [attributeSubTabs, setAttributeSubTabs] = useState<Array<{id: string; label: string; key: string; isDefault: boolean; order: number}>>([]);
// 마스터 항목이 추가/수정될 때 속성 탭 자동 생성
useEffect(() => {
@@ -344,7 +338,9 @@ export function ItemMasterDataManagement() {
const [newSectionTitle, setNewSectionTitle] = useState('');
const [newSectionDescription, setNewSectionDescription] = useState('');
const [newSectionType, setNewSectionType] = useState<'fields' | 'bom'>('fields');
const [sectionInputMode, setSectionInputMode] = useState<'custom' | 'template'>('custom');
const [selectedSectionTemplateId, setSelectedSectionTemplateId] = useState<number | null>(null);
// 모바일 체크
const [isMobile, setIsMobile] = useState(false);
useEffect(() => {
@@ -426,6 +422,10 @@ export function ItemMasterDataManagement() {
const [templateFieldMultiColumn, setTemplateFieldMultiColumn] = useState(false);
const [templateFieldColumnCount, setTemplateFieldColumnCount] = useState(2);
const [templateFieldColumnNames, setTemplateFieldColumnNames] = useState<string[]>(['컬럼1', '컬럼2']);
// 템플릿 필드 마스터 항목 관련 상태
const [templateFieldInputMode, setTemplateFieldInputMode] = useState<'custom' | 'master'>('custom');
const [templateFieldShowMasterFieldList, setTemplateFieldShowMasterFieldList] = useState(false);
const [templateFieldSelectedMasterFieldId, setTemplateFieldSelectedMasterFieldId] = useState('');
// BOM 관리 상태
const [_bomItems, setBomItems] = useState<BOMItem[]>([]);
@@ -433,6 +433,8 @@ export function ItemMasterDataManagement() {
// 속성 변경 시 연동된 마스터 항목의 옵션 자동 업데이트
useEffect(() => {
itemMasterFields.forEach(field => {
// default_properties가 null/undefined인 경우 스킵
if (!field.default_properties) return;
const attributeType = (field.default_properties as any).attributeType;
if (attributeType && attributeType !== 'custom' && field.default_properties?.inputType === 'dropdown') {
let newOptions: string[] = [];
@@ -548,20 +550,19 @@ export function ItemMasterDataManagement() {
setIsLoading(true);
const absolutePath = generateAbsolutePath(newPageItemType, newPageName);
// API 호출
const response = await itemMasterApi.pages.create({
// Context의 addItemPage 사용 (API 호출 + state 업데이트)
// ⚠️ 이전 코드는 여기서 API 호출 후 addItemPage도 호출해서 API가 2번 호출되는 버그가 있었음
const newPage = await addItemPage({
page_name: newPageName,
item_type: newPageItemType,
absolute_path: absolutePath,
is_active: true,
sections: [],
order_no: 0,
});
// 응답 변환 및 context에 추가
const transformedPage = transformPageResponse(response);
addItemPage(transformedPage);
// 새로 생성된 페이지를 선택
setSelectedPageId(transformedPage.id);
setSelectedPageId(newPage.id);
// 폼 초기화
setNewPageName('');
@@ -586,43 +587,39 @@ export function ItemMasterDataManagement() {
}
};
const handleDuplicatePage = (pageId: number) => {
const handleDuplicatePage = async (pageId: number) => {
const originalPage = itemPages.find(p => p.id === pageId);
if (!originalPage) return toast.error('페이지를 찾을 수 없습니다');
// 섹션 인스턴스 깊은 복사 (새로운 ID 부여)
const duplicatedSections = originalPage.sections.map(section => ({
...section,
id: Date.now(),
fields: section.fields?.map(field => ({
...field,
id: Date.now()
})) || [],
bomItems: section.bomItems?.map(item => ({
...item,
id: Date.now()
}))
}));
try {
setIsLoading(true);
// 페이지 복제
const duplicatedPageName = `${originalPage.page_name} (복제)`;
const absolutePath = generateAbsolutePath(originalPage.item_type, duplicatedPageName);
const newPage: ItemPage = {
id: Date.now(),
page_name: duplicatedPageName,
item_type: originalPage.item_type,
sections: duplicatedSections,
is_active: true,
absolute_path: absolutePath,
order_no: 0,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
};
// 페이지 복제
const duplicatedPageName = `${originalPage.page_name} (복제)`;
const absolutePath = generateAbsolutePath(originalPage.item_type, duplicatedPageName);
// 페이지 추가
addItemPage(newPage);
setSelectedPageId(newPage.id);
toast.success('페이지가 복제되었습니다 (저장 필요)');
// Context의 addItemPage 사용 (API 호출 + state 업데이트)
const newPage = await addItemPage({
page_name: duplicatedPageName,
item_type: originalPage.item_type,
sections: [], // 섹션은 별도 API로 복제해야 함
is_active: true,
absolute_path: absolutePath,
order_no: 0,
});
// 서버에서 반환된 ID로 선택
setSelectedPageId(newPage.id);
toast.success('페이지가 복제되었습니다');
// TODO: 원본 페이지의 섹션들도 복제 필요 (별도 API 호출)
} catch (err) {
const errorMessage = getErrorMessage(err);
toast.error(errorMessage);
console.error('❌ Failed to duplicate page:', err);
} finally {
setIsLoading(false);
}
};
const handleAddSection = () => {
@@ -657,20 +654,21 @@ export function ItemMasterDataManagement() {
// 섹션은 페이지의 일부이므로 sections로 별도 추적하지 않음
// 2. 섹션관리 탭에도 템플릿으로 자동 추가 (계층구조 섹션 = 섹션 탭 섹션)
const newTemplate: SectionTemplate = {
id: Date.now(),
// 프론트엔드 형식: template_name, section_type ('BASIC' | 'BOM' | 'CUSTOM')
const newTemplateData = {
tenant_id: tenantId ?? 0,
template_name: newSection.section_name,
section_type: newSection.section_type,
description: newSection.description,
section_type: newSection.section_type as 'BASIC' | 'BOM' | 'CUSTOM',
description: newSection.description ?? null,
default_fields: null,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
created_by: null,
updated_by: null,
};
addSectionTemplate(newTemplate);
addSectionTemplate(newTemplateData);
console.log('Section added to both page and template:', {
sectionId: newSection.id,
templateId: newTemplate.id
templateTitle: newTemplateData.title
});
setNewSectionTitle('');
@@ -680,6 +678,67 @@ export function ItemMasterDataManagement() {
toast.success(`${newSectionType === 'bom' ? 'BOM' : '일반'} 섹션이 페이지에 추가되고 템플릿으로도 등록되었습니다!`);
};
// 섹션 템플릿을 페이지에 연결 (SectionDialog에서 사용)
const handleLinkTemplate = (template: SectionTemplate) => {
if (!selectedPage) {
toast.error('페이지를 먼저 선택해주세요');
return;
}
// 템플릿을 섹션으로 변환하여 페이지에 추가
const newSection: Omit<ItemSection, 'id' | 'created_at' | 'updated_at'> = {
page_id: selectedPage.id,
section_name: template.template_name,
section_type: template.section_type,
description: template.description || undefined,
order_no: selectedPage.sections.length + 1,
is_collapsible: true,
is_default_open: true,
fields: template.fields ? template.fields.map((field, idx) => ({
id: Date.now() + idx,
section_id: 0, // 추후 업데이트됨
field_name: field.name,
field_type: field.property.inputType,
order_no: idx + 1,
is_required: field.property.required,
placeholder: field.description || null,
default_value: null,
display_condition: null,
validation_rules: null,
options: field.property.options
? field.property.options.map(opt => ({ label: opt, value: opt }))
: null,
properties: field.property.multiColumn ? {
multiColumn: true,
columnCount: field.property.columnCount,
columnNames: field.property.columnNames
} : null,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
})) : [],
bomItems: template.section_type === 'BOM' ? (template.bomItems || []) : undefined
};
console.log('Linking template to page:', {
templateId: template.id,
templateName: template.template_name,
pageId: selectedPage.id,
newSection
});
addSectionToPage(selectedPage.id, newSection);
// 다이얼로그 상태 초기화
setSectionInputMode('custom');
setSelectedSectionTemplateId(null);
setNewSectionTitle('');
setNewSectionDescription('');
setNewSectionType('fields');
setIsSectionDialogOpen(false);
toast.success(`"${template.template_name}" 템플릿이 페이지에 연결되었습니다!`);
};
const handleEditSectionTitle = (sectionId: string, currentTitle: string) => {
setEditingSectionId(sectionId);
setEditingSectionTitle(currentTitle);
@@ -758,9 +817,15 @@ export function ItemMasterDataManagement() {
// 텍스트박스 컬럼 설정
const hasColumns = newFieldInputType === 'textbox' && textboxColumns.length > 0;
// 마스터 항목에서 가져온 경우 master_field_id 설정
const masterFieldId = fieldInputMode === 'master' && selectedMasterFieldId
? Number(selectedMasterFieldId)
: null;
const newField: ItemField = {
id: editingFieldId ? Number(editingFieldId) : Date.now(),
section_id: Number(selectedSectionForField),
master_field_id: masterFieldId, // 마스터 항목 연결 정보
field_name: newFieldName,
field_type: newFieldInputType,
order_no: 0,
@@ -805,17 +870,16 @@ export function ItemMasterDataManagement() {
// 1. 섹션에 항목 추가
addFieldToSection(Number(selectedSectionForField), newField);
// 2. 항목관리 탭에도 마스터 항목으로 자동 추가 (중복 체크)
// 2. 마스터 항목 선택이 아닌 경우에만 새 마스터 항목 자동 생성
// (마스터 항목 선택 시에는 이미 master_field_id로 연결되어 있음)
const isFromMasterField = masterFieldId !== null;
const existingMasterField = itemMasterFields.find(mf => mf.id.toString() === newField.field_name);
if (!existingMasterField) {
if (!isFromMasterField && !existingMasterField) {
// API 스펙: field_type은 소문자만 허용 (textbox, number, dropdown, checkbox, date, textarea)
const newMasterField: ItemMasterField = {
id: Date.now(),
field_name: newField.field_name,
field_type: newField.field_type === 'textbox' ? 'TEXT' :
newField.field_type === 'number' ? 'NUMBER' :
newField.field_type === 'date' ? 'DATE' :
newField.field_type === 'textarea' ? 'TEXTAREA' :
newField.field_type === 'checkbox' ? 'CHECKBOX' : 'SELECT',
field_type: newField.field_type, // API 스펙에 맞게 소문자 그대로 전달
description: newField.placeholder,
default_properties: newField.properties,
category: selectedPage.item_type, // 현재 페이지의 품목유형을 카테고리로 설정
@@ -975,14 +1039,11 @@ export function ItemMasterDataManagement() {
}));
}
// API 스펙: field_type은 소문자만 허용 (textbox, number, dropdown, checkbox, date, textarea)
const newMasterField: ItemMasterField = {
id: Date.now(),
field_name: newMasterFieldName,
field_type: newMasterFieldInputType === 'textbox' ? 'TEXT' :
newMasterFieldInputType === 'number' ? 'NUMBER' :
newMasterFieldInputType === 'date' ? 'DATE' :
newMasterFieldInputType === 'textarea' ? 'TEXTAREA' :
newMasterFieldInputType === 'checkbox' ? 'CHECKBOX' : 'SELECT',
field_type: newMasterFieldInputType,
category: newMasterFieldCategory || null,
description: newMasterFieldDescription || null,
default_validation: null,
@@ -1214,21 +1275,24 @@ export function ItemMasterDataManagement() {
// 섹션 템플릿 핸들러
const handleAddSectionTemplate = () => {
if (!newSectionTemplateTitle.trim())
if (!newSectionTemplateTitle.trim())
return toast.error('섹션 제목을 입력해주세요');
const newTemplate: SectionTemplate = {
id: Date.now(),
// Context의 addSectionTemplate이 기대하는 SectionTemplate 형식 사용
// template_name, section_type ('BASIC' | 'BOM' | 'CUSTOM')
const newTemplateData = {
tenant_id: tenantId ?? 0,
template_name: newSectionTemplateTitle,
section_type: newSectionTemplateType === 'bom' ? 'BOM' : 'BASIC',
description: newSectionTemplateDescription || undefined,
section_type: (newSectionTemplateType === 'bom' ? 'BOM' : 'BASIC') as 'BASIC' | 'BOM' | 'CUSTOM',
description: newSectionTemplateDescription || null,
default_fields: null,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
category: newSectionTemplateCategory,
created_by: null,
updated_by: null,
};
console.log('Adding section template:', newTemplate);
addSectionTemplate(newTemplate);
console.log('Adding section template:', newTemplateData);
addSectionTemplate(newTemplateData);
setNewSectionTemplateTitle('');
setNewSectionTemplateDescription('');
@@ -1240,9 +1304,10 @@ export function ItemMasterDataManagement() {
const handleEditSectionTemplate = (template: SectionTemplate) => {
setEditingSectionTemplateId(template.id);
// SectionTemplate 타입에 맞게 template_name, section_type 사용
setNewSectionTemplateTitle(template.template_name);
setNewSectionTemplateDescription(template.description || '');
setNewSectionTemplateCategory([]);
setNewSectionTemplateCategory(template.category || []);
setNewSectionTemplateType(template.section_type === 'BOM' ? 'bom' : 'fields');
setIsSectionTemplateDialogOpen(true);
};
@@ -1251,13 +1316,16 @@ export function ItemMasterDataManagement() {
if (!editingSectionTemplateId || !newSectionTemplateTitle.trim())
return toast.error('섹션 제목을 입력해주세요');
// Context의 updateSectionTemplate이 기대하는 SectionTemplate 형식 사용
// template_name, section_type ('BASIC' | 'BOM' | 'CUSTOM')
const updateData = {
title: newSectionTemplateTitle,
template_name: newSectionTemplateTitle,
description: newSectionTemplateDescription || undefined,
category: newSectionTemplateCategory.length > 0 ? newSectionTemplateCategory : undefined,
type: newSectionTemplateType
section_type: (newSectionTemplateType === 'bom' ? 'BOM' : 'BASIC') as 'BASIC' | 'BOM' | 'CUSTOM'
};
console.log('Updating section template:', { id: editingSectionTemplateId, updateData });
updateSectionTemplate(editingSectionTemplateId, updateData);
setEditingSectionTemplateId(null);
@@ -1290,19 +1358,20 @@ export function ItemMasterDataManagement() {
}
// 템플릿을 복사해서 섹션으로 추가
// API 스펙: SectionTemplate은 title, type ('fields' | 'bom') 사용
const newSection: Omit<ItemSection, 'id' | 'created_at' | 'updated_at'> = {
page_id: selectedPage.id,
section_name: template.template_name,
section_type: template.section_type,
section_name: template.title,
section_type: template.type === 'bom' ? 'BOM' : 'BASIC',
description: template.description || undefined,
order_no: selectedPage.sections.length + 1,
is_collapsible: true,
is_default_open: true,
fields: [],
bomItems: template.section_type === 'BOM' ? [] : undefined
bomItems: template.type === 'bom' ? [] : undefined
};
console.log('Loading template to section:', template.template_name, 'type:', template.section_type, 'newSection:', newSection);
console.log('Loading template to section:', template.title, 'type:', template.type, 'newSection:', newSection);
addSectionToPage(selectedPage.id, newSection);
setSelectedTemplateId(null);
setIsLoadTemplateDialogOpen(false);
@@ -1321,15 +1390,11 @@ export function ItemMasterDataManagement() {
// 항목 탭에 해당 항목이 없으면 자동으로 추가
const existingMasterField = itemMasterFields.find(f => f.id.toString() === templateFieldKey);
if (!existingMasterField && !editingTemplateFieldId) {
// API 스펙: field_type은 소문자만 허용 (textbox, number, dropdown, checkbox, date, textarea)
const newMasterField: ItemMasterField = {
id: Date.now(),
field_name: templateFieldName,
field_type: templateFieldInputType === 'textbox' ? 'TEXT' :
templateFieldInputType === 'number' ? 'NUMBER' :
templateFieldInputType === 'date' ? 'DATE' :
templateFieldInputType === 'dropdown' ? 'SELECT' :
templateFieldInputType === 'textarea' ? 'TEXTAREA' :
templateFieldInputType === 'checkbox' ? 'CHECKBOX' : 'TEXT',
field_type: templateFieldInputType,
default_properties: {
inputType: templateFieldInputType,
required: templateFieldRequired,
@@ -1380,38 +1445,30 @@ export function ItemMasterDataManagement() {
}
}
const newField: ItemField = {
id: editingTemplateFieldId || Date.now(),
section_id: 0, // Placeholder for template
field_name: templateFieldName,
field_type: templateFieldInputType,
order_no: 0,
is_required: templateFieldRequired,
placeholder: templateFieldDescription || null,
default_value: null,
display_condition: null,
validation_rules: null,
options: templateFieldInputType === 'dropdown' && templateFieldOptions.trim()
? templateFieldOptions.split(',').map(o => ({ label: o.trim(), value: o.trim() }))
: null,
properties: {
// TemplateField 형식으로 생성 (UI가 기대하는 형식)
const newField: TemplateField = {
id: String(editingTemplateFieldId || Date.now()),
name: templateFieldName,
fieldKey: templateFieldKey,
property: {
inputType: templateFieldInputType,
required: templateFieldRequired,
row: 1,
col: 1,
options: templateFieldInputType === 'dropdown' && templateFieldOptions.trim()
? templateFieldOptions.split(',').map(o => o.trim())
: undefined,
multiColumn: (templateFieldInputType === 'textbox' || templateFieldInputType === 'textarea') ? templateFieldMultiColumn : undefined,
columnCount: (templateFieldInputType === 'textbox' || templateFieldInputType === 'textarea') && templateFieldMultiColumn ? templateFieldColumnCount : undefined,
columnNames: (templateFieldInputType === 'textbox' || templateFieldInputType === 'textarea') && templateFieldMultiColumn ? templateFieldColumnNames : undefined
},
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
description: templateFieldDescription || undefined
};
let updatedFields;
const currentFields = template.default_fields ? (typeof template.default_fields === 'string' ? JSON.parse(template.default_fields) : template.default_fields) : [];
if (editingTemplateFieldId) {
updatedFields = Array.isArray(currentFields) ? currentFields.map((f: any) => f.id === editingTemplateFieldId ? newField : f) : [];
// f.id는 string, editingTemplateFieldId는 number이므로 String으로 변환하여 비교
updatedFields = Array.isArray(currentFields) ? currentFields.map((f: any) => String(f.id) === String(editingTemplateFieldId) ? newField : f) : [];
toast.success('항목이 수정되었습니다');
} else {
updatedFields = Array.isArray(currentFields) ? [...currentFields, newField] : [newField];
@@ -1434,18 +1491,19 @@ export function ItemMasterDataManagement() {
setIsTemplateFieldDialogOpen(false);
};
const handleEditTemplateField = (templateId: number, field: ItemField) => {
// TemplateField 형식으로 수정 (UI가 전달하는 형식)
const handleEditTemplateField = (templateId: number, field: TemplateField) => {
setCurrentTemplateId(templateId);
setEditingTemplateFieldId(field.id);
setTemplateFieldName(field.field_name);
setTemplateFieldKey(field.id.toString());
setTemplateFieldInputType(field.properties?.inputType);
setTemplateFieldRequired(field.is_required);
setTemplateFieldOptions(field.options?.map(o => o.value).join(', ') || '');
setTemplateFieldDescription(field.placeholder || '');
setTemplateFieldMultiColumn(field.properties?.multiColumn || false);
setTemplateFieldColumnCount(field.properties?.columnCount || 2);
setTemplateFieldColumnNames(field.properties?.columnNames || ['컬럼1', '컬럼2']);
setEditingTemplateFieldId(Number(field.id)); // TemplateField.id는 string
setTemplateFieldName(field.name);
setTemplateFieldKey(field.fieldKey);
setTemplateFieldInputType(field.property.inputType);
setTemplateFieldRequired(field.property.required);
setTemplateFieldOptions(field.property.options?.join(', ') || '');
setTemplateFieldDescription(field.description || '');
setTemplateFieldMultiColumn(field.property.multiColumn || false);
setTemplateFieldColumnCount(field.property.columnCount || 2);
setTemplateFieldColumnNames(field.property.columnNames || ['컬럼1', '컬럼2']);
setIsTemplateFieldDialogOpen(true);
};
@@ -1456,7 +1514,8 @@ export function ItemMasterDataManagement() {
if (!template) return;
const currentFields = template.default_fields ? (typeof template.default_fields === 'string' ? JSON.parse(template.default_fields) : template.default_fields) : [];
const updatedFields = Array.isArray(currentFields) ? currentFields.filter((f: any) => f.id !== fieldId) : [];
// f.id는 number 또는 string일 수 있으므로 String으로 변환하여 비교
const updatedFields = Array.isArray(currentFields) ? currentFields.filter((f: any) => String(f.id) !== String(fieldId)) : [];
updateSectionTemplate(templateId, { default_fields: updatedFields });
toast.success('항목이 삭제되었습니다');
};
@@ -1468,7 +1527,7 @@ export function ItemMasterDataManagement() {
id: Date.now(),
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
tenant_id: 1,
tenant_id: tenantId ?? 0,
section_id: 0
};
setBomItems(prev => [...prev, newItem]);
@@ -1489,7 +1548,7 @@ export function ItemMasterDataManagement() {
id: Date.now(),
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
tenant_id: 1,
tenant_id: tenantId ?? 0,
section_id: 0
};
@@ -1770,11 +1829,7 @@ export function ItemMasterDataManagement() {
{ id: 'attributes', label: '속성', icon: 'Settings', isDefault: true, order: 4 }
]);
setAttributeSubTabs([
{ id: 'units', label: '단위', key: 'units', isDefault: true, order: 0 },
{ id: 'materials', label: '재질', key: 'materials', isDefault: true, order: 1 },
{ id: 'surface', label: '표면처리', key: 'surface', isDefault: true, order: 2 }
]);
setAttributeSubTabs([]);
console.log('🗑️ 모든 품목기준관리 데이터가 초기화되었습니다');
toast.success('✅ 모든 데이터가 초기화되었습니다!\n계층구조, 섹션, 항목, 속성이 모두 삭제되었습니다.');
@@ -1831,14 +1886,14 @@ export function ItemMasterDataManagement() {
);
})}
</TabsList>
<Button
size="sm"
variant="outline"
onClick={() => setIsManageTabsDialogOpen(true)}
>
<Settings className="h-4 w-4 mr-1" />
</Button>
{/*<Button*/}
{/* size="sm"*/}
{/* variant="outline"*/}
{/* onClick={() => setIsManageTabsDialogOpen(true)}*/}
{/*>*/}
{/* <Settings className="h-4 w-4 mr-1" />*/}
{/* 탭 관리*/}
{/*</Button>*/}
{/* 전체 초기화 버튼 숨김 처리 - 디자인에 없는 기능 */}
{/* <Button
size="sm"
@@ -1880,7 +1935,7 @@ export function ItemMasterDataManagement() {
className="shrink-0"
>
<Settings className="w-4 h-4 mr-1" />
</Button>
</div>
@@ -2622,6 +2677,12 @@ export function ItemMasterDataManagement() {
newSectionDescription={newSectionDescription}
setNewSectionDescription={setNewSectionDescription}
handleAddSection={handleAddSection}
sectionInputMode={sectionInputMode}
setSectionInputMode={setSectionInputMode}
sectionTemplates={sectionTemplates}
selectedTemplateId={selectedSectionTemplateId}
setSelectedTemplateId={setSelectedSectionTemplateId}
handleLinkTemplate={handleLinkTemplate}
/>
{/* 항목 추가/수정 다이얼로그 - 데스크톱 */}
@@ -2809,6 +2870,14 @@ export function ItemMasterDataManagement() {
templateFieldColumnNames={templateFieldColumnNames}
setTemplateFieldColumnNames={setTemplateFieldColumnNames}
handleAddTemplateField={handleAddTemplateField}
// 마스터 항목 관련 props
itemMasterFields={itemMasterFields}
templateFieldInputMode={templateFieldInputMode}
setTemplateFieldInputMode={setTemplateFieldInputMode}
showMasterFieldList={templateFieldShowMasterFieldList}
setShowMasterFieldList={setTemplateFieldShowMasterFieldList}
selectedMasterFieldId={templateFieldSelectedMasterFieldId}
setSelectedMasterFieldId={setTemplateFieldSelectedMasterFieldId}
/>
<LoadTemplateDialog