Files
sam-react-prod/src/components/items/ItemMasterDataManagement.tsx
유병철 f5362e6887 feat(WEB): 회계/HR/생산/품질 탭 복원 및 대시보드·검색 개선
- 회계 모듈 탭 UI 복원 (대손/은행거래/청구/입금/예상경비/상품권/매입/매출/세금계산서/거래처원장/거래처/출금)
- HR 모듈 탭 복원 (근태/급여/휴가)
- 대시보드 type2/3/4 페이지 개선
- CEO 대시보드 섹션 로딩 최적화
- 품목 마스터데이터 관리 탭 기능 강화
- 생산 작업자화면/작업지시 개선
- 품질 검사 생성/상세 화면 보완
- 건설 견적/현장관리 상세 개선
- UniversalListPage 기능 확장
- E2E 잔여 버그 핸드오프 문서 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 14:55:40 +09:00

1037 lines
45 KiB
TypeScript

'use client';
import { useState, useEffect, useMemo } from 'react';
import dynamic from 'next/dynamic';
import { PageLayout } from '@/components/organisms/PageLayout';
import { PageHeader } from '@/components/organisms/PageHeader';
import { useItemMaster } from '@/contexts/ItemMasterContext';
import type { SectionTemplate, BOMItem, TemplateField } from '@/contexts/ItemMasterContext';
import { MasterFieldTab, HierarchyTab, SectionsTab } from './ItemMasterDataManagement/tabs';
import { DetailPageSkeleton } from '@/components/ui/skeleton';
import { ServerErrorPage } from '@/components/common/ServerErrorPage';
// 2025-12-24: Phase 2 UI 컴포넌트 분리
import { AttributeTabContent } from './ItemMasterDataManagement/components';
import {
Database,
FileText,
} from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
// 다이얼로그 컴포넌트 - lazy load (사용자 클릭 시에만 로드)
const TabManagementDialogs = dynamic(
() => import('./ItemMasterDataManagement/dialogs/TabManagementDialogs').then(mod => ({ default: mod.TabManagementDialogs })),
);
const OptionDialog = dynamic(
() => import('./ItemMasterDataManagement/dialogs/OptionDialog').then(mod => ({ default: mod.OptionDialog })),
);
const ColumnManageDialog = dynamic(
() => import('./ItemMasterDataManagement/dialogs/ColumnManageDialog').then(mod => ({ default: mod.ColumnManageDialog })),
);
const PathEditDialog = dynamic(
() => import('./ItemMasterDataManagement/dialogs/PathEditDialog').then(mod => ({ default: mod.PathEditDialog })),
);
const PageDialog = dynamic(
() => import('./ItemMasterDataManagement/dialogs/PageDialog').then(mod => ({ default: mod.PageDialog })),
);
const SectionDialog = dynamic(
() => import('./ItemMasterDataManagement/dialogs/SectionDialog').then(mod => ({ default: mod.SectionDialog })),
);
const FieldDialog = dynamic(
() => import('./ItemMasterDataManagement/dialogs/FieldDialog').then(mod => ({ default: mod.FieldDialog })),
);
const FieldDrawer = dynamic(
() => import('./ItemMasterDataManagement/dialogs/FieldDrawer').then(mod => ({ default: mod.FieldDrawer })),
);
const ColumnDialog = dynamic(
() => import('./ItemMasterDataManagement/dialogs/ColumnDialog').then(mod => ({ default: mod.ColumnDialog })),
);
const MasterFieldDialog = dynamic(
() => import('./ItemMasterDataManagement/dialogs/MasterFieldDialog').then(mod => ({ default: mod.MasterFieldDialog })),
);
const SectionTemplateDialog = dynamic(
() => import('./ItemMasterDataManagement/dialogs/SectionTemplateDialog').then(mod => ({ default: mod.SectionTemplateDialog })),
);
const TemplateFieldDialog = dynamic(
() => import('./ItemMasterDataManagement/dialogs/TemplateFieldDialog').then(mod => ({ default: mod.TemplateFieldDialog })),
);
const LoadTemplateDialog = dynamic(
() => import('./ItemMasterDataManagement/dialogs/LoadTemplateDialog').then(mod => ({ default: mod.LoadTemplateDialog })),
);
const ImportSectionDialog = dynamic(
() => import('./ItemMasterDataManagement/dialogs/ImportSectionDialog').then(mod => ({ default: mod.ImportSectionDialog })),
);
const ImportFieldDialog = dynamic(
() => import('./ItemMasterDataManagement/dialogs/ImportFieldDialog').then(mod => ({ default: mod.ImportFieldDialog })),
);
// 커스텀 훅 import
import {
usePageManagement,
useSectionManagement,
useFieldManagement,
useMasterFieldManagement,
useTemplateManagement,
useAttributeManagement,
useTabManagement,
// 2025-12-24: 신규 훅 추가
useInitialDataLoading,
useImportManagement,
useReorderManagement,
useDeleteManagement,
} from './ItemMasterDataManagement/hooks';
// 에러 알림 Context
import { ErrorAlertProvider } from './ItemMasterDataManagement/contexts';
const ITEM_TYPE_OPTIONS = [
{ value: 'FG', label: '제품 (FG)' },
{ value: 'PT', label: '부품 (PT)' },
{ value: 'SM', label: '부자재 (SM)' },
{ value: 'RM', label: '원자재 (RM)' },
{ value: 'CS', label: '소모품 (CS)' }
];
const INPUT_TYPE_OPTIONS = [
{ value: 'textbox', label: '텍스트박스' },
{ value: 'dropdown', label: '드롭다운' },
{ value: 'checkbox', label: '체크박스' },
{ value: 'number', label: '숫자' },
{ value: 'date', label: '날짜' },
{ value: 'textarea', label: '텍스트영역' }
];
// Wrapper 컴포넌트: ErrorAlertProvider를 먼저 제공
export function ItemMasterDataManagement() {
return (
<ErrorAlertProvider>
<ItemMasterDataManagementContent />
</ErrorAlertProvider>
);
}
// 실제 로직을 담는 내부 컴포넌트
function ItemMasterDataManagementContent() {
const {
itemPages,
loadItemPages: _loadItemPages,
updateItemPage,
deleteItemPage: _deleteItemPage,
updateSection,
deleteSection: _deleteSection,
reorderFields: _reorderFields,
itemMasterFields,
loadItemMasterFields: _loadItemMasterFields,
sectionTemplates,
loadSectionTemplates: _loadSectionTemplates,
resetAllData: _resetAllData,
// 2025-11-26 추가: 독립 엔티티 관리
independentSections,
loadIndependentSections: _loadIndependentSections,
loadIndependentFields: _loadIndependentFields,
refreshIndependentSections,
refreshIndependentFields,
linkSectionToPage: _linkSectionToPage,
linkFieldToSection: _linkFieldToSection,
unlinkFieldFromSection: _unlinkFieldFromSection,
getSectionUsage,
getFieldUsage,
cloneSection: _cloneSection,
reorderSections: _reorderSections,
// 2025-11-27 추가: BOM 항목 API 함수
addBOMItem,
updateBOMItem,
deleteBOMItem,
} = useItemMaster();
// ===== 커스텀 훅 초기화 =====
const pageManagement = usePageManagement();
const sectionManagement = useSectionManagement();
const fieldManagement = useFieldManagement();
const masterFieldManagement = useMasterFieldManagement();
const templateManagement = useTemplateManagement();
const attributeManagement = useAttributeManagement();
const tabManagement = useTabManagement();
// 2025-12-24: 신규 훅 (먼저 tabManagement, attributeManagement에서 setter 추출 필요)
const {
customTabs, setCustomTabs,
activeTab, setActiveTab,
attributeSubTabs, setAttributeSubTabs,
activeAttributeTab, setActiveAttributeTab,
isAddTabDialogOpen, setIsAddTabDialogOpen,
isManageTabsDialogOpen, setIsManageTabsDialogOpen,
newTabLabel, setNewTabLabel,
editingTabId, setEditingTabId,
deletingTabId, setDeletingTabId,
isDeleteTabDialogOpen, setIsDeleteTabDialogOpen,
isManageAttributeTabsDialogOpen, setIsManageAttributeTabsDialogOpen,
isAddAttributeTabDialogOpen, setIsAddAttributeTabDialogOpen,
newAttributeTabLabel, setNewAttributeTabLabel,
editingAttributeTabId, setEditingAttributeTabId,
deletingAttributeTabId, setDeletingAttributeTabId,
isDeleteAttributeTabDialogOpen, setIsDeleteAttributeTabDialogOpen,
handleAddTab, handleUpdateTab, handleDeleteTab, confirmDeleteTab,
handleAddAttributeTab, handleUpdateAttributeTab,
handleDeleteAttributeTab, confirmDeleteAttributeTab,
moveTabUp, moveTabDown,
moveAttributeTabUp, moveAttributeTabDown,
getTabIcon, handleEditTabFromManage,
} = tabManagement;
const {
unitOptions, setUnitOptions,
materialOptions, setMaterialOptions,
surfaceTreatmentOptions, setSurfaceTreatmentOptions,
customAttributeOptions, setCustomAttributeOptions,
isOptionDialogOpen, setIsOptionDialogOpen,
editingOptionType, setEditingOptionType,
newOptionValue, setNewOptionValue,
newOptionLabel, setNewOptionLabel,
newOptionColumnValues, setNewOptionColumnValues,
newOptionInputType, setNewOptionInputType,
newOptionRequired, setNewOptionRequired,
newOptionOptions, setNewOptionOptions,
newOptionPlaceholder, setNewOptionPlaceholder,
newOptionDefaultValue, setNewOptionDefaultValue,
isColumnManageDialogOpen, setIsColumnManageDialogOpen,
managingColumnType, setManagingColumnType,
attributeColumns, setAttributeColumns,
newColumnName, setNewColumnName,
newColumnKey, setNewColumnKey,
newColumnType, setNewColumnType,
newColumnRequired, setNewColumnRequired,
handleAddOption, handleDeleteOption,
} = attributeManagement;
// 2025-12-24: 신규 훅 초기화
const { isInitialLoading, error } = useInitialDataLoading({
setCustomTabs,
setUnitOptions,
});
const importManagement = useImportManagement();
const {
isImportSectionDialogOpen, setIsImportSectionDialogOpen,
isImportFieldDialogOpen, setIsImportFieldDialogOpen,
selectedImportSectionId, setSelectedImportSectionId,
selectedImportFieldId, setSelectedImportFieldId,
importFieldTargetSectionId, setImportFieldTargetSectionId,
handleImportSection: handleImportSectionFromHook,
handleImportField: handleImportFieldFromHook,
handleCloneSection,
} = importManagement;
const reorderManagement = useReorderManagement();
const { moveSection: moveSectionFromHook, moveField: moveFieldFromHook } = reorderManagement;
const deleteManagement = useDeleteManagement({ itemPages });
const {
handleDeletePage: handleDeletePageWithTracking,
handleUnlinkField: handleUnlinkFieldWithTracking,
handleResetAllData: handleResetAllDataFromHook,
} = deleteManagement;
// 훅에서 필요한 값들 구조분해
const {
selectedPageId, setSelectedPageId, selectedPage,
editingPageId, setEditingPageId, editingPageName, setEditingPageName,
isPageDialogOpen, setIsPageDialogOpen,
newPageName, setNewPageName, newPageItemType, setNewPageItemType,
editingPathPageId, setEditingPathPageId, editingAbsolutePath, setEditingAbsolutePath,
handleAddPage, handleDuplicatePage,
} = pageManagement;
const {
editingSectionId, setEditingSectionId,
editingSectionTitle, setEditingSectionTitle,
isSectionDialogOpen, setIsSectionDialogOpen,
newSectionTitle, setNewSectionTitle,
newSectionDescription, setNewSectionDescription,
newSectionType, setNewSectionType,
sectionInputMode, setSectionInputMode,
selectedSectionTemplateId, setSelectedSectionTemplateId,
handleAddSection, handleLinkTemplate,
handleEditSectionTitle, handleSaveSectionTitle,
handleUnlinkSection,
} = sectionManagement;
const {
isFieldDialogOpen, setIsFieldDialogOpen,
selectedSectionForField, setSelectedSectionForField,
editingFieldId, setEditingFieldId,
fieldInputMode, setFieldInputMode,
showMasterFieldList, setShowMasterFieldList,
selectedMasterFieldId, setSelectedMasterFieldId,
newFieldName, setNewFieldName,
newFieldKey, setNewFieldKey,
newFieldInputType, setNewFieldInputType,
newFieldRequired, setNewFieldRequired,
newFieldOptions, setNewFieldOptions,
newFieldDescription, setNewFieldDescription,
textboxColumns, setTextboxColumns,
isColumnDialogOpen, setIsColumnDialogOpen,
editingColumnId, setEditingColumnId,
columnName, setColumnName,
columnKey, setColumnKey,
newFieldConditionEnabled, setNewFieldConditionEnabled,
newFieldConditionTargetType, setNewFieldConditionTargetType,
newFieldConditionFields, setNewFieldConditionFields,
newFieldConditionSections, setNewFieldConditionSections,
tempConditionValue, setTempConditionValue,
handleAddField, handleEditField,
} = fieldManagement;
const {
isMasterFieldDialogOpen, setIsMasterFieldDialogOpen,
editingMasterFieldId, setEditingMasterFieldId,
newMasterFieldName, setNewMasterFieldName,
newMasterFieldKey, setNewMasterFieldKey,
newMasterFieldInputType, setNewMasterFieldInputType,
newMasterFieldRequired, setNewMasterFieldRequired,
newMasterFieldCategory, setNewMasterFieldCategory,
newMasterFieldDescription, setNewMasterFieldDescription,
newMasterFieldOptions, setNewMasterFieldOptions,
newMasterFieldAttributeType, setNewMasterFieldAttributeType,
newMasterFieldMultiColumn, setNewMasterFieldMultiColumn,
newMasterFieldColumnCount, setNewMasterFieldColumnCount,
newMasterFieldColumnNames, setNewMasterFieldColumnNames,
handleAddMasterField, handleEditMasterField,
handleUpdateMasterField, handleDeleteMasterField,
} = masterFieldManagement;
const {
isSectionTemplateDialogOpen, setIsSectionTemplateDialogOpen,
editingSectionTemplateId, setEditingSectionTemplateId,
newSectionTemplateTitle, setNewSectionTemplateTitle,
newSectionTemplateDescription, setNewSectionTemplateDescription,
newSectionTemplateCategory, setNewSectionTemplateCategory,
newSectionTemplateType, setNewSectionTemplateType,
isLoadTemplateDialogOpen, setIsLoadTemplateDialogOpen,
selectedTemplateId, setSelectedTemplateId,
isTemplateFieldDialogOpen, setIsTemplateFieldDialogOpen,
setCurrentTemplateId,
editingTemplateFieldId, setEditingTemplateFieldId,
templateFieldName, setTemplateFieldName,
templateFieldKey, setTemplateFieldKey,
templateFieldInputType, setTemplateFieldInputType,
templateFieldRequired, setTemplateFieldRequired,
templateFieldOptions, setTemplateFieldOptions,
templateFieldDescription, setTemplateFieldDescription,
templateFieldMultiColumn, setTemplateFieldMultiColumn,
templateFieldColumnCount, setTemplateFieldColumnCount,
templateFieldColumnNames, setTemplateFieldColumnNames,
templateFieldInputMode, setTemplateFieldInputMode,
templateFieldShowMasterFieldList, setTemplateFieldShowMasterFieldList,
templateFieldSelectedMasterFieldId, setTemplateFieldSelectedMasterFieldId,
handleAddSectionTemplate, handleEditSectionTemplate,
handleUpdateSectionTemplate, handleDeleteSectionTemplate,
handleLoadTemplate, handleAddTemplateField,
handleEditTemplateField, handleDeleteTemplateField,
handleAddBOMItemToTemplate, handleUpdateBOMItemInTemplate,
handleDeleteBOMItemFromTemplate,
} = templateManagement;
// 2025-11-26: itemPages의 모든 섹션 + 독립 섹션(independentSections)을 SectionTemplate 형식으로 변환
// 이렇게 하면 계층구조 탭과 섹션 탭이 같은 데이터 소스를 사용하여 자동 동기화됨
// 독립 섹션: 페이지에서 연결 해제된 섹션 (page_id = null)
const sectionsAsTemplates: SectionTemplate[] = useMemo(() => {
// 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 변환
// 2025-12-16: field_key 전체 표시 (백엔드 형식: {ID}_{사용자입력})
fields: section.fields?.map(field => {
const rawKey = field.field_key || field.field_name.toLowerCase().replace(/\s+/g, '_');
return {
id: field.id.toString(),
name: field.field_name,
fieldKey: rawKey,
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, 연결 해제된 섹션)
// 2025-12-16: field_key 전체 표시 (백엔드 형식: {ID}_{사용자입력})
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 => {
const rawKey = field.field_key || field.field_name.toLowerCase().replace(/\s+/g, '_');
return {
id: field.id.toString(),
name: field.field_name,
fieldKey: rawKey,
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. 중복 제거 (같은 섹션이 여러 페이지에 연결되었거나, 연결 섹션과 독립 섹션에 동시 존재하는 경우)
// 2025-12-01: linkedSections를 나중에 추가하여 우선시 (Map에서 나중 값이 덮어씀)
// itemPages의 섹션이 최신 상태이므로 이 데이터가 우선되어야 실시간 업데이트 반영됨
const allSections = [...unlinkedSections, ...linkedSections];
const uniqueSections = Array.from(
new Map(allSections.map(s => [s.id, s])).values()
);
return uniqueSections;
}, [itemPages, independentSections]);
// 모바일 체크
const [isMobile, setIsMobile] = useState(false);
useEffect(() => {
const checkMobile = () => setIsMobile(window.innerWidth < 768);
checkMobile();
window.addEventListener('resize', checkMobile);
return () => window.removeEventListener('resize', checkMobile);
}, []);
// 필드, 마스터필드, 템플릿 관련 상태는 위의 훅에서 관리됩니다.
// BOM 관리 상태 (훅에 없음)
const [_bomItems, setBomItems] = useState<BOMItem[]>([]);
// 2025-12-24: 신규 훅에서 가져온 핸들러 래퍼
const handleImportSection = async () => handleImportSectionFromHook(selectedPageId);
const handleImportField = async () => handleImportFieldFromHook(selectedPage ?? null);
const moveSection = async (dragIndex: number, hoverIndex: number) => moveSectionFromHook(selectedPage ?? null, dragIndex, hoverIndex);
const moveField = async (sectionId: number, dragFieldId: number, hoverFieldId: number) => moveFieldFromHook(selectedPage ?? null, sectionId, dragFieldId, hoverFieldId);
const _handleResetAllData = () => handleResetAllDataFromHook(
setUnitOptions,
setMaterialOptions,
setSurfaceTreatmentOptions,
setCustomAttributeOptions,
setAttributeColumns,
setBomItems,
setCustomTabs,
setAttributeSubTabs,
);
// ===== 래퍼 함수들 (훅 함수에 selectedPage 바인딩 및 타입 호환성) =====
const handleAddSectionWrapper = () => handleAddSection(selectedPage);
const handleLinkTemplateWrapper = (template: SectionTemplate) => handleLinkTemplate(template, selectedPage);
const handleSaveSectionTitleWrapper = () => handleSaveSectionTitle(selectedPage);
const handleAddFieldWrapper = () => handleAddField(selectedPage);
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;
// SectionTemplateDialog 카테고리 래퍼 (string | string[] → string[])
const handleSetNewSectionTemplateCategory = (category: string | string[]) => {
if (Array.isArray(category)) {
setNewSectionTemplateCategory(category);
} else {
setNewSectionTemplateCategory(category ? [category] : []);
}
};
// LoadTemplateDialog 선택 ID 래퍼 (string | number | null → string | null)
const handleSetSelectedTemplateId = (id: string | number | null) => {
if (id === null) {
setSelectedTemplateId(null);
} else {
setSelectedTemplateId(String(id));
}
};
// TemplateFieldDialog 선택 ID 래퍼 (string | number | null → string)
const handleSetTemplateFieldSelectedMasterFieldId = (id: string | number | null) => {
setTemplateFieldSelectedMasterFieldId(id === null ? '' : String(id));
};
// 초기 로딩 중 UI
if (isInitialLoading) {
return <DetailPageSkeleton />;
}
// 에러 발생 시 UI
if (error) {
return (
<ServerErrorPage
title="데이터를 불러올 수 없습니다"
message={error}
onRetry={() => window.location.reload()}
showContactInfo={true}
/>
);
}
return (
<PageLayout>
<PageHeader
title="품목기준관리"
description="품목관리에서 사용되는 기준 정보를 설정하고 관리합니다"
icon={Database}
/>
<Tabs value={activeTab} onValueChange={setActiveTab} className="min-w-0 overflow-x-hidden">
<div
className="mb-4 min-w-0 overflow-x-auto [&::-webkit-scrollbar]:hidden"
style={{ scrollbarWidth: 'none', msOverflowStyle: 'none' }}
>
<TabsList>
{customTabs.sort((a, b) => a.order - b.order).map(tab => {
const Icon = getTabIcon(tab.icon);
return (
<TabsTrigger key={tab.id} value={tab.id}>
<Icon className="w-4 h-4 mr-2" />
{tab.label}
</TabsTrigger>
);
})}
</TabsList>
{/*<Button*/}
{/* size="sm"*/}
{/* variant="outline"*/}
{/* onClick={() => setIsManageTabsDialogOpen(true)}*/}
{/*>*/}
{/* <Settings className="h-4 w-4 mr-1" />*/}
{/* 탭 관리*/}
{/*</Button>*/}
{/* 전체 초기화 버튼 숨김 처리 - 디자인에 없는 기능 */}
{/* <Button
size="sm"
variant="destructive"
onClick={handleResetAllData}
>
<Trash2 className="h-4 w-4 mr-1" />
전체 초기화
</Button> */}
</div>
{/* 속성 탭 (단위/재질/표면처리 통합) - 2025-12-24: AttributeTabContent로 분리 */}
<TabsContent value="attributes" className="space-y-4">
<AttributeTabContent
activeAttributeTab={activeAttributeTab}
setActiveAttributeTab={setActiveAttributeTab}
attributeSubTabs={attributeSubTabs}
unitOptions={unitOptions}
materialOptions={materialOptions}
surfaceTreatmentOptions={surfaceTreatmentOptions}
customAttributeOptions={customAttributeOptions}
attributeColumns={attributeColumns}
itemMasterFields={itemMasterFields}
setIsManageAttributeTabsDialogOpen={setIsManageAttributeTabsDialogOpen}
setIsOptionDialogOpen={setIsOptionDialogOpen}
setEditingOptionType={setEditingOptionType}
setNewOptionValue={setNewOptionValue}
setNewOptionLabel={setNewOptionLabel}
setNewOptionColumnValues={setNewOptionColumnValues}
setIsColumnManageDialogOpen={setIsColumnManageDialogOpen}
setManagingColumnType={setManagingColumnType}
setNewColumnName={setNewColumnName}
setNewColumnKey={setNewColumnKey}
setNewColumnType={setNewColumnType}
setNewColumnRequired={setNewColumnRequired}
handleDeleteOption={handleDeleteOption}
/>
</TabsContent>
{/* 항목 탭 */}
<TabsContent value="items" className="space-y-4">
<MasterFieldTab
itemMasterFields={itemMasterFields}
setIsMasterFieldDialogOpen={setIsMasterFieldDialogOpen}
handleEditMasterField={handleEditMasterField}
handleDeleteMasterField={handleDeleteMasterField}
hasUnsavedChanges={false}
pendingChanges={{ masterFields: [] }}
/>
</TabsContent>
{/* 섹션관리 탭 */}
<TabsContent value="sections" className="space-y-4">
<SectionsTab
sectionTemplates={sectionsAsTemplates}
setIsSectionTemplateDialogOpen={setIsSectionTemplateDialogOpen}
setCurrentTemplateId={setCurrentTemplateId}
setIsTemplateFieldDialogOpen={setIsTemplateFieldDialogOpen}
handleEditSectionTemplate={handleEditSectionTemplate}
handleDeleteSectionTemplate={handleDeleteSectionTemplate}
handleEditTemplateField={handleEditTemplateField}
handleDeleteTemplateField={handleDeleteTemplateField}
handleAddBOMItemToTemplate={handleAddBOMItemToTemplate}
handleUpdateBOMItemInTemplate={handleUpdateBOMItemInTemplate}
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>
{/* 계층구조 탭 */}
<TabsContent value="hierarchy" className="space-y-4">
<HierarchyTab
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}
setEditingPageName={setEditingPageName}
selectedPageId={selectedPageId}
setSelectedPageId={setSelectedPageId}
editingPathPageId={editingPathPageId}
setEditingPathPageId={setEditingPathPageId}
editingAbsolutePath={editingAbsolutePath}
setEditingAbsolutePath={setEditingAbsolutePath}
editingSectionId={editingSectionId}
setEditingSectionId={setEditingSectionId}
editingSectionTitle={editingSectionTitle}
setEditingSectionTitle={setEditingSectionTitle}
hasUnsavedChanges={false}
pendingChanges={{ pages: [], sections: [], fields: [], masterFields: [], attributes: [], sectionTemplates: [] }}
selectedSectionForField={selectedSectionForField}
setSelectedSectionForField={setSelectedSectionForField}
newSectionType={newSectionType}
setNewSectionType={setNewSectionTypeWrapper}
updateItemPage={updateItemPage}
trackChange={() => {}}
deleteItemPage={handleDeletePageWithTracking}
duplicatePage={handleDuplicatePage}
setIsPageDialogOpen={setIsPageDialogOpen}
setIsSectionDialogOpen={setIsSectionDialogOpen}
setIsFieldDialogOpen={setIsFieldDialogOpen}
handleEditSectionTitle={handleEditSectionTitle}
handleSaveSectionTitle={handleSaveSectionTitleWrapper}
moveSection={moveSection}
unlinkSection={handleUnlinkSection}
updateSection={updateSection}
deleteField={handleUnlinkFieldWithTracking}
handleEditField={handleEditField}
moveField={moveField}
setIsImportSectionDialogOpen={setIsImportSectionDialogOpen}
setIsImportFieldDialogOpen={setIsImportFieldDialogOpen}
setImportFieldTargetSectionId={setImportFieldTargetSectionId}
// 2025-11-27 추가: BOM 항목 API 함수
addBOMItem={addBOMItem}
updateBOMItem={updateBOMItem}
deleteBOMItem={deleteBOMItem}
/>
</TabsContent>
{/* 사용자 정의 탭들 */}
{customTabs.filter(tab => !tab.isDefault).map(tab => (
<TabsContent key={tab.id} value={tab.id}>
<Card>
<CardHeader>
<CardTitle>{tab.label}</CardTitle>
<CardDescription> . .</CardDescription>
</CardHeader>
<CardContent>
<div className="text-center py-12">
<FileText className="w-16 h-16 mx-auto text-gray-300 mb-4" />
<p className="text-muted-foreground mb-2">{tab.label} </p>
<p className="text-sm text-muted-foreground">
</p>
</div>
</CardContent>
</Card>
</TabsContent>
))}
</Tabs>
<TabManagementDialogs
isManageTabsDialogOpen={isManageTabsDialogOpen}
setIsManageTabsDialogOpen={setIsManageTabsDialogOpen}
customTabs={customTabs}
moveTabUp={moveTabUp}
moveTabDown={moveTabDown}
handleEditTabFromManage={handleEditTabFromManage}
handleDeleteTab={handleDeleteTab}
getTabIcon={getTabIcon}
setIsAddTabDialogOpen={setIsAddTabDialogOpen}
isDeleteTabDialogOpen={isDeleteTabDialogOpen}
setIsDeleteTabDialogOpen={setIsDeleteTabDialogOpen}
deletingTabId={deletingTabId}
setDeletingTabId={setDeletingTabId}
confirmDeleteTab={confirmDeleteTab}
isAddTabDialogOpen={isAddTabDialogOpen}
editingTabId={editingTabId}
setEditingTabId={setEditingTabId}
newTabLabel={newTabLabel}
setNewTabLabel={setNewTabLabel}
handleUpdateTab={handleUpdateTab}
handleAddTab={handleAddTab}
isManageAttributeTabsDialogOpen={isManageAttributeTabsDialogOpen}
setIsManageAttributeTabsDialogOpen={setIsManageAttributeTabsDialogOpen}
attributeSubTabs={attributeSubTabs}
moveAttributeTabUp={moveAttributeTabUp}
moveAttributeTabDown={moveAttributeTabDown}
handleDeleteAttributeTab={handleDeleteAttributeTab}
isDeleteAttributeTabDialogOpen={isDeleteAttributeTabDialogOpen}
setIsDeleteAttributeTabDialogOpen={setIsDeleteAttributeTabDialogOpen}
deletingAttributeTabId={deletingAttributeTabId}
setDeletingAttributeTabId={setDeletingAttributeTabId}
confirmDeleteAttributeTab={confirmDeleteAttributeTab}
isAddAttributeTabDialogOpen={isAddAttributeTabDialogOpen}
setIsAddAttributeTabDialogOpen={setIsAddAttributeTabDialogOpen}
editingAttributeTabId={editingAttributeTabId}
setEditingAttributeTabId={setEditingAttributeTabId}
newAttributeTabLabel={newAttributeTabLabel}
setNewAttributeTabLabel={setNewAttributeTabLabel}
handleUpdateAttributeTab={handleUpdateAttributeTab}
handleAddAttributeTab={handleAddAttributeTab}
/>
<OptionDialog
isOpen={isOptionDialogOpen}
setIsOpen={setIsOptionDialogOpen}
newOptionValue={newOptionValue}
setNewOptionValue={setNewOptionValue}
newOptionLabel={newOptionLabel}
setNewOptionLabel={setNewOptionLabel}
newOptionColumnValues={newOptionColumnValues}
setNewOptionColumnValues={setNewOptionColumnValues}
newOptionInputType={newOptionInputType}
setNewOptionInputType={setNewOptionInputType}
newOptionRequired={newOptionRequired}
setNewOptionRequired={setNewOptionRequired}
newOptionOptions={newOptionOptions}
setNewOptionOptions={setNewOptionOptions}
newOptionPlaceholder={newOptionPlaceholder}
setNewOptionPlaceholder={setNewOptionPlaceholder}
newOptionDefaultValue={newOptionDefaultValue}
setNewOptionDefaultValue={setNewOptionDefaultValue}
editingOptionType={editingOptionType}
attributeSubTabs={attributeSubTabs}
attributeColumns={attributeColumns}
handleAddOption={handleAddOption}
/>
<ColumnManageDialog
isOpen={isColumnManageDialogOpen}
setIsOpen={setIsColumnManageDialogOpen}
managingColumnType={managingColumnType}
attributeSubTabs={attributeSubTabs}
attributeColumns={attributeColumns}
setAttributeColumns={setAttributeColumns}
newColumnName={newColumnName}
setNewColumnName={setNewColumnName}
newColumnKey={newColumnKey}
setNewColumnKey={setNewColumnKey}
newColumnType={newColumnType}
setNewColumnType={setNewColumnType}
newColumnRequired={newColumnRequired}
setNewColumnRequired={setNewColumnRequired}
/>
<PathEditDialog
editingPathPageId={editingPathPageId}
setEditingPathPageId={setEditingPathPageId}
editingAbsolutePath={editingAbsolutePath}
setEditingAbsolutePath={setEditingAbsolutePath}
updateItemPage={updateItemPage}
trackChange={() => {}}
/>
<PageDialog
isPageDialogOpen={isPageDialogOpen}
setIsPageDialogOpen={setIsPageDialogOpen}
newPageName={newPageName}
setNewPageName={setNewPageName}
newPageItemType={newPageItemType}
setNewPageItemType={setNewPageItemTypeWrapper}
handleAddPage={handleAddPage}
/>
<SectionDialog
isSectionDialogOpen={isSectionDialogOpen}
setIsSectionDialogOpen={setIsSectionDialogOpen}
newSectionType={newSectionType}
setNewSectionType={setNewSectionType}
newSectionTitle={newSectionTitle}
setNewSectionTitle={setNewSectionTitle}
newSectionDescription={newSectionDescription}
setNewSectionDescription={setNewSectionDescription}
handleAddSection={handleAddSectionWrapper}
sectionInputMode={sectionInputMode}
setSectionInputMode={setSectionInputMode}
sectionTemplates={sectionsAsTemplates}
selectedTemplateId={selectedSectionTemplateId}
setSelectedTemplateId={setSelectedSectionTemplateId}
handleLinkTemplate={handleLinkTemplateWrapper}
/>
{/* 항목 추가/수정 다이얼로그 - 데스크톱 */}
{!isMobile && (
<FieldDialog
isOpen={isFieldDialogOpen}
onOpenChange={setIsFieldDialogOpen}
editingFieldId={editingFieldId}
setEditingFieldId={setEditingFieldId}
fieldInputMode={fieldInputMode}
setFieldInputMode={setFieldInputMode}
showMasterFieldList={showMasterFieldList}
setShowMasterFieldList={setShowMasterFieldList}
selectedMasterFieldId={selectedMasterFieldId}
setSelectedMasterFieldId={setSelectedMasterFieldId}
textboxColumns={textboxColumns}
setTextboxColumns={setTextboxColumns}
newFieldConditionEnabled={newFieldConditionEnabled}
setNewFieldConditionEnabled={setNewFieldConditionEnabled}
newFieldConditionTargetType={newFieldConditionTargetType}
setNewFieldConditionTargetType={setNewFieldConditionTargetType}
newFieldConditionFields={newFieldConditionFields}
setNewFieldConditionFields={setNewFieldConditionFields}
newFieldConditionSections={newFieldConditionSections}
setNewFieldConditionSections={setNewFieldConditionSections}
tempConditionValue={tempConditionValue}
setTempConditionValue={setTempConditionValue}
newFieldName={newFieldName}
setNewFieldName={setNewFieldName}
newFieldKey={newFieldKey}
setNewFieldKey={setNewFieldKey}
newFieldInputType={newFieldInputType}
setNewFieldInputType={setNewFieldInputType}
newFieldRequired={newFieldRequired}
setNewFieldRequired={setNewFieldRequired}
newFieldDescription={newFieldDescription}
setNewFieldDescription={setNewFieldDescription}
newFieldOptions={newFieldOptions}
setNewFieldOptions={setNewFieldOptions as (options: string | string[]) => void}
selectedSectionForField={selectedPage?.sections.find(s => s.id === selectedSectionForField) || null}
selectedPage={selectedPage || null}
itemMasterFields={itemMasterFields}
handleAddField={handleAddFieldWrapper}
setIsColumnDialogOpen={setIsColumnDialogOpen}
setEditingColumnId={setEditingColumnId}
setColumnName={setColumnName}
setColumnKey={setColumnKey}
/>
)}
{/* 항목 추가/수정 다이얼로그 - 모바일 (바텀시트) */}
{isMobile && (
<FieldDrawer
isOpen={isFieldDialogOpen}
onOpenChange={setIsFieldDialogOpen}
editingFieldId={editingFieldId}
setEditingFieldId={setEditingFieldId}
fieldInputMode={fieldInputMode}
setFieldInputMode={setFieldInputMode}
showMasterFieldList={showMasterFieldList}
setShowMasterFieldList={setShowMasterFieldList}
selectedMasterFieldId={selectedMasterFieldId}
setSelectedMasterFieldId={setSelectedMasterFieldId}
textboxColumns={textboxColumns}
setTextboxColumns={setTextboxColumns}
newFieldConditionEnabled={newFieldConditionEnabled}
setNewFieldConditionEnabled={setNewFieldConditionEnabled}
newFieldConditionTargetType={newFieldConditionTargetType}
setNewFieldConditionTargetType={setNewFieldConditionTargetType}
newFieldConditionFields={newFieldConditionFields}
setNewFieldConditionFields={setNewFieldConditionFields}
newFieldConditionSections={newFieldConditionSections}
setNewFieldConditionSections={setNewFieldConditionSections}
tempConditionValue={tempConditionValue}
setTempConditionValue={setTempConditionValue}
newFieldName={newFieldName}
setNewFieldName={setNewFieldName}
newFieldKey={newFieldKey}
setNewFieldKey={setNewFieldKey}
newFieldInputType={newFieldInputType}
setNewFieldInputType={setNewFieldInputType}
newFieldRequired={newFieldRequired}
setNewFieldRequired={setNewFieldRequired}
newFieldDescription={newFieldDescription}
setNewFieldDescription={setNewFieldDescription}
newFieldOptions={newFieldOptions}
setNewFieldOptions={setNewFieldOptions as (options: string | string[]) => void}
selectedSectionForField={selectedPage?.sections.find(s => s.id === selectedSectionForField) || null}
selectedPage={selectedPage || null}
itemMasterFields={itemMasterFields}
handleAddField={handleAddFieldWrapper}
setIsColumnDialogOpen={setIsColumnDialogOpen}
setEditingColumnId={setEditingColumnId}
setColumnName={setColumnName}
setColumnKey={setColumnKey}
/>
)}
{/* 텍스트박스 컬럼 추가/수정 다이얼로그 */}
<ColumnDialog
isColumnDialogOpen={isColumnDialogOpen}
setIsColumnDialogOpen={setIsColumnDialogOpen}
editingColumnId={editingColumnId}
setEditingColumnId={setEditingColumnId}
columnName={columnName}
setColumnName={setColumnName}
columnKey={columnKey}
setColumnKey={setColumnKey}
textboxColumns={textboxColumns}
setTextboxColumns={setTextboxColumns}
/>
<MasterFieldDialog
isMasterFieldDialogOpen={isMasterFieldDialogOpen}
setIsMasterFieldDialogOpen={setIsMasterFieldDialogOpen}
editingMasterFieldId={editingMasterFieldId}
setEditingMasterFieldId={setEditingMasterFieldId}
newMasterFieldName={newMasterFieldName}
setNewMasterFieldName={setNewMasterFieldName}
newMasterFieldKey={newMasterFieldKey}
setNewMasterFieldKey={setNewMasterFieldKey}
newMasterFieldInputType={newMasterFieldInputType}
setNewMasterFieldInputType={setNewMasterFieldInputType}
newMasterFieldRequired={newMasterFieldRequired}
setNewMasterFieldRequired={setNewMasterFieldRequired}
newMasterFieldCategory={newMasterFieldCategory}
setNewMasterFieldCategory={setNewMasterFieldCategory}
newMasterFieldDescription={newMasterFieldDescription}
setNewMasterFieldDescription={setNewMasterFieldDescription}
newMasterFieldOptions={newMasterFieldOptions}
setNewMasterFieldOptions={setNewMasterFieldOptions}
newMasterFieldAttributeType={newMasterFieldAttributeType}
setNewMasterFieldAttributeType={setNewMasterFieldAttributeType}
newMasterFieldMultiColumn={newMasterFieldMultiColumn}
setNewMasterFieldMultiColumn={setNewMasterFieldMultiColumn}
newMasterFieldColumnCount={newMasterFieldColumnCount}
setNewMasterFieldColumnCount={setNewMasterFieldColumnCount}
newMasterFieldColumnNames={newMasterFieldColumnNames}
setNewMasterFieldColumnNames={setNewMasterFieldColumnNames}
handleUpdateMasterField={handleUpdateMasterField}
handleAddMasterField={handleAddMasterField}
/>
<SectionTemplateDialog
isSectionTemplateDialogOpen={isSectionTemplateDialogOpen}
setIsSectionTemplateDialogOpen={setIsSectionTemplateDialogOpen}
editingSectionTemplateId={editingSectionTemplateId}
setEditingSectionTemplateId={setEditingSectionTemplateId}
newSectionTemplateTitle={newSectionTemplateTitle}
setNewSectionTemplateTitle={setNewSectionTemplateTitle}
newSectionTemplateDescription={newSectionTemplateDescription}
setNewSectionTemplateDescription={setNewSectionTemplateDescription}
newSectionTemplateCategory={newSectionTemplateCategory}
setNewSectionTemplateCategory={handleSetNewSectionTemplateCategory}
newSectionTemplateType={newSectionTemplateType}
setNewSectionTemplateType={setNewSectionTemplateType}
handleUpdateSectionTemplate={handleUpdateSectionTemplate}
handleAddSectionTemplate={handleAddSectionTemplate}
/>
<TemplateFieldDialog
isTemplateFieldDialogOpen={isTemplateFieldDialogOpen}
setIsTemplateFieldDialogOpen={setIsTemplateFieldDialogOpen}
editingTemplateFieldId={editingTemplateFieldId}
setEditingTemplateFieldId={setEditingTemplateFieldId}
templateFieldName={templateFieldName}
setTemplateFieldName={setTemplateFieldName}
templateFieldKey={templateFieldKey}
setTemplateFieldKey={setTemplateFieldKey}
templateFieldInputType={templateFieldInputType}
setTemplateFieldInputType={setTemplateFieldInputType}
templateFieldRequired={templateFieldRequired}
setTemplateFieldRequired={setTemplateFieldRequired}
templateFieldOptions={templateFieldOptions}
setTemplateFieldOptions={setTemplateFieldOptions}
templateFieldDescription={templateFieldDescription}
setTemplateFieldDescription={setTemplateFieldDescription}
templateFieldMultiColumn={templateFieldMultiColumn}
setTemplateFieldMultiColumn={setTemplateFieldMultiColumn}
templateFieldColumnCount={templateFieldColumnCount}
setTemplateFieldColumnCount={setTemplateFieldColumnCount}
templateFieldColumnNames={templateFieldColumnNames}
setTemplateFieldColumnNames={setTemplateFieldColumnNames}
handleAddTemplateField={handleAddTemplateField}
// 마스터 항목 관련 props
itemMasterFields={itemMasterFields}
templateFieldInputMode={templateFieldInputMode}
setTemplateFieldInputMode={setTemplateFieldInputMode}
showMasterFieldList={templateFieldShowMasterFieldList}
setShowMasterFieldList={setTemplateFieldShowMasterFieldList}
selectedMasterFieldId={templateFieldSelectedMasterFieldId}
setSelectedMasterFieldId={handleSetTemplateFieldSelectedMasterFieldId}
/>
<LoadTemplateDialog
isLoadTemplateDialogOpen={isLoadTemplateDialogOpen}
setIsLoadTemplateDialogOpen={setIsLoadTemplateDialogOpen}
sectionTemplates={sectionTemplates}
selectedTemplateId={selectedTemplateId}
setSelectedTemplateId={handleSetSelectedTemplateId}
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>
);
}