refactor: 품목기준관리 설정 페이지 훅/컴포넌트 분리

- Phase 1: 신규 훅 4개 생성
  - useInitialDataLoading.ts (초기 데이터 로딩)
  - useImportManagement.ts (섹션/필드 Import)
  - useReorderManagement.ts (드래그앤드롭 순서 변경)
  - useDeleteManagement.ts (삭제/언링크 핸들러)
- Phase 2: UI 컴포넌트 2개 생성
  - AttributeTabContent.tsx (속성 탭 콘텐츠)
  - ItemMasterDialogs.tsx (다이얼로그 통합)
- 메인 컴포넌트 1,799줄 → ~1,478줄 (약 320줄 감소)

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
byeongcheolryu
2025-12-24 14:35:29 +09:00
parent 5ecb0c3925
commit a823ae0777
13 changed files with 2532 additions and 429 deletions

View File

@@ -0,0 +1,472 @@
'use client';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Plus, Trash2, Settings, Package } from 'lucide-react';
import type { ItemMasterField } from '@/contexts/ItemMasterContext';
// 타입 정의
export interface UnitOption {
id: string;
value: string;
label: string;
inputType?: string;
required?: boolean;
placeholder?: string;
defaultValue?: string;
options?: string[];
columnValues?: Record<string, string>;
}
export interface AttributeSubTab {
id: string;
key: string;
label: string;
order: number;
}
export interface AttributeColumn {
id: string;
name: string;
key: string;
type: string;
required: boolean;
}
// 입력 타입 라벨 변환 헬퍼 함수
const getInputTypeLabel = (inputType: string | undefined): string => {
const labels: Record<string, string> = {
textbox: '텍스트박스',
number: '숫자',
dropdown: '드롭다운',
checkbox: '체크박스',
date: '날짜',
textarea: '텍스트영역',
};
return labels[inputType || ''] || '텍스트박스';
};
interface AttributeTabContentProps {
activeAttributeTab: string;
setActiveAttributeTab: (tab: string) => void;
attributeSubTabs: AttributeSubTab[];
unitOptions: UnitOption[];
materialOptions: UnitOption[];
surfaceTreatmentOptions: UnitOption[];
customAttributeOptions: Record<string, UnitOption[]>;
attributeColumns: Record<string, AttributeColumn[]>;
itemMasterFields: ItemMasterField[];
// 다이얼로그 핸들러
setIsManageAttributeTabsDialogOpen: (open: boolean) => void;
setIsOptionDialogOpen: (open: boolean) => void;
setEditingOptionType: (type: string) => void;
setNewOptionValue: (value: string) => void;
setNewOptionLabel: (value: string) => void;
setNewOptionColumnValues: (values: Record<string, string>) => void;
setIsColumnManageDialogOpen: (open: boolean) => void;
setManagingColumnType: (type: string) => void;
setNewColumnName: (name: string) => void;
setNewColumnKey: (key: string) => void;
setNewColumnType: (type: string) => void;
setNewColumnRequired: (required: boolean) => void;
handleDeleteOption: (type: string, id: string) => void;
}
export function AttributeTabContent({
activeAttributeTab,
setActiveAttributeTab,
attributeSubTabs,
unitOptions,
materialOptions,
surfaceTreatmentOptions,
customAttributeOptions,
attributeColumns,
itemMasterFields,
setIsManageAttributeTabsDialogOpen,
setIsOptionDialogOpen,
setEditingOptionType,
setNewOptionValue,
setNewOptionLabel,
setNewOptionColumnValues,
setIsColumnManageDialogOpen,
setManagingColumnType,
setNewColumnName,
setNewColumnKey,
setNewColumnType,
setNewColumnRequired,
handleDeleteOption,
}: AttributeTabContentProps) {
// 옵션 목록 렌더링 헬퍼
const renderOptionList = (
options: UnitOption[],
optionType: string,
title: string
) => {
const columns = attributeColumns[optionType] || [];
return (
<div className="space-y-4">
<div className="flex items-center justify-between mb-4">
<h3 className="font-medium">{title}</h3>
<div className="flex gap-2">
<Button variant="outline" size="sm" onClick={() => {
setManagingColumnType(optionType);
setNewColumnName('');
setNewColumnKey('');
setNewColumnType('text');
setNewColumnRequired(false);
setIsColumnManageDialogOpen(true);
}}>
<Settings className="w-4 h-4 mr-2" />
</Button>
<Button size="sm" onClick={() => {
setEditingOptionType(optionType === 'units' ? 'unit' : optionType === 'materials' ? 'material' : optionType === 'surface' ? 'surface' : optionType);
setIsOptionDialogOpen(true);
}}>
<Plus className="w-4 h-4 mr-2" />
</Button>
</div>
</div>
<div className="space-y-3">
{options.map((option) => {
const hasColumns = columns.length > 0 && option.columnValues;
return (
<div key={option.id} className="p-4 border rounded hover:bg-gray-50 transition-colors">
<div className="flex items-start justify-between gap-3">
<div className="flex-1 space-y-2">
<div className="flex items-center gap-2">
<span className="font-medium text-base">{option.label}</span>
{option.inputType && (
<Badge variant="outline" className="text-xs">{getInputTypeLabel(option.inputType)}</Badge>
)}
{option.required && (
<Badge variant="destructive" className="text-xs"></Badge>
)}
</div>
<div className="text-sm text-muted-foreground space-y-1">
<div className="flex gap-2">
<span className="font-medium min-w-16">(Value):</span>
<span>{option.value}</span>
</div>
{option.placeholder && (
<div className="flex gap-2">
<span className="font-medium min-w-16">:</span>
<span>{option.placeholder}</span>
</div>
)}
{option.defaultValue && (
<div className="flex gap-2">
<span className="font-medium min-w-16">:</span>
<span>{option.defaultValue}</span>
</div>
)}
{option.inputType === 'dropdown' && option.options && (
<div className="flex gap-2">
<span className="font-medium min-w-16">:</span>
<div className="flex flex-wrap gap-1">
{option.options.map((opt, idx) => (
<Badge key={idx} variant="secondary" className="text-xs">{opt}</Badge>
))}
</div>
</div>
)}
</div>
{hasColumns && (
<div className="mt-3 pt-3 border-t">
<p className="text-xs font-medium text-muted-foreground mb-2"> </p>
<div className="grid grid-cols-2 gap-2 text-sm">
{columns.map((column) => (
<div key={column.id} className="flex gap-2">
<span className="text-muted-foreground">{column.name}:</span>
<span>{option.columnValues?.[column.key] || '-'}</span>
</div>
))}
</div>
</div>
)}
</div>
<Button variant="ghost" size="sm" onClick={() => handleDeleteOption(optionType === 'units' ? 'unit' : optionType === 'materials' ? 'material' : optionType === 'surface' ? 'surface' : optionType, option.id)}>
<Trash2 className="w-4 h-4 text-red-500" />
</Button>
</div>
</div>
);
})}
</div>
</div>
);
};
// 마스터 필드 속성 렌더링
const renderMasterFieldProperties = (masterField: ItemMasterField) => {
const propertiesArray = masterField?.properties
? Object.entries(masterField.properties).map(([key, value]) => ({ key, ...value as object }))
: [];
if (propertiesArray.length === 0) return null;
return (
<div className="space-y-4">
<div className="flex items-center justify-between mb-4">
<div>
<h3 className="font-medium">{masterField.field_name} </h3>
<p className="text-sm text-muted-foreground mt-1">
&quot;{masterField.field_name}&quot;
</p>
</div>
</div>
<div className="space-y-3">
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
{propertiesArray.map((property: any) => {
const inputTypeLabel = getInputTypeLabel(property.type);
return (
<div key={property.id} className="p-4 border rounded hover:bg-gray-50 transition-colors">
<div className="flex items-start justify-between gap-3">
<div className="flex-1 space-y-2">
<div className="flex items-center gap-2">
<span className="font-medium text-base">{property.label}</span>
<Badge variant="outline" className="text-xs">{inputTypeLabel}</Badge>
{property.required && (
<Badge variant="destructive" className="text-xs"></Badge>
)}
</div>
<div className="text-sm text-muted-foreground space-y-1">
<div className="flex gap-2">
<span className="font-medium min-w-24">(Key):</span>
<code className="bg-gray-100 px-2 py-0.5 rounded text-xs">{property.key}</code>
</div>
{property.placeholder && (
<div className="flex gap-2">
<span className="font-medium min-w-24">:</span>
<span>{property.placeholder}</span>
</div>
)}
{property.defaultValue && (
<div className="flex gap-2">
<span className="font-medium min-w-24">:</span>
<span>{property.defaultValue}</span>
</div>
)}
{property.type === 'dropdown' && property.options && (
<div className="flex gap-2">
<span className="font-medium min-w-24">:</span>
<div className="flex flex-wrap gap-1">
{property.options.map((opt: string, idx: number) => (
<Badge key={idx} variant="secondary" className="text-xs">{opt}</Badge>
))}
</div>
</div>
)}
</div>
</div>
</div>
</div>
);
})}
</div>
<div className="mt-4 p-4 bg-blue-50 border border-blue-200 rounded-lg">
<div className="flex items-start gap-2">
<Package className="w-5 h-5 text-blue-600 mt-0.5" />
<div className="flex-1">
<p className="text-sm font-medium text-blue-900">
</p>
<p className="text-xs text-blue-700 mt-1">
<strong> </strong> &quot;{masterField.field_name}&quot; // .
</p>
</div>
</div>
</div>
</div>
);
};
// 사용자 정의 속성 렌더링
const renderCustomAttributeTab = () => {
const currentTabKey = activeAttributeTab;
const currentOptions = customAttributeOptions[currentTabKey] || [];
const columns = attributeColumns[currentTabKey] || [];
return (
<div className="space-y-4">
<div className="flex items-center justify-between mb-4">
<h3 className="font-medium">
{attributeSubTabs.find(t => t.key === activeAttributeTab)?.label || '사용자 정의'}
</h3>
<div className="flex gap-2">
<Button variant="outline" size="sm" onClick={() => {
setManagingColumnType(currentTabKey);
setNewColumnName('');
setNewColumnKey('');
setNewColumnType('text');
setNewColumnRequired(false);
setIsColumnManageDialogOpen(true);
}}>
<Settings className="w-4 h-4 mr-2" />
</Button>
<Button size="sm" onClick={() => {
setEditingOptionType(activeAttributeTab);
setNewOptionValue('');
setNewOptionLabel('');
setNewOptionColumnValues({});
setIsOptionDialogOpen(true);
}}>
<Plus className="w-4 h-4 mr-2" />
</Button>
</div>
</div>
{currentOptions.length > 0 ? (
<div className="space-y-3">
{currentOptions.map((option) => {
const hasColumns = columns.length > 0 && option.columnValues;
const inputTypeLabel = getInputTypeLabel(option.inputType);
return (
<div key={option.id} className="p-4 border rounded hover:bg-gray-50 transition-colors">
<div className="flex items-start justify-between gap-3">
<div className="flex-1 space-y-2">
<div className="flex items-center gap-2">
<span className="font-medium text-base">{option.label}</span>
<Badge variant="outline" className="text-xs">{inputTypeLabel}</Badge>
{option.required && (
<Badge variant="destructive" className="text-xs"></Badge>
)}
</div>
<div className="text-sm text-muted-foreground space-y-1">
<div className="flex gap-2">
<span className="font-medium min-w-16">(Value):</span>
<span>{option.value}</span>
</div>
{option.placeholder && (
<div className="flex gap-2">
<span className="font-medium min-w-16">:</span>
<span>{option.placeholder}</span>
</div>
)}
{option.defaultValue && (
<div className="flex gap-2">
<span className="font-medium min-w-16">:</span>
<span>{option.defaultValue}</span>
</div>
)}
{option.inputType === 'dropdown' && option.options && (
<div className="flex gap-2">
<span className="font-medium min-w-16">:</span>
<div className="flex flex-wrap gap-1">
{option.options.map((opt, idx) => (
<Badge key={idx} variant="secondary" className="text-xs">{opt}</Badge>
))}
</div>
</div>
)}
</div>
{hasColumns && (
<div className="mt-3 pt-3 border-t">
<p className="text-xs font-medium text-muted-foreground mb-2"> </p>
<div className="grid grid-cols-2 gap-2 text-sm">
{columns.map((column) => (
<div key={column.id} className="flex gap-2">
<span className="text-muted-foreground">{column.name}:</span>
<span>{option.columnValues?.[column.key] || '-'}</span>
</div>
))}
</div>
</div>
)}
</div>
<Button variant="ghost" size="sm" onClick={() => handleDeleteOption(currentTabKey, option.id)}>
<Trash2 className="w-4 h-4 text-red-500" />
</Button>
</div>
</div>
);
})}
</div>
) : (
<div className="border-2 border-dashed rounded-lg p-8 text-center text-gray-500">
<Settings className="w-12 h-12 mx-auto mb-4 text-gray-400" />
<p className="mb-2"> </p>
<p className="text-sm"> &quot;&quot; </p>
</div>
)}
</div>
);
};
return (
<Card>
<CardHeader>
<CardTitle> </CardTitle>
<CardDescription>, , </CardDescription>
</CardHeader>
<CardContent>
{/* 속성 하위 탭 (칩 형태) */}
<div className="flex items-center gap-2 mb-6 border-b pb-2">
<div className="flex gap-2 flex-1 flex-wrap">
{attributeSubTabs.sort((a, b) => a.order - b.order).map(tab => (
<Button
key={tab.id}
variant={activeAttributeTab === tab.key ? 'default' : 'outline'}
size="sm"
onClick={() => setActiveAttributeTab(tab.key)}
className="rounded-full"
>
{tab.label}
</Button>
))}
</div>
<Button
variant="ghost"
size="sm"
onClick={() => setIsManageAttributeTabsDialogOpen(true)}
className="shrink-0"
>
<Settings className="w-4 h-4 mr-1" />
</Button>
</div>
{/* 단위 관리 */}
{activeAttributeTab === 'units' && renderOptionList(unitOptions, 'units', '단위 목록')}
{/* 재질 관리 */}
{activeAttributeTab === 'materials' && renderOptionList(materialOptions, 'materials', '재질 목록')}
{/* 표면처리 관리 */}
{activeAttributeTab === 'surface' && renderOptionList(surfaceTreatmentOptions, 'surface', '표면처리 목록')}
{/* 사용자 정의 속성 탭 및 마스터 항목 탭 */}
{!['units', 'materials', 'surface'].includes(activeAttributeTab) && (() => {
const currentTabKey = activeAttributeTab;
// 마스터 항목인지 확인
const masterField = itemMasterFields.find(f => f.id.toString() === currentTabKey);
if (masterField) {
const propertiesArray = masterField?.properties
? Object.entries(masterField.properties).map(([key, value]) => ({ key, ...value as object }))
: [];
if (propertiesArray.length > 0) {
return renderMasterFieldProperties(masterField);
}
}
// 사용자 정의 속성 탭
return renderCustomAttributeTab();
})()}
</CardContent>
</Card>
);
}

View File

@@ -0,0 +1,943 @@
'use client';
import type { ItemPage, SectionTemplate, ItemMasterField, ItemSection, BOMItem } from '@/contexts/ItemMasterContext';
import { FieldDialog } from '../dialogs/FieldDialog';
import { FieldDrawer } from '../dialogs/FieldDrawer';
import { TabManagementDialogs } from '../dialogs/TabManagementDialogs';
import { OptionDialog } from '../dialogs/OptionDialog';
import { ColumnManageDialog } from '../dialogs/ColumnManageDialog';
import { PathEditDialog } from '../dialogs/PathEditDialog';
import { PageDialog } from '../dialogs/PageDialog';
import { SectionDialog } from '../dialogs/SectionDialog';
import { MasterFieldDialog } from '../dialogs/MasterFieldDialog';
import { TemplateFieldDialog } from '../dialogs/TemplateFieldDialog';
import { LoadTemplateDialog } from '../dialogs/LoadTemplateDialog';
import { ColumnDialog } from '../dialogs/ColumnDialog';
import { SectionTemplateDialog } from '../dialogs/SectionTemplateDialog';
import { ImportSectionDialog } from '../dialogs/ImportSectionDialog';
import { ImportFieldDialog } from '../dialogs/ImportFieldDialog';
import type { CustomTab, AttributeSubTab } from '../hooks/useTabManagement';
import type { UnitOption } from '../hooks/useAttributeManagement';
interface TextboxColumn {
id: string;
name: string;
key: string;
}
interface ConditionField {
fieldId: string;
fieldName: string;
operator: string;
value: string;
logicOperator?: 'AND' | 'OR';
}
interface ConditionSection {
sectionId: string;
sectionTitle: string;
operator: string;
value: string;
logicOperator?: 'AND' | 'OR';
}
interface AttributeColumn {
id: string;
name: string;
key: string;
type: string;
required: boolean;
}
export interface ItemMasterDialogsProps {
isMobile: boolean;
selectedPage: ItemPage | null;
// Tab Management
isManageTabsDialogOpen: boolean;
setIsManageTabsDialogOpen: (open: boolean) => void;
customTabs: CustomTab[];
moveTabUp: (tabId: string) => void;
moveTabDown: (tabId: string) => void;
handleEditTabFromManage: (tabId: string) => void;
handleDeleteTab: (tabId: string) => void;
getTabIcon: (iconName: string) => React.ComponentType<{ className?: string }>;
setIsAddTabDialogOpen: (open: boolean) => void;
isDeleteTabDialogOpen: boolean;
setIsDeleteTabDialogOpen: (open: boolean) => void;
deletingTabId: string | null;
setDeletingTabId: (id: string | null) => void;
confirmDeleteTab: () => void;
isAddTabDialogOpen: boolean;
editingTabId: string | null;
setEditingTabId: (id: string | null) => void;
newTabLabel: string;
setNewTabLabel: (label: string) => void;
handleUpdateTab: () => void;
handleAddTab: () => void;
isManageAttributeTabsDialogOpen: boolean;
setIsManageAttributeTabsDialogOpen: (open: boolean) => void;
attributeSubTabs: AttributeSubTab[];
moveAttributeTabUp: (tabId: string) => void;
moveAttributeTabDown: (tabId: string) => void;
handleDeleteAttributeTab: (tabId: string) => void;
isDeleteAttributeTabDialogOpen: boolean;
setIsDeleteAttributeTabDialogOpen: (open: boolean) => void;
deletingAttributeTabId: string | null;
setDeletingAttributeTabId: (id: string | null) => void;
confirmDeleteAttributeTab: () => void;
isAddAttributeTabDialogOpen: boolean;
setIsAddAttributeTabDialogOpen: (open: boolean) => void;
editingAttributeTabId: string | null;
setEditingAttributeTabId: (id: string | null) => void;
newAttributeTabLabel: string;
setNewAttributeTabLabel: (label: string) => void;
handleUpdateAttributeTab: () => void;
handleAddAttributeTab: () => void;
// Option Dialog
isOptionDialogOpen: boolean;
setIsOptionDialogOpen: (open: boolean) => void;
newOptionValue: string;
setNewOptionValue: (value: string) => void;
newOptionLabel: string;
setNewOptionLabel: (value: string) => void;
newOptionColumnValues: Record<string, string>;
setNewOptionColumnValues: (values: Record<string, string>) => void;
newOptionInputType: string;
setNewOptionInputType: (type: string) => void;
newOptionRequired: boolean;
setNewOptionRequired: (required: boolean) => void;
newOptionOptions: string[];
setNewOptionOptions: (options: string[]) => void;
newOptionPlaceholder: string;
setNewOptionPlaceholder: (placeholder: string) => void;
newOptionDefaultValue: string;
setNewOptionDefaultValue: (value: string) => void;
editingOptionType: string;
attributeColumns: Record<string, AttributeColumn[]>;
handleAddOption: () => void;
// Column Manage Dialog
isColumnManageDialogOpen: boolean;
setIsColumnManageDialogOpen: (open: boolean) => void;
managingColumnType: string;
setAttributeColumns: React.Dispatch<React.SetStateAction<Record<string, AttributeColumn[]>>>;
newColumnName: string;
setNewColumnName: (name: string) => void;
newColumnKey: string;
setNewColumnKey: (key: string) => void;
newColumnType: string;
setNewColumnType: (type: string) => void;
newColumnRequired: boolean;
setNewColumnRequired: (required: boolean) => void;
// Path Edit Dialog
editingPathPageId: number | null;
setEditingPathPageId: (id: number | null) => void;
editingAbsolutePath: string;
setEditingAbsolutePath: (path: string) => void;
updateItemPage: (id: number, updates: Partial<ItemPage>) => void;
// Page Dialog
isPageDialogOpen: boolean;
setIsPageDialogOpen: (open: boolean) => void;
newPageName: string;
setNewPageName: (name: string) => void;
newPageItemType: 'FG' | 'PT' | 'SM' | 'RM' | 'CS';
setNewPageItemType: React.Dispatch<React.SetStateAction<'FG' | 'PT' | 'SM' | 'RM' | 'CS'>>;
handleAddPage: () => void;
// Section Dialog
isSectionDialogOpen: boolean;
setIsSectionDialogOpen: (open: boolean) => void;
newSectionType: 'fields' | 'bom';
setNewSectionType: (type: 'fields' | 'bom') => void;
newSectionTitle: string;
setNewSectionTitle: (title: string) => void;
newSectionDescription: string;
setNewSectionDescription: (description: string) => void;
handleAddSection: () => void;
sectionInputMode: 'new' | 'existing';
setSectionInputMode: (mode: 'new' | 'existing') => void;
sectionsAsTemplates: SectionTemplate[];
selectedSectionTemplateId: number | null;
setSelectedSectionTemplateId: (id: number | null) => void;
handleLinkTemplate: () => void;
// Field Dialog
isFieldDialogOpen: boolean;
setIsFieldDialogOpen: (open: boolean) => void;
selectedSectionForField: number | null;
editingFieldId: number | null;
setEditingFieldId: (id: number | null) => void;
fieldInputMode: 'new' | 'existing';
setFieldInputMode: (mode: 'new' | 'existing') => void;
showMasterFieldList: boolean;
setShowMasterFieldList: (show: boolean) => void;
selectedMasterFieldId: number | null;
setSelectedMasterFieldId: (id: number | null) => void;
textboxColumns: TextboxColumn[];
setTextboxColumns: React.Dispatch<React.SetStateAction<TextboxColumn[]>>;
newFieldConditionEnabled: boolean;
setNewFieldConditionEnabled: (enabled: boolean) => void;
newFieldConditionTargetType: 'field' | 'section';
setNewFieldConditionTargetType: (type: 'field' | 'section') => void;
newFieldConditionFields: ConditionField[];
setNewFieldConditionFields: React.Dispatch<React.SetStateAction<ConditionField[]>>;
newFieldConditionSections: ConditionSection[];
setNewFieldConditionSections: React.Dispatch<React.SetStateAction<ConditionSection[]>>;
tempConditionValue: string;
setTempConditionValue: (value: string) => void;
newFieldName: string;
setNewFieldName: (name: string) => void;
newFieldKey: string;
setNewFieldKey: (key: string) => void;
newFieldInputType: string;
setNewFieldInputType: (type: string) => void;
newFieldRequired: boolean;
setNewFieldRequired: (required: boolean) => void;
newFieldDescription: string;
setNewFieldDescription: (description: string) => void;
newFieldOptions: string[];
setNewFieldOptions: React.Dispatch<React.SetStateAction<string[]>>;
itemMasterFields: ItemMasterField[];
handleAddField: () => void;
isColumnDialogOpen: boolean;
setIsColumnDialogOpen: (open: boolean) => void;
editingColumnId: string | null;
setEditingColumnId: (id: string | null) => void;
columnName: string;
setColumnName: (name: string) => void;
columnKey: string;
setColumnKey: (key: string) => void;
// Master Field Dialog
isMasterFieldDialogOpen: boolean;
setIsMasterFieldDialogOpen: (open: boolean) => void;
editingMasterFieldId: number | null;
setEditingMasterFieldId: (id: number | null) => void;
newMasterFieldName: string;
setNewMasterFieldName: (name: string) => void;
newMasterFieldKey: string;
setNewMasterFieldKey: (key: string) => void;
newMasterFieldInputType: string;
setNewMasterFieldInputType: (type: string) => void;
newMasterFieldRequired: boolean;
setNewMasterFieldRequired: (required: boolean) => void;
newMasterFieldCategory: string;
setNewMasterFieldCategory: (category: string) => void;
newMasterFieldDescription: string;
setNewMasterFieldDescription: (description: string) => void;
newMasterFieldOptions: string[];
setNewMasterFieldOptions: React.Dispatch<React.SetStateAction<string[]>>;
newMasterFieldAttributeType: string;
setNewMasterFieldAttributeType: (type: string) => void;
newMasterFieldMultiColumn: boolean;
setNewMasterFieldMultiColumn: (multiColumn: boolean) => void;
newMasterFieldColumnCount: number;
setNewMasterFieldColumnCount: (count: number) => void;
newMasterFieldColumnNames: string[];
setNewMasterFieldColumnNames: React.Dispatch<React.SetStateAction<string[]>>;
handleUpdateMasterField: () => void;
handleAddMasterField: () => void;
// Section Template Dialog
isSectionTemplateDialogOpen: boolean;
setIsSectionTemplateDialogOpen: (open: boolean) => void;
editingSectionTemplateId: number | null;
setEditingSectionTemplateId: (id: number | null) => void;
newSectionTemplateTitle: string;
setNewSectionTemplateTitle: (title: string) => void;
newSectionTemplateDescription: string;
setNewSectionTemplateDescription: (description: string) => void;
newSectionTemplateCategory: string;
setNewSectionTemplateCategory: (category: string) => void;
newSectionTemplateType: 'fields' | 'bom';
setNewSectionTemplateType: (type: 'fields' | 'bom') => void;
handleUpdateSectionTemplate: () => void;
handleAddSectionTemplate: () => void;
// Template Field Dialog
isTemplateFieldDialogOpen: boolean;
setIsTemplateFieldDialogOpen: (open: boolean) => void;
editingTemplateFieldId: string | null;
setEditingTemplateFieldId: (id: string | null) => void;
templateFieldName: string;
setTemplateFieldName: (name: string) => void;
templateFieldKey: string;
setTemplateFieldKey: (key: string) => void;
templateFieldInputType: string;
setTemplateFieldInputType: (type: string) => void;
templateFieldRequired: boolean;
setTemplateFieldRequired: (required: boolean) => void;
templateFieldOptions: string[];
setTemplateFieldOptions: React.Dispatch<React.SetStateAction<string[]>>;
templateFieldDescription: string;
setTemplateFieldDescription: (description: string) => void;
templateFieldMultiColumn: boolean;
setTemplateFieldMultiColumn: (multiColumn: boolean) => void;
templateFieldColumnCount: number;
setTemplateFieldColumnCount: (count: number) => void;
templateFieldColumnNames: string[];
setTemplateFieldColumnNames: React.Dispatch<React.SetStateAction<string[]>>;
handleAddTemplateField: () => void;
templateFieldInputMode: 'new' | 'existing';
setTemplateFieldInputMode: (mode: 'new' | 'existing') => void;
templateFieldShowMasterFieldList: boolean;
setTemplateFieldShowMasterFieldList: (show: boolean) => void;
templateFieldSelectedMasterFieldId: number | null;
setTemplateFieldSelectedMasterFieldId: (id: number | null) => void;
// Load Template Dialog
isLoadTemplateDialogOpen: boolean;
setIsLoadTemplateDialogOpen: (open: boolean) => void;
sectionTemplates: SectionTemplate[];
selectedTemplateId: number | null;
setSelectedTemplateId: (id: number | null) => void;
handleLoadTemplate: () => void;
// Import Section Dialog
isImportSectionDialogOpen: boolean;
setIsImportSectionDialogOpen: (open: boolean) => void;
independentSections: ItemSection[];
selectedImportSectionId: number | null;
setSelectedImportSectionId: (id: number | null) => void;
handleImportSection: () => Promise<void>;
refreshIndependentSections: () => void;
getSectionUsage: (sectionId: number) => Promise<{ pages: { id: number; name: string }[] }>;
// Import Field Dialog
isImportFieldDialogOpen: boolean;
setIsImportFieldDialogOpen: (open: boolean) => void;
selectedImportFieldId: number | null;
setSelectedImportFieldId: (id: number | null) => void;
handleImportField: () => Promise<void>;
refreshIndependentFields: () => void;
getFieldUsage: (fieldId: number) => Promise<{ sections: { id: number; title: string }[] }>;
importFieldTargetSectionId: number | null;
}
export function ItemMasterDialogs({
isMobile,
selectedPage,
// Tab Management
isManageTabsDialogOpen,
setIsManageTabsDialogOpen,
customTabs,
moveTabUp,
moveTabDown,
handleEditTabFromManage,
handleDeleteTab,
getTabIcon,
setIsAddTabDialogOpen,
isDeleteTabDialogOpen,
setIsDeleteTabDialogOpen,
deletingTabId,
setDeletingTabId,
confirmDeleteTab,
isAddTabDialogOpen,
editingTabId,
setEditingTabId,
newTabLabel,
setNewTabLabel,
handleUpdateTab,
handleAddTab,
isManageAttributeTabsDialogOpen,
setIsManageAttributeTabsDialogOpen,
attributeSubTabs,
moveAttributeTabUp,
moveAttributeTabDown,
handleDeleteAttributeTab,
isDeleteAttributeTabDialogOpen,
setIsDeleteAttributeTabDialogOpen,
deletingAttributeTabId,
setDeletingAttributeTabId,
confirmDeleteAttributeTab,
isAddAttributeTabDialogOpen,
setIsAddAttributeTabDialogOpen,
editingAttributeTabId,
setEditingAttributeTabId,
newAttributeTabLabel,
setNewAttributeTabLabel,
handleUpdateAttributeTab,
handleAddAttributeTab,
// Option Dialog
isOptionDialogOpen,
setIsOptionDialogOpen,
newOptionValue,
setNewOptionValue,
newOptionLabel,
setNewOptionLabel,
newOptionColumnValues,
setNewOptionColumnValues,
newOptionInputType,
setNewOptionInputType,
newOptionRequired,
setNewOptionRequired,
newOptionOptions,
setNewOptionOptions,
newOptionPlaceholder,
setNewOptionPlaceholder,
newOptionDefaultValue,
setNewOptionDefaultValue,
editingOptionType,
attributeColumns,
handleAddOption,
// Column Manage Dialog
isColumnManageDialogOpen,
setIsColumnManageDialogOpen,
managingColumnType,
setAttributeColumns,
newColumnName,
setNewColumnName,
newColumnKey,
setNewColumnKey,
newColumnType,
setNewColumnType,
newColumnRequired,
setNewColumnRequired,
// Path Edit Dialog
editingPathPageId,
setEditingPathPageId,
editingAbsolutePath,
setEditingAbsolutePath,
updateItemPage,
// Page Dialog
isPageDialogOpen,
setIsPageDialogOpen,
newPageName,
setNewPageName,
newPageItemType,
setNewPageItemType,
handleAddPage,
// Section Dialog
isSectionDialogOpen,
setIsSectionDialogOpen,
newSectionType,
setNewSectionType,
newSectionTitle,
setNewSectionTitle,
newSectionDescription,
setNewSectionDescription,
handleAddSection,
sectionInputMode,
setSectionInputMode,
sectionsAsTemplates,
selectedSectionTemplateId,
setSelectedSectionTemplateId,
handleLinkTemplate,
// Field Dialog
isFieldDialogOpen,
setIsFieldDialogOpen,
selectedSectionForField,
editingFieldId,
setEditingFieldId,
fieldInputMode,
setFieldInputMode,
showMasterFieldList,
setShowMasterFieldList,
selectedMasterFieldId,
setSelectedMasterFieldId,
textboxColumns,
setTextboxColumns,
newFieldConditionEnabled,
setNewFieldConditionEnabled,
newFieldConditionTargetType,
setNewFieldConditionTargetType,
newFieldConditionFields,
setNewFieldConditionFields,
newFieldConditionSections,
setNewFieldConditionSections,
tempConditionValue,
setTempConditionValue,
newFieldName,
setNewFieldName,
newFieldKey,
setNewFieldKey,
newFieldInputType,
setNewFieldInputType,
newFieldRequired,
setNewFieldRequired,
newFieldDescription,
setNewFieldDescription,
newFieldOptions,
setNewFieldOptions,
itemMasterFields,
handleAddField,
isColumnDialogOpen,
setIsColumnDialogOpen,
editingColumnId,
setEditingColumnId,
columnName,
setColumnName,
columnKey,
setColumnKey,
// Master Field Dialog
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,
handleUpdateMasterField,
handleAddMasterField,
// Section Template Dialog
isSectionTemplateDialogOpen,
setIsSectionTemplateDialogOpen,
editingSectionTemplateId,
setEditingSectionTemplateId,
newSectionTemplateTitle,
setNewSectionTemplateTitle,
newSectionTemplateDescription,
setNewSectionTemplateDescription,
newSectionTemplateCategory,
setNewSectionTemplateCategory,
newSectionTemplateType,
setNewSectionTemplateType,
handleUpdateSectionTemplate,
handleAddSectionTemplate,
// Template Field Dialog
isTemplateFieldDialogOpen,
setIsTemplateFieldDialogOpen,
editingTemplateFieldId,
setEditingTemplateFieldId,
templateFieldName,
setTemplateFieldName,
templateFieldKey,
setTemplateFieldKey,
templateFieldInputType,
setTemplateFieldInputType,
templateFieldRequired,
setTemplateFieldRequired,
templateFieldOptions,
setTemplateFieldOptions,
templateFieldDescription,
setTemplateFieldDescription,
templateFieldMultiColumn,
setTemplateFieldMultiColumn,
templateFieldColumnCount,
setTemplateFieldColumnCount,
templateFieldColumnNames,
setTemplateFieldColumnNames,
handleAddTemplateField,
templateFieldInputMode,
setTemplateFieldInputMode,
templateFieldShowMasterFieldList,
setTemplateFieldShowMasterFieldList,
templateFieldSelectedMasterFieldId,
setTemplateFieldSelectedMasterFieldId,
// Load Template Dialog
isLoadTemplateDialogOpen,
setIsLoadTemplateDialogOpen,
sectionTemplates,
selectedTemplateId,
setSelectedTemplateId,
handleLoadTemplate,
// Import Section Dialog
isImportSectionDialogOpen,
setIsImportSectionDialogOpen,
independentSections,
selectedImportSectionId,
setSelectedImportSectionId,
handleImportSection,
refreshIndependentSections,
getSectionUsage,
// Import Field Dialog
isImportFieldDialogOpen,
setIsImportFieldDialogOpen,
selectedImportFieldId,
setSelectedImportFieldId,
handleImportField,
refreshIndependentFields,
getFieldUsage,
importFieldTargetSectionId,
}: ItemMasterDialogsProps) {
return (
<>
<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={setNewPageItemType}
handleAddPage={handleAddPage}
/>
<SectionDialog
isSectionDialogOpen={isSectionDialogOpen}
setIsSectionDialogOpen={setIsSectionDialogOpen}
newSectionType={newSectionType}
setNewSectionType={setNewSectionType}
newSectionTitle={newSectionTitle}
setNewSectionTitle={setNewSectionTitle}
newSectionDescription={newSectionDescription}
setNewSectionDescription={setNewSectionDescription}
handleAddSection={handleAddSection}
sectionInputMode={sectionInputMode}
setSectionInputMode={setSectionInputMode}
sectionTemplates={sectionsAsTemplates}
selectedTemplateId={selectedSectionTemplateId}
setSelectedTemplateId={setSelectedSectionTemplateId}
handleLinkTemplate={handleLinkTemplate}
/>
{/* 항목 추가/수정 다이얼로그 - 데스크톱 */}
{!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}
selectedSectionForField={selectedPage?.sections.find(s => s.id === selectedSectionForField) || null}
selectedPage={selectedPage || null}
itemMasterFields={itemMasterFields}
handleAddField={handleAddField}
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}
selectedSectionForField={selectedPage?.sections.find(s => s.id === selectedSectionForField) || null}
selectedPage={selectedPage || null}
itemMasterFields={itemMasterFields}
handleAddField={handleAddField}
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={setNewSectionTemplateCategory}
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}
itemMasterFields={itemMasterFields}
templateFieldInputMode={templateFieldInputMode}
setTemplateFieldInputMode={setTemplateFieldInputMode}
showMasterFieldList={templateFieldShowMasterFieldList}
setShowMasterFieldList={setTemplateFieldShowMasterFieldList}
selectedMasterFieldId={templateFieldSelectedMasterFieldId}
setSelectedMasterFieldId={setTemplateFieldSelectedMasterFieldId}
/>
<LoadTemplateDialog
isLoadTemplateDialogOpen={isLoadTemplateDialogOpen}
setIsLoadTemplateDialogOpen={setIsLoadTemplateDialogOpen}
sectionTemplates={sectionTemplates}
selectedTemplateId={selectedTemplateId}
setSelectedTemplateId={setSelectedTemplateId}
handleLoadTemplate={handleLoadTemplate}
/>
{/* 섹션 불러오기 다이얼로그 */}
<ImportSectionDialog
isOpen={isImportSectionDialogOpen}
setIsOpen={setIsImportSectionDialogOpen}
independentSections={independentSections}
selectedSectionId={selectedImportSectionId}
setSelectedSectionId={setSelectedImportSectionId}
onImport={handleImportSection}
onRefresh={refreshIndependentSections}
onGetUsage={getSectionUsage}
/>
{/* 필드 불러오기 다이얼로그 */}
<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
}
/>
</>
);
}

View File

@@ -1,2 +1,7 @@
export { DraggableSection } from './DraggableSection';
export { DraggableField } from './DraggableField';
export { DraggableField } from './DraggableField';
// 2025-12-24: Phase 2 UI 컴포넌트 분리
export { AttributeTabContent } from './AttributeTabContent';
export { ItemMasterDialogs } from './ItemMasterDialogs';
export type { ItemMasterDialogsProps } from './ItemMasterDialogs';

View File

@@ -17,4 +17,17 @@ export { useAttributeManagement } from './useAttributeManagement';
export type { UseAttributeManagementReturn } from './useAttributeManagement';
export { useTabManagement } from './useTabManagement';
export type { UseTabManagementReturn, CustomTab, AttributeSubTab } from './useTabManagement';
export type { UseTabManagementReturn, CustomTab, AttributeSubTab } from './useTabManagement';
// 2025-12-24: 신규 훅 추가
export { useInitialDataLoading } from './useInitialDataLoading';
export type { UseInitialDataLoadingReturn } from './useInitialDataLoading';
export { useImportManagement } from './useImportManagement';
export type { UseImportManagementReturn } from './useImportManagement';
export { useReorderManagement } from './useReorderManagement';
export type { UseReorderManagementReturn } from './useReorderManagement';
export { useDeleteManagement } from './useDeleteManagement';
export type { UseDeleteManagementReturn } from './useDeleteManagement';

View File

@@ -0,0 +1,119 @@
'use client';
import { useCallback } from 'react';
import { useItemMaster } from '@/contexts/ItemMasterContext';
import { toast } from 'sonner';
import type { ItemPage, BOMItem } from '@/contexts/ItemMasterContext';
import type { CustomTab, AttributeSubTab } from './useTabManagement';
import type { UnitOption, MaterialOption, SurfaceTreatmentOption } from './useAttributeManagement';
export interface UseDeleteManagementReturn {
handleDeletePage: (pageId: number) => void;
handleDeleteSection: (pageId: number, sectionId: number) => void;
handleUnlinkField: (pageId: string, sectionId: string, fieldId: string) => Promise<void>;
handleResetAllData: (
setUnitOptions: React.Dispatch<React.SetStateAction<UnitOption[]>>,
setMaterialOptions: React.Dispatch<React.SetStateAction<MaterialOption[]>>,
setSurfaceTreatmentOptions: React.Dispatch<React.SetStateAction<SurfaceTreatmentOption[]>>,
setCustomAttributeOptions: React.Dispatch<React.SetStateAction<Record<string, UnitOption[]>>>,
setAttributeColumns: React.Dispatch<React.SetStateAction<Record<string, { id: string; name: string; key: string; type: string; required: boolean }[]>>>,
setBomItems: React.Dispatch<React.SetStateAction<BOMItem[]>>,
setCustomTabs: React.Dispatch<React.SetStateAction<CustomTab[]>>,
setAttributeSubTabs: React.Dispatch<React.SetStateAction<AttributeSubTab[]>>,
) => void;
}
interface UseDeleteManagementProps {
itemPages: ItemPage[];
}
export function useDeleteManagement({ itemPages }: UseDeleteManagementProps): UseDeleteManagementReturn {
const {
deleteItemPage,
deleteSection,
unlinkFieldFromSection,
resetAllData,
} = useItemMaster();
// 페이지 삭제 핸들러
const handleDeletePage = useCallback((pageId: number) => {
const pageToDelete = itemPages.find(p => p.id === pageId);
const sectionIds = pageToDelete?.sections.map(s => s.id) || [];
const fieldIds = pageToDelete?.sections.flatMap(s => s.fields?.map(f => f.id) || []) || [];
deleteItemPage(pageId);
console.log('페이지 삭제 완료:', { pageId, removedSections: sectionIds.length, removedFields: fieldIds.length });
}, [itemPages, deleteItemPage]);
// 섹션 삭제 핸들러
const handleDeleteSection = useCallback((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) || [];
deleteSection(Number(sectionId));
console.log('섹션 삭제 완료:', { sectionId, removedFields: fieldIds.length });
}, [itemPages, deleteSection]);
// 필드 연결 해제 핸들러
const handleUnlinkField = useCallback(async (_pageId: string, sectionId: string, fieldId: string) => {
try {
await unlinkFieldFromSection(Number(sectionId), Number(fieldId));
console.log('필드 연결 해제 완료:', fieldId);
} catch (error) {
console.error('필드 연결 해제 실패:', error);
toast.error('필드 연결 해제에 실패했습니다');
}
}, [unlinkFieldFromSection]);
// 전체 데이터 초기화 핸들러
const handleResetAllData = useCallback((
setUnitOptions: React.Dispatch<React.SetStateAction<UnitOption[]>>,
setMaterialOptions: React.Dispatch<React.SetStateAction<MaterialOption[]>>,
setSurfaceTreatmentOptions: React.Dispatch<React.SetStateAction<SurfaceTreatmentOption[]>>,
setCustomAttributeOptions: React.Dispatch<React.SetStateAction<Record<string, UnitOption[]>>>,
setAttributeColumns: React.Dispatch<React.SetStateAction<Record<string, { id: string; name: string; key: string; type: string; required: boolean }[]>>>,
setBomItems: React.Dispatch<React.SetStateAction<BOMItem[]>>,
setCustomTabs: React.Dispatch<React.SetStateAction<CustomTab[]>>,
setAttributeSubTabs: React.Dispatch<React.SetStateAction<AttributeSubTab[]>>,
) => {
if (!confirm('⚠️ 경고: 모든 품목기준관리 데이터(계층구조, 섹션, 항목, 속성)를 초기화하시겠습니까?\n\n이 작업은 되돌릴 수 없습니다!')) {
return;
}
try {
resetAllData();
setUnitOptions([]);
setMaterialOptions([]);
setSurfaceTreatmentOptions([]);
setCustomAttributeOptions({});
setAttributeColumns({});
setBomItems([]);
setCustomTabs([
{ id: 'hierarchy', label: '계층구조', icon: 'FolderTree', isDefault: true, order: 1 },
{ id: 'sections', label: '섹션', icon: 'Layers', isDefault: true, order: 2 },
{ id: 'items', label: '항목', icon: 'ListTree', isDefault: true, order: 3 },
{ id: 'attributes', label: '속성', icon: 'Settings', isDefault: true, order: 4 }
]);
setAttributeSubTabs([]);
console.log('🗑️ 모든 품목기준관리 데이터가 초기화되었습니다');
toast.success('✅ 모든 데이터가 초기화되었습니다!\n계층구조, 섹션, 항목, 속성이 모두 삭제되었습니다.');
setTimeout(() => {
window.location.reload();
}, 1500);
} catch (error) {
toast.error('초기화 중 오류가 발생했습니다');
console.error('Reset error:', error);
}
}, [resetAllData]);
return {
handleDeletePage,
handleDeleteSection,
handleUnlinkField,
handleResetAllData,
};
}

View File

@@ -0,0 +1,112 @@
'use client';
import { useState, useCallback } from 'react';
import { useItemMaster } from '@/contexts/ItemMasterContext';
import { getErrorMessage } from '@/lib/api/error-handler';
import { toast } from 'sonner';
import type { ItemPage } from '@/contexts/ItemMasterContext';
export interface UseImportManagementReturn {
// 섹션 Import 상태
isImportSectionDialogOpen: boolean;
setIsImportSectionDialogOpen: React.Dispatch<React.SetStateAction<boolean>>;
selectedImportSectionId: number | null;
setSelectedImportSectionId: React.Dispatch<React.SetStateAction<number | null>>;
// 필드 Import 상태
isImportFieldDialogOpen: boolean;
setIsImportFieldDialogOpen: React.Dispatch<React.SetStateAction<boolean>>;
selectedImportFieldId: number | null;
setSelectedImportFieldId: React.Dispatch<React.SetStateAction<number | null>>;
importFieldTargetSectionId: number | null;
setImportFieldTargetSectionId: React.Dispatch<React.SetStateAction<number | null>>;
// 핸들러
handleImportSection: (selectedPageId: number | null) => Promise<void>;
handleImportField: (selectedPage: ItemPage | null) => Promise<void>;
handleCloneSection: (sectionId: number) => Promise<void>;
}
export function useImportManagement(): UseImportManagementReturn {
const {
linkSectionToPage,
linkFieldToSection,
cloneSection,
} = useItemMaster();
// 섹션 Import 상태
const [isImportSectionDialogOpen, setIsImportSectionDialogOpen] = useState(false);
const [selectedImportSectionId, setSelectedImportSectionId] = useState<number | null>(null);
// 필드 Import 상태
const [isImportFieldDialogOpen, setIsImportFieldDialogOpen] = useState(false);
const [selectedImportFieldId, setSelectedImportFieldId] = useState<number | null>(null);
const [importFieldTargetSectionId, setImportFieldTargetSectionId] = useState<number | null>(null);
// 섹션 불러오기 핸들러
const handleImportSection = useCallback(async (selectedPageId: number | null) => {
if (!selectedPageId || !selectedImportSectionId) return;
try {
await linkSectionToPage(selectedPageId, selectedImportSectionId);
toast.success('섹션을 불러왔습니다.');
setSelectedImportSectionId(null);
} catch (error) {
console.error('섹션 불러오기 실패:', error);
toast.error(getErrorMessage(error));
}
}, [selectedImportSectionId, linkSectionToPage]);
// 필드 불러오기 핸들러
const handleImportField = useCallback(async (selectedPage: ItemPage | null) => {
if (!importFieldTargetSectionId || !selectedImportFieldId) return;
try {
// 해당 섹션의 마지막 순서 + 1로 설정
const targetSection = selectedPage?.sections.find(s => s.id === importFieldTargetSectionId);
const existingFieldsCount = targetSection?.fields?.length ?? 0;
const newOrderNo = existingFieldsCount;
await linkFieldToSection(importFieldTargetSectionId, selectedImportFieldId, newOrderNo);
toast.success('필드를 섹션에 연결했습니다.');
setSelectedImportFieldId(null);
setImportFieldTargetSectionId(null);
} catch (error) {
console.error('필드 불러오기 실패:', error);
toast.error(getErrorMessage(error));
}
}, [importFieldTargetSectionId, selectedImportFieldId, linkFieldToSection]);
// 섹션 복제 핸들러
const handleCloneSection = useCallback(async (sectionId: number) => {
try {
await cloneSection(sectionId);
toast.success('섹션이 복제되었습니다.');
} catch (error) {
console.error('섹션 복제 실패:', error);
toast.error(getErrorMessage(error));
}
}, [cloneSection]);
return {
// 섹션 Import
isImportSectionDialogOpen,
setIsImportSectionDialogOpen,
selectedImportSectionId,
setSelectedImportSectionId,
// 필드 Import
isImportFieldDialogOpen,
setIsImportFieldDialogOpen,
selectedImportFieldId,
setSelectedImportFieldId,
importFieldTargetSectionId,
setImportFieldTargetSectionId,
// 핸들러
handleImportSection,
handleImportField,
handleCloneSection,
};
}

View File

@@ -0,0 +1,150 @@
'use client';
import { useState, useEffect, useCallback } from 'react';
import { useItemMaster } from '@/contexts/ItemMasterContext';
import { itemMasterApi } from '@/lib/api/item-master';
import { getErrorMessage, ApiError } from '@/lib/api/error-handler';
import {
transformPagesResponse,
transformSectionsResponse,
transformSectionTemplatesResponse,
transformFieldsResponse,
transformCustomTabsResponse,
transformUnitOptionsResponse,
transformSectionTemplateFromSection,
} from '@/lib/api/transformers';
import { toast } from 'sonner';
import type { CustomTab } from './useTabManagement';
import type { UnitOption } from './useAttributeManagement';
export interface UseInitialDataLoadingReturn {
isInitialLoading: boolean;
error: string | null;
reload: () => Promise<void>;
}
interface UseInitialDataLoadingProps {
setCustomTabs: React.Dispatch<React.SetStateAction<CustomTab[]>>;
setUnitOptions: React.Dispatch<React.SetStateAction<UnitOption[]>>;
}
export function useInitialDataLoading({
setCustomTabs,
setUnitOptions,
}: UseInitialDataLoadingProps): UseInitialDataLoadingReturn {
const {
loadItemPages,
loadSectionTemplates,
loadItemMasterFields,
loadIndependentSections,
loadIndependentFields,
} = useItemMaster();
const [isInitialLoading, setIsInitialLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const loadInitialData = useCallback(async () => {
try {
setIsInitialLoading(true);
setError(null);
const data = await itemMasterApi.init();
// 1. 페이지 데이터 로드 (섹션이 이미 포함되어 있음)
const transformedPages = transformPagesResponse(data.pages);
loadItemPages(transformedPages);
// 2. 독립 섹션 로드 (모든 재사용 가능 섹션)
if (data.sections && data.sections.length > 0) {
const transformedSections = transformSectionsResponse(data.sections);
loadIndependentSections(transformedSections);
console.log('✅ 독립 섹션 로드:', transformedSections.length);
}
// 3. 섹션 템플릿 로드
if (data.sectionTemplates && data.sectionTemplates.length > 0) {
const transformedTemplates = transformSectionTemplatesResponse(data.sectionTemplates);
loadSectionTemplates(transformedTemplates);
} else if (data.sections && data.sections.length > 0) {
const templates = data.sections
.filter((s: { is_template?: boolean }) => s.is_template)
.map(transformSectionTemplateFromSection);
if (templates.length > 0) {
loadSectionTemplates(templates);
}
}
// 4. 필드 로드
if (data.fields && data.fields.length > 0) {
const transformedFields = transformFieldsResponse(data.fields);
const independentOnlyFields = transformedFields.filter(
f => f.section_id === null || f.section_id === undefined
);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
loadItemMasterFields(transformedFields as any);
loadIndependentFields(independentOnlyFields);
console.log('✅ 필드 로드:', {
total: transformedFields.length,
independent: independentOnlyFields.length,
});
}
// 5. 커스텀 탭 로드
if (data.customTabs && data.customTabs.length > 0) {
const transformedTabs = transformCustomTabsResponse(data.customTabs);
setCustomTabs(transformedTabs);
}
// 6. 단위 옵션 로드
if (data.unitOptions && data.unitOptions.length > 0) {
const transformedUnits = transformUnitOptionsResponse(data.unitOptions);
setUnitOptions(transformedUnits);
}
console.log('✅ Initial data loaded:', {
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,
});
} catch (err) {
if (err instanceof ApiError && err.errors) {
const errorMessages = Object.entries(err.errors)
.map(([field, messages]) => `${field}: ${messages.join(', ')}`)
.join('\n');
toast.error(errorMessages);
setError('입력값을 확인해주세요.');
} else {
const errorMessage = getErrorMessage(err);
setError(errorMessage);
toast.error(errorMessage);
}
console.error('❌ Failed to load initial data:', err);
} finally {
setIsInitialLoading(false);
}
}, [
loadItemPages,
loadSectionTemplates,
loadItemMasterFields,
loadIndependentSections,
loadIndependentFields,
setCustomTabs,
setUnitOptions,
]);
useEffect(() => {
loadInitialData();
}, [loadInitialData]);
return {
isInitialLoading,
error,
reload: loadInitialData,
};
}

View File

@@ -0,0 +1,84 @@
'use client';
import { useCallback } from 'react';
import { useItemMaster } from '@/contexts/ItemMasterContext';
import { toast } from 'sonner';
import type { ItemPage } from '@/contexts/ItemMasterContext';
export interface UseReorderManagementReturn {
moveSection: (selectedPage: ItemPage | null, dragIndex: number, hoverIndex: number) => Promise<void>;
moveField: (selectedPage: ItemPage | null, sectionId: number, dragFieldId: number, hoverFieldId: number) => Promise<void>;
}
export function useReorderManagement(): UseReorderManagementReturn {
const {
reorderSections,
reorderFields,
} = useItemMaster();
// 섹션 순서 변경 핸들러 (드래그앤드롭)
const moveSection = useCallback(async (
selectedPage: ItemPage | null,
dragIndex: number,
hoverIndex: number
) => {
if (!selectedPage) return;
const sections = [...selectedPage.sections];
const [draggedSection] = sections.splice(dragIndex, 1);
sections.splice(hoverIndex, 0, draggedSection);
const sectionIds = sections.map(s => s.id);
try {
await reorderSections(selectedPage.id, sectionIds);
toast.success('섹션 순서가 변경되었습니다');
} catch (error) {
console.error('섹션 순서 변경 실패:', error);
toast.error('섹션 순서 변경에 실패했습니다');
}
}, [reorderSections]);
// 필드 순서 변경 핸들러
const moveField = useCallback(async (
selectedPage: ItemPage | null,
sectionId: number,
dragFieldId: number,
hoverFieldId: number
) => {
if (!selectedPage) return;
const section = selectedPage.sections.find(s => s.id === sectionId);
if (!section || !section.fields) return;
// 동일 필드면 스킵
if (dragFieldId === hoverFieldId) return;
// 정렬된 배열에서 ID로 인덱스 찾기
const sortedFields = [...section.fields].sort((a, b) => (a.order_no ?? 0) - (b.order_no ?? 0));
const dragIndex = sortedFields.findIndex(f => f.id === dragFieldId);
const hoverIndex = sortedFields.findIndex(f => f.id === hoverFieldId);
// 유효하지 않은 인덱스 체크
if (dragIndex === -1 || hoverIndex === -1) {
return;
}
// 드래그된 필드를 제거하고 새 위치에 삽입
const [draggedField] = sortedFields.splice(dragIndex, 1);
sortedFields.splice(hoverIndex, 0, draggedField);
const newFieldIds = sortedFields.map(f => f.id);
try {
await reorderFields(sectionId, newFieldIds);
toast.success('항목 순서가 변경되었습니다');
} catch (error) {
toast.error('항목 순서 변경에 실패했습니다');
}
}, [reorderFields]);
return {
moveSection,
moveField,
};
}