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:
@@ -1,10 +1,10 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import { PageLayout } from '@/components/organisms/PageLayout';
|
||||
import { PageHeader } from '@/components/organisms/PageHeader';
|
||||
import { useItemMaster } from '@/contexts/ItemMasterContext';
|
||||
import type { SectionTemplate, BOMItem } from '@/contexts/ItemMasterContext';
|
||||
import type { SectionTemplate, BOMItem, TemplateField } from '@/contexts/ItemMasterContext';
|
||||
import { MasterFieldTab, HierarchyTab, SectionsTab } from './ItemMasterDataManagement/tabs';
|
||||
import { FieldDialog } from './ItemMasterDataManagement/dialogs/FieldDialog';
|
||||
// ConditionalFieldConfig type removed - not currently used
|
||||
@@ -20,16 +20,21 @@ import { TemplateFieldDialog } from './ItemMasterDataManagement/dialogs/Template
|
||||
import { LoadTemplateDialog } from './ItemMasterDataManagement/dialogs/LoadTemplateDialog';
|
||||
import { ColumnDialog } from './ItemMasterDataManagement/dialogs/ColumnDialog';
|
||||
import { SectionTemplateDialog } from './ItemMasterDataManagement/dialogs/SectionTemplateDialog';
|
||||
import { ImportSectionDialog } from './ItemMasterDataManagement/dialogs/ImportSectionDialog';
|
||||
import { ImportFieldDialog } from './ItemMasterDataManagement/dialogs/ImportFieldDialog';
|
||||
import { itemMasterApi } from '@/lib/api/item-master';
|
||||
import { getErrorMessage, ApiError } from '@/lib/api/error-handler';
|
||||
import { LoadingSpinner } from '@/components/ui/loading-spinner';
|
||||
import { ErrorMessage } from '@/components/ui/error-message';
|
||||
import { ServerErrorPage } from '@/components/common/ServerErrorPage';
|
||||
import {
|
||||
transformPagesResponse,
|
||||
transformSectionsResponse,
|
||||
transformSectionTemplatesResponse,
|
||||
transformMasterFieldsResponse,
|
||||
transformFieldsResponse,
|
||||
transformCustomTabsResponse,
|
||||
transformUnitOptionsResponse,
|
||||
transformSectionTemplateFromSection,
|
||||
} from '@/lib/api/transformers';
|
||||
import {
|
||||
Database,
|
||||
@@ -85,7 +90,7 @@ export function ItemMasterDataManagement() {
|
||||
deleteSection,
|
||||
addFieldToSection: _addFieldToSection,
|
||||
updateField: _updateField,
|
||||
deleteField,
|
||||
deleteField: _deleteField,
|
||||
reorderFields,
|
||||
itemMasterFields,
|
||||
loadItemMasterFields,
|
||||
@@ -98,11 +103,47 @@ export function ItemMasterDataManagement() {
|
||||
updateSectionTemplate: _updateSectionTemplate,
|
||||
deleteSectionTemplate: _deleteSectionTemplate,
|
||||
resetAllData,
|
||||
tenantId: _tenantId
|
||||
tenantId: _tenantId,
|
||||
// 2025-11-26 추가: 독립 엔티티 관리
|
||||
independentSections,
|
||||
loadIndependentSections,
|
||||
independentFields: _independentFields,
|
||||
loadIndependentFields,
|
||||
refreshIndependentSections,
|
||||
refreshIndependentFields,
|
||||
linkSectionToPage,
|
||||
unlinkSectionFromPage: _unlinkSectionFromPage,
|
||||
linkFieldToSection,
|
||||
unlinkFieldFromSection,
|
||||
getSectionUsage,
|
||||
getFieldUsage,
|
||||
cloneSection,
|
||||
reorderSections,
|
||||
// 2025-11-27 추가: BOM 항목 API 함수
|
||||
addBOMItem,
|
||||
updateBOMItem,
|
||||
deleteBOMItem,
|
||||
} = useItemMaster();
|
||||
|
||||
console.log('ItemMasterDataManagement: Current sectionTemplates', sectionTemplates);
|
||||
|
||||
// 2025-11-27: itemPages 변화 추적 (디버깅용)
|
||||
useEffect(() => {
|
||||
console.log('[ItemMasterDataManagement] ⚡ itemPages changed:', {
|
||||
pageCount: itemPages.length,
|
||||
pages: itemPages.map(p => ({
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
sectionsCount: p.sections.length,
|
||||
sections: p.sections.map(s => ({
|
||||
id: s.id,
|
||||
title: s.title,
|
||||
fieldsCount: s.fields?.length || 0
|
||||
}))
|
||||
}))
|
||||
});
|
||||
}, [itemPages]);
|
||||
|
||||
// ===== 커스텀 훅 초기화 =====
|
||||
const pageManagement = usePageManagement();
|
||||
const sectionManagement = useSectionManagement();
|
||||
@@ -135,7 +176,7 @@ export function ItemMasterDataManagement() {
|
||||
expandedSections: _expandedSections, setExpandedSections: _setExpandedSections,
|
||||
handleAddSection, handleLinkTemplate,
|
||||
handleEditSectionTitle, handleSaveSectionTitle,
|
||||
handleDeleteSection: _handleDeleteSection, toggleSection: _toggleSection,
|
||||
handleUnlinkSection, handleDeleteSection: _handleDeleteSection, toggleSection: _toggleSection,
|
||||
} = sectionManagement;
|
||||
|
||||
const {
|
||||
@@ -273,6 +314,92 @@ export function ItemMasterDataManagement() {
|
||||
}))
|
||||
);
|
||||
|
||||
// 2025-11-26: itemPages의 모든 섹션 + 독립 섹션(independentSections)을 SectionTemplate 형식으로 변환
|
||||
// 이렇게 하면 계층구조 탭과 섹션 탭이 같은 데이터 소스를 사용하여 자동 동기화됨
|
||||
// 독립 섹션: 페이지에서 연결 해제된 섹션 (page_id = null)
|
||||
const sectionsAsTemplates: SectionTemplate[] = useMemo(() => {
|
||||
console.log('[sectionsAsTemplates] useMemo 재계산! itemPages:', itemPages.map(p => ({
|
||||
id: p.id,
|
||||
sections: p.sections.map(s => ({ id: s.id, fieldsCount: s.fields?.length || 0 }))
|
||||
})));
|
||||
|
||||
// 1. itemPages에 연결된 섹션들
|
||||
const linkedSections = itemPages.flatMap(page =>
|
||||
page.sections.map(section => ({
|
||||
id: section.id,
|
||||
tenant_id: section.tenant_id || 0,
|
||||
template_name: section.title,
|
||||
section_type: section.section_type,
|
||||
description: section.description || null,
|
||||
default_fields: null,
|
||||
// ItemField → TemplateField 변환
|
||||
fields: section.fields?.map(field => ({
|
||||
id: field.id.toString(),
|
||||
name: field.field_name,
|
||||
fieldKey: field.field_name.toLowerCase().replace(/\s+/g, '_'),
|
||||
property: {
|
||||
inputType: field.field_type,
|
||||
// 2025-11-27: is_required와 properties.required 둘 다 체크
|
||||
required: field.is_required || field.properties?.required,
|
||||
options: field.options?.map((opt: { label: string; value: string }) => opt.label || opt.value),
|
||||
},
|
||||
description: field.placeholder || undefined,
|
||||
} as TemplateField)),
|
||||
bomItems: section.bom_items,
|
||||
created_by: section.created_by || null,
|
||||
updated_by: section.updated_by || null,
|
||||
created_at: section.created_at,
|
||||
updated_at: section.updated_at,
|
||||
}))
|
||||
);
|
||||
|
||||
// 2. 독립 섹션들 (page_id = null, 연결 해제된 섹션)
|
||||
const unlinkedSections = independentSections.map(section => ({
|
||||
id: section.id,
|
||||
tenant_id: section.tenant_id || 0,
|
||||
template_name: section.title,
|
||||
section_type: section.section_type,
|
||||
description: section.description || null,
|
||||
default_fields: null,
|
||||
fields: section.fields?.map(field => ({
|
||||
id: field.id.toString(),
|
||||
name: field.field_name,
|
||||
fieldKey: field.field_name.toLowerCase().replace(/\s+/g, '_'),
|
||||
property: {
|
||||
inputType: field.field_type,
|
||||
// 2025-11-27: is_required와 properties.required 둘 다 체크
|
||||
required: field.is_required || field.properties?.required,
|
||||
options: field.options?.map((opt: { label: string; value: string }) => opt.label || opt.value),
|
||||
},
|
||||
description: field.placeholder || undefined,
|
||||
} as TemplateField)),
|
||||
bomItems: section.bom_items,
|
||||
created_by: section.created_by || null,
|
||||
updated_by: section.updated_by || null,
|
||||
created_at: section.created_at,
|
||||
updated_at: section.updated_at,
|
||||
}));
|
||||
|
||||
// 3. 중복 제거 (같은 섹션이 여러 페이지에 연결되었거나, 연결 섹션과 독립 섹션에 동시 존재하는 경우)
|
||||
const allSections = [...linkedSections, ...unlinkedSections];
|
||||
const uniqueSections = Array.from(
|
||||
new Map(allSections.map(s => [s.id, s])).values()
|
||||
);
|
||||
return uniqueSections;
|
||||
}, [itemPages, independentSections]);
|
||||
|
||||
// 2025-11-27: sectionsAsTemplates 변화 추적 (디버깅용)
|
||||
useEffect(() => {
|
||||
console.log('[ItemMasterDataManagement] 📋 sectionsAsTemplates changed:', {
|
||||
count: sectionsAsTemplates.length,
|
||||
sections: sectionsAsTemplates.map(s => ({
|
||||
id: s.id,
|
||||
name: s.template_name,
|
||||
fieldsCount: s.fields?.length || 0
|
||||
}))
|
||||
});
|
||||
}, [sectionsAsTemplates]);
|
||||
|
||||
// 마운트 상태 추적 (SSR 호환)
|
||||
const [_mounted, setMounted] = useState(false);
|
||||
|
||||
@@ -294,22 +421,67 @@ export function ItemMasterDataManagement() {
|
||||
|
||||
const data = await itemMasterApi.init();
|
||||
|
||||
// 페이지 데이터 로드 (이미 존재하는 데이터를 state에 로드 - API 호출 없음)
|
||||
// 2025-11-26: 백엔드가 entity_relationships 기반으로 변경됨
|
||||
// - pages[].sections: entity_relationships 기반으로 연결된 섹션 (이미 포함)
|
||||
// - sections: 모든 독립 섹션 (재사용 가능 목록)
|
||||
// - sectionTemplates: 삭제됨 → sections로 대체
|
||||
|
||||
// 1. 페이지 데이터 로드 (섹션이 이미 포함되어 있음)
|
||||
const transformedPages = transformPagesResponse(data.pages);
|
||||
loadItemPages(transformedPages);
|
||||
|
||||
// 섹션 템플릿 로드 (덮어쓰기 - API 호출 없음!)
|
||||
const transformedTemplates = transformSectionTemplatesResponse(data.sectionTemplates);
|
||||
loadSectionTemplates(transformedTemplates);
|
||||
// 2. 독립 섹션 로드 (모든 재사용 가능 섹션)
|
||||
// 백엔드가 sections 배열로 모든 독립 섹션을 반환
|
||||
if (data.sections && data.sections.length > 0) {
|
||||
const transformedSections = transformSectionsResponse(data.sections);
|
||||
loadIndependentSections(transformedSections);
|
||||
console.log('✅ 독립 섹션 로드:', transformedSections.length);
|
||||
}
|
||||
|
||||
// 마스터 필드 로드 (덮어쓰기 - API 호출 없음!)
|
||||
const transformedFields = transformMasterFieldsResponse(data.masterFields);
|
||||
loadItemMasterFields(transformedFields);
|
||||
// 3. 섹션 템플릿 로드 (sectionTemplates → sections로 통합됨)
|
||||
// 기존 sectionTemplates가 있으면 호환성 유지, 없으면 sections 사용
|
||||
if (data.sectionTemplates && data.sectionTemplates.length > 0) {
|
||||
const transformedTemplates = transformSectionTemplatesResponse(data.sectionTemplates);
|
||||
loadSectionTemplates(transformedTemplates);
|
||||
} else if (data.sections && data.sections.length > 0) {
|
||||
// sectionTemplates가 없으면 sections에서 is_template=true인 것만 사용
|
||||
const templates = data.sections
|
||||
.filter((s: { is_template?: boolean }) => s.is_template)
|
||||
.map(transformSectionTemplateFromSection);
|
||||
if (templates.length > 0) {
|
||||
loadSectionTemplates(templates);
|
||||
}
|
||||
}
|
||||
|
||||
// 커스텀 탭 로드 (local state)
|
||||
// 필드 로드 (2025-11-27: masterFields가 fields로 통합됨)
|
||||
// data.fields = 모든 필드 목록 (백엔드 init API에서 반환)
|
||||
if (data.fields && data.fields.length > 0) {
|
||||
const transformedFields = transformFieldsResponse(data.fields);
|
||||
|
||||
// 2025-11-27: section_id가 null인 필드만 필터링 (독립 필드)
|
||||
const independentOnlyFields = transformedFields.filter(
|
||||
f => f.section_id === null || f.section_id === undefined
|
||||
);
|
||||
|
||||
// 2025-11-27: 항목탭용 (itemMasterFields) - 모든 필드 로드
|
||||
// 계층구조에서 추가한 필드도 항목탭에 바로 표시되도록 함
|
||||
// addFieldToSection에서 setItemMasterFields를 호출하므로 일관성 유지
|
||||
loadItemMasterFields(transformedFields as any);
|
||||
|
||||
// 독립 필드용 (independentFields) - section_id=null인 필드만
|
||||
loadIndependentFields(independentOnlyFields);
|
||||
|
||||
console.log('✅ 필드 로드:', {
|
||||
total: transformedFields.length,
|
||||
independent: independentOnlyFields.length,
|
||||
allFieldsForItemsTab: transformedFields.length,
|
||||
});
|
||||
}
|
||||
|
||||
// 커스텀 탭 로드 (local state) - 교체 방식 (복제 방지)
|
||||
if (data.customTabs && data.customTabs.length > 0) {
|
||||
const transformedTabs = transformCustomTabsResponse(data.customTabs);
|
||||
setCustomTabs(prev => [...prev, ...transformedTabs]);
|
||||
setCustomTabs(transformedTabs);
|
||||
}
|
||||
|
||||
// 단위 옵션 로드 (local state)
|
||||
@@ -319,9 +491,9 @@ export function ItemMasterDataManagement() {
|
||||
}
|
||||
|
||||
console.log('✅ Initial data loaded:', {
|
||||
pages: data.pages.length,
|
||||
templates: data.sectionTemplates.length,
|
||||
masterFields: data.masterFields.length,
|
||||
pages: data.pages?.length || 0,
|
||||
sections: data.sections?.length || 0,
|
||||
fields: data.fields?.length || 0,
|
||||
customTabs: data.customTabs?.length || 0,
|
||||
unitOptions: data.unitOptions?.length || 0,
|
||||
});
|
||||
@@ -365,6 +537,61 @@ export function ItemMasterDataManagement() {
|
||||
// BOM 관리 상태 (훅에 없음)
|
||||
const [_bomItems, setBomItems] = useState<BOMItem[]>([]);
|
||||
|
||||
// 2025-11-26 추가: 섹션/필드 불러오기 다이얼로그 상태
|
||||
const [isImportSectionDialogOpen, setIsImportSectionDialogOpen] = useState(false);
|
||||
const [isImportFieldDialogOpen, setIsImportFieldDialogOpen] = useState(false);
|
||||
const [selectedImportSectionId, setSelectedImportSectionId] = useState<number | null>(null);
|
||||
const [selectedImportFieldId, setSelectedImportFieldId] = useState<number | null>(null);
|
||||
const [importFieldTargetSectionId, setImportFieldTargetSectionId] = useState<number | null>(null);
|
||||
|
||||
// 2025-11-26 추가: 섹션 불러오기 핸들러
|
||||
const handleImportSection = async () => {
|
||||
if (!selectedPageId || !selectedImportSectionId) return;
|
||||
|
||||
try {
|
||||
await linkSectionToPage(selectedPageId, selectedImportSectionId);
|
||||
toast.success('섹션을 불러왔습니다.');
|
||||
setSelectedImportSectionId(null);
|
||||
} catch (error) {
|
||||
console.error('섹션 불러오기 실패:', error);
|
||||
toast.error(getErrorMessage(error));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 필드 불러오기 핸들러
|
||||
*
|
||||
* @description 2025-11-27: API 변경으로 단순화
|
||||
* - 이전: source 파라미터로 'master' | 'independent' 구분
|
||||
* - 현재: 모든 필드가 item_fields로 통합 → linkFieldToSection만 사용
|
||||
* - section_id=NULL인 필드를 섹션에 연결하는 방식으로 통일
|
||||
*/
|
||||
const handleImportField = async () => {
|
||||
if (!importFieldTargetSectionId || !selectedImportFieldId) return;
|
||||
|
||||
try {
|
||||
// 2025-11-27: 통합된 필드 연결 방식
|
||||
await linkFieldToSection(importFieldTargetSectionId, selectedImportFieldId);
|
||||
toast.success('필드를 섹션에 연결했습니다.');
|
||||
|
||||
setSelectedImportFieldId(null);
|
||||
setImportFieldTargetSectionId(null);
|
||||
} catch (error) {
|
||||
console.error('필드 불러오기 실패:', error);
|
||||
toast.error(getErrorMessage(error));
|
||||
}
|
||||
};
|
||||
|
||||
// 2025-11-26 추가: 섹션 복제 핸들러
|
||||
const handleCloneSection = async (sectionId: number) => {
|
||||
try {
|
||||
await cloneSection(sectionId);
|
||||
toast.success('섹션이 복제되었습니다.');
|
||||
} catch (error) {
|
||||
console.error('섹션 복제 실패:', error);
|
||||
toast.error(getErrorMessage(error));
|
||||
}
|
||||
};
|
||||
|
||||
// ===== 이하 핸들러들은 훅으로 이동되어 제거됨 =====
|
||||
// handleAddOption, handleDeleteOption → useAttributeManagement
|
||||
@@ -385,7 +612,7 @@ export function ItemMasterDataManagement() {
|
||||
};
|
||||
|
||||
// 섹션 삭제 핸들러 (pendingChanges 제거 포함) - 훅에 없어 유지
|
||||
const handleDeleteSectionWithTracking = (pageId: number, sectionId: number) => {
|
||||
const _handleDeleteSectionWithTracking = (pageId: number, sectionId: number) => {
|
||||
const page = itemPages.find(p => p.id === pageId);
|
||||
const sectionToDelete = page?.sections.find(s => s.id === sectionId);
|
||||
const fieldIds = sectionToDelete?.fields?.map(f => f.id) || [];
|
||||
@@ -393,10 +620,16 @@ export function ItemMasterDataManagement() {
|
||||
console.log('섹션 삭제 완료:', { sectionId, removedFields: fieldIds.length });
|
||||
};
|
||||
|
||||
// 필드 삭제 핸들러 (pendingChanges 제거 포함) - 훅에 없어 유지
|
||||
const handleDeleteFieldWithTracking = (_pageId: string, _sectionId: string, fieldId: string) => {
|
||||
deleteField(Number(fieldId));
|
||||
console.log('필드 삭제 완료:', fieldId);
|
||||
// 필드 연결 해제 핸들러 (2025-11-27: 삭제 → unlink로 변경)
|
||||
// 섹션에서 필드 연결만 해제하고, 필드 자체는 독립 필드 목록에 유지됨
|
||||
const handleUnlinkFieldWithTracking = async (_pageId: string, sectionId: string, fieldId: string) => {
|
||||
try {
|
||||
await unlinkFieldFromSection(Number(sectionId), Number(fieldId));
|
||||
console.log('필드 연결 해제 완료:', fieldId);
|
||||
} catch (error) {
|
||||
console.error('필드 연결 해제 실패:', error);
|
||||
toast.error('필드 연결 해제에 실패했습니다');
|
||||
}
|
||||
};
|
||||
|
||||
// 절대경로 업데이트 - 로컬에서 처리
|
||||
@@ -424,7 +657,9 @@ export function ItemMasterDataManagement() {
|
||||
const handleLoadTemplateWrapper = () => handleLoadTemplate(selectedPage);
|
||||
|
||||
// setter 래퍼들 (Dispatch<SetStateAction> 타입 호환성)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const setNewSectionTypeWrapper: React.Dispatch<React.SetStateAction<'fields' | 'bom'>> = setNewSectionType as any;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const setNewPageItemTypeWrapper: React.Dispatch<React.SetStateAction<'FG' | 'PT' | 'SM' | 'RM' | 'CS'>> = setNewPageItemType as any;
|
||||
|
||||
// ===== 유틸리티 함수들 =====
|
||||
@@ -436,23 +671,24 @@ export function ItemMasterDataManagement() {
|
||||
};
|
||||
|
||||
// 섹션 순서 변경 핸들러 (드래그앤드롭)
|
||||
const moveSection = (dragIndex: number, hoverIndex: number) => {
|
||||
const moveSection = async (dragIndex: number, hoverIndex: number) => {
|
||||
if (!selectedPage) return;
|
||||
|
||||
const sections = [...selectedPage.sections];
|
||||
const [draggedSection] = sections.splice(dragIndex, 1);
|
||||
sections.splice(hoverIndex, 0, draggedSection);
|
||||
|
||||
// order 값 재설정
|
||||
const updatedSections = sections.map((section, idx) => ({
|
||||
...section,
|
||||
order: idx + 1
|
||||
}));
|
||||
// 새로운 순서의 섹션 ID 배열 생성
|
||||
const sectionIds = sections.map(s => s.id);
|
||||
|
||||
// 페이지 업데이트
|
||||
updateItemPage(selectedPage.id, { sections: updatedSections });
|
||||
// hasUnsavedChanges는 computed value이므로 자동 계산됨
|
||||
toast.success('섹션 순서가 변경되었습니다 (저장 필요)');
|
||||
try {
|
||||
// API를 통해 섹션 순서 변경 (Context의 reorderSections 사용)
|
||||
await reorderSections(selectedPage.id, sectionIds);
|
||||
toast.success('섹션 순서가 변경되었습니다');
|
||||
} catch (error) {
|
||||
console.error('섹션 순서 변경 실패:', error);
|
||||
toast.error('섹션 순서 변경에 실패했습니다');
|
||||
}
|
||||
};
|
||||
|
||||
// 필드 순서 변경 핸들러
|
||||
@@ -520,12 +756,12 @@ export function ItemMasterDataManagement() {
|
||||
// 에러 발생 시 UI
|
||||
if (error) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen p-4">
|
||||
<ErrorMessage
|
||||
message={error}
|
||||
onRetry={() => window.location.reload()}
|
||||
/>
|
||||
</div>
|
||||
<ServerErrorPage
|
||||
title="데이터를 불러올 수 없습니다"
|
||||
message={error}
|
||||
onRetry={() => window.location.reload()}
|
||||
showContactInfo={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -935,6 +1171,7 @@ export function ItemMasterDataManagement() {
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
|
||||
{propertiesArray.map((property: any) => {
|
||||
const inputTypeLabel =
|
||||
property.type === 'textbox' ? '텍스트박스' :
|
||||
@@ -1147,7 +1384,7 @@ export function ItemMasterDataManagement() {
|
||||
{/* 섹션관리 탭 */}
|
||||
<TabsContent value="sections" className="space-y-4">
|
||||
<SectionsTab
|
||||
sectionTemplates={sectionTemplates}
|
||||
sectionTemplates={sectionsAsTemplates}
|
||||
setIsSectionTemplateDialogOpen={setIsSectionTemplateDialogOpen}
|
||||
setCurrentTemplateId={setCurrentTemplateId}
|
||||
setIsTemplateFieldDialogOpen={setIsTemplateFieldDialogOpen}
|
||||
@@ -1160,6 +1397,10 @@ export function ItemMasterDataManagement() {
|
||||
handleDeleteBOMItemFromTemplate={handleDeleteBOMItemFromTemplate}
|
||||
ITEM_TYPE_OPTIONS={ITEM_TYPE_OPTIONS}
|
||||
INPUT_TYPE_OPTIONS={INPUT_TYPE_OPTIONS}
|
||||
unitOptions={unitOptions.map(opt => ({ value: opt.value, label: opt.label }))}
|
||||
onCloneSection={handleCloneSection}
|
||||
setIsImportFieldDialogOpen={setIsImportFieldDialogOpen}
|
||||
setImportFieldTargetSectionId={setImportFieldTargetSectionId}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
@@ -1169,6 +1410,7 @@ export function ItemMasterDataManagement() {
|
||||
itemPages={itemPages}
|
||||
selectedPage={selectedPage}
|
||||
ITEM_TYPE_OPTIONS={ITEM_TYPE_OPTIONS}
|
||||
unitOptions={unitOptions.map(opt => ({ value: opt.value, label: opt.label }))}
|
||||
editingPageId={editingPageId}
|
||||
setEditingPageId={setEditingPageId}
|
||||
editingPageName={editingPageName}
|
||||
@@ -1199,11 +1441,18 @@ export function ItemMasterDataManagement() {
|
||||
handleEditSectionTitle={handleEditSectionTitle}
|
||||
handleSaveSectionTitle={handleSaveSectionTitleWrapper}
|
||||
moveSection={moveSection}
|
||||
deleteSection={handleDeleteSectionWithTracking}
|
||||
unlinkSection={handleUnlinkSection}
|
||||
updateSection={updateSection}
|
||||
deleteField={handleDeleteFieldWithTracking}
|
||||
deleteField={handleUnlinkFieldWithTracking}
|
||||
handleEditField={handleEditField}
|
||||
moveField={moveField}
|
||||
setIsImportSectionDialogOpen={setIsImportSectionDialogOpen}
|
||||
setIsImportFieldDialogOpen={setIsImportFieldDialogOpen}
|
||||
setImportFieldTargetSectionId={setImportFieldTargetSectionId}
|
||||
// 2025-11-27 추가: BOM 항목 API 함수
|
||||
addBOMItem={addBOMItem}
|
||||
updateBOMItem={updateBOMItem}
|
||||
deleteBOMItem={deleteBOMItem}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
@@ -1346,7 +1595,7 @@ export function ItemMasterDataManagement() {
|
||||
handleAddSection={handleAddSectionWrapper}
|
||||
sectionInputMode={sectionInputMode}
|
||||
setSectionInputMode={setSectionInputMode}
|
||||
sectionTemplates={sectionTemplates}
|
||||
sectionTemplates={sectionsAsTemplates}
|
||||
selectedTemplateId={selectedSectionTemplateId}
|
||||
setSelectedTemplateId={setSelectedSectionTemplateId}
|
||||
handleLinkTemplate={handleLinkTemplateWrapper}
|
||||
@@ -1555,6 +1804,35 @@ export function ItemMasterDataManagement() {
|
||||
setSelectedTemplateId={setSelectedTemplateId}
|
||||
handleLoadTemplate={handleLoadTemplateWrapper}
|
||||
/>
|
||||
|
||||
{/* 섹션 불러오기 다이얼로그 */}
|
||||
<ImportSectionDialog
|
||||
isOpen={isImportSectionDialogOpen}
|
||||
setIsOpen={setIsImportSectionDialogOpen}
|
||||
independentSections={independentSections}
|
||||
selectedSectionId={selectedImportSectionId}
|
||||
setSelectedSectionId={setSelectedImportSectionId}
|
||||
onImport={handleImportSection}
|
||||
onRefresh={refreshIndependentSections}
|
||||
onGetUsage={getSectionUsage}
|
||||
/>
|
||||
|
||||
{/* 필드 불러오기 다이얼로그 - 2025-11-27: 탭 통합 (항목+독립필드 → 필드) */}
|
||||
<ImportFieldDialog
|
||||
isOpen={isImportFieldDialogOpen}
|
||||
setIsOpen={setIsImportFieldDialogOpen}
|
||||
fields={itemMasterFields}
|
||||
selectedFieldId={selectedImportFieldId}
|
||||
setSelectedFieldId={setSelectedImportFieldId}
|
||||
onImport={handleImportField}
|
||||
onRefresh={refreshIndependentFields}
|
||||
onGetUsage={getFieldUsage}
|
||||
targetSectionTitle={
|
||||
importFieldTargetSectionId
|
||||
? selectedPage?.sections.find(s => s.id === importFieldTargetSectionId)?.title
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</PageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user