- 미사용 import/변수/console.log 대량 정리 (100+개 파일) - ItemMasterContext 간소화 (미사용 로직 제거) - IntegratedListTemplateV2 / UniversalListPage 개선 - 결재 컴포넌트(ApprovalBox, DraftBox, ReferenceBox) 정리 - HR 컴포넌트(급여/휴가/부서) 코드 간소화 - globals.css 스타일 정리 및 개선 - AuthenticatedLayout 개선 - middleware CSP 정리 - proxy route 불필요 로깅 제거 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
557 lines
24 KiB
TypeScript
557 lines
24 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { toast } from 'sonner';
|
|
import { useItemMaster } from '@/contexts/ItemMasterContext';
|
|
import { useErrorAlert } from '../contexts';
|
|
import type { ItemPage, SectionTemplate, TemplateField, BOMItem, ItemMasterField } from '@/contexts/ItemMasterContext';
|
|
import { templateService } from '../services';
|
|
import { ApiError } from '@/lib/api/error-handler';
|
|
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
|
|
|
// 필드 타입 정의 - field_type 캐스팅용
|
|
type FieldTypeValue = 'textbox' | 'number' | 'dropdown' | 'checkbox' | 'date' | 'textarea';
|
|
|
|
export interface UseTemplateManagementReturn {
|
|
// 섹션 템플릿 다이얼로그 상태
|
|
isSectionTemplateDialogOpen: boolean;
|
|
setIsSectionTemplateDialogOpen: (open: boolean) => void;
|
|
editingSectionTemplateId: number | null;
|
|
setEditingSectionTemplateId: (id: number | null) => void;
|
|
|
|
// 섹션 템플릿 폼 상태
|
|
newSectionTemplateTitle: string;
|
|
setNewSectionTemplateTitle: (title: string) => void;
|
|
newSectionTemplateDescription: string;
|
|
setNewSectionTemplateDescription: (desc: string) => void;
|
|
newSectionTemplateCategory: string[];
|
|
setNewSectionTemplateCategory: React.Dispatch<React.SetStateAction<string[]>>;
|
|
newSectionTemplateType: 'fields' | 'bom';
|
|
setNewSectionTemplateType: (type: 'fields' | 'bom') => void;
|
|
|
|
// 템플릿 불러오기 다이얼로그
|
|
isLoadTemplateDialogOpen: boolean;
|
|
setIsLoadTemplateDialogOpen: (open: boolean) => void;
|
|
selectedTemplateId: string | null;
|
|
setSelectedTemplateId: (id: string | null) => void;
|
|
|
|
// 템플릿 필드 다이얼로그 상태
|
|
isTemplateFieldDialogOpen: boolean;
|
|
setIsTemplateFieldDialogOpen: (open: boolean) => void;
|
|
currentTemplateId: number | null;
|
|
setCurrentTemplateId: (id: number | null) => void;
|
|
editingTemplateFieldId: number | null;
|
|
setEditingTemplateFieldId: (id: number | null) => void;
|
|
|
|
// 템플릿 필드 폼 상태
|
|
templateFieldName: string;
|
|
setTemplateFieldName: (name: string) => void;
|
|
templateFieldKey: string;
|
|
setTemplateFieldKey: (key: string) => void;
|
|
// string 타입으로 유연하게 처리
|
|
templateFieldInputType: string;
|
|
setTemplateFieldInputType: (type: string) => void;
|
|
templateFieldRequired: boolean;
|
|
setTemplateFieldRequired: (required: boolean) => void;
|
|
templateFieldOptions: string;
|
|
setTemplateFieldOptions: (options: string) => void;
|
|
templateFieldDescription: string;
|
|
setTemplateFieldDescription: (desc: string) => void;
|
|
templateFieldMultiColumn: boolean;
|
|
setTemplateFieldMultiColumn: (multi: boolean) => void;
|
|
templateFieldColumnCount: number;
|
|
setTemplateFieldColumnCount: (count: number) => void;
|
|
templateFieldColumnNames: string[];
|
|
setTemplateFieldColumnNames: React.Dispatch<React.SetStateAction<string[]>>;
|
|
|
|
// 템플릿 필드 마스터 항목 관련 - 유연한 타입 지원
|
|
templateFieldInputMode: 'custom' | 'master' | 'new' | 'existing';
|
|
setTemplateFieldInputMode: (mode: 'custom' | 'master' | 'new' | 'existing') => void;
|
|
templateFieldShowMasterFieldList: boolean;
|
|
setTemplateFieldShowMasterFieldList: (show: boolean) => void;
|
|
templateFieldSelectedMasterFieldId: string;
|
|
setTemplateFieldSelectedMasterFieldId: (id: string) => void;
|
|
|
|
// 핸들러
|
|
handleAddSectionTemplate: () => void;
|
|
handleEditSectionTemplate: (template: SectionTemplate) => void;
|
|
handleUpdateSectionTemplate: () => void;
|
|
handleDeleteSectionTemplate: (id: number) => void;
|
|
handleLoadTemplate: (selectedPage: ItemPage | undefined) => void;
|
|
handleAddTemplateField: () => Promise<void>;
|
|
handleEditTemplateField: (templateId: number, field: TemplateField) => void;
|
|
handleDeleteTemplateField: (templateId: number, fieldId: string) => void;
|
|
handleAddBOMItemToTemplate: (templateId: number, item: Omit<BOMItem, 'id' | 'created_at' | 'updated_at' | 'tenant_id' | 'section_id'>) => void;
|
|
handleUpdateBOMItemInTemplate: (templateId: number, itemId: number, item: Partial<BOMItem>) => void;
|
|
handleDeleteBOMItemFromTemplate: (templateId: number, itemId: number) => void;
|
|
resetSectionTemplateForm: () => void;
|
|
resetTemplateFieldForm: () => void;
|
|
}
|
|
|
|
export function useTemplateManagement(): UseTemplateManagementReturn {
|
|
const {
|
|
sectionTemplates,
|
|
addSectionTemplate,
|
|
updateSectionTemplate,
|
|
deleteSectionTemplate,
|
|
addSectionToPage,
|
|
addItemMasterField,
|
|
itemMasterFields,
|
|
tenantId,
|
|
// 2025-11-26: sectionsAsTemplates가 itemPages에서 파생되므로
|
|
// 섹션 탭에서 수정/삭제 시 실제 섹션 API를 호출해야 함
|
|
updateSection,
|
|
deleteSection,
|
|
itemPages,
|
|
// 2025-11-26: 섹션 탭에서 새 섹션 추가 시 독립 섹션으로 생성
|
|
createIndependentSection,
|
|
// 2025-11-27: entity_relationships 기반 필드 연결/해제
|
|
linkFieldToSection,
|
|
unlinkFieldFromSection,
|
|
independentFields,
|
|
// 2025-11-27: 필드 수정 API
|
|
updateField,
|
|
// 2025-12-01: 섹션에 필드 추가 API (계층구조 탭과 동일한 방식)
|
|
addFieldToSection,
|
|
// 2025-12-01: BOM 관리 API (API 기반으로 변경)
|
|
addBOMItem,
|
|
updateBOMItem,
|
|
deleteBOMItem,
|
|
} = useItemMaster();
|
|
|
|
// 에러 알림 (AlertDialog로 표시)
|
|
const { showErrorAlert } = useErrorAlert();
|
|
|
|
// 섹션 템플릿 다이얼로그 상태
|
|
const [isSectionTemplateDialogOpen, setIsSectionTemplateDialogOpen] = useState(false);
|
|
const [editingSectionTemplateId, setEditingSectionTemplateId] = useState<number | null>(null);
|
|
|
|
// 섹션 템플릿 폼 상태
|
|
const [newSectionTemplateTitle, setNewSectionTemplateTitle] = useState('');
|
|
const [newSectionTemplateDescription, setNewSectionTemplateDescription] = useState('');
|
|
const [newSectionTemplateCategory, setNewSectionTemplateCategory] = useState<string[]>([]);
|
|
const [newSectionTemplateType, setNewSectionTemplateType] = useState<'fields' | 'bom'>('fields');
|
|
|
|
// 템플릿 불러오기 다이얼로그
|
|
const [isLoadTemplateDialogOpen, setIsLoadTemplateDialogOpen] = useState(false);
|
|
const [selectedTemplateId, setSelectedTemplateId] = useState<string | null>(null);
|
|
|
|
// 템플릿 필드 다이얼로그 상태
|
|
const [isTemplateFieldDialogOpen, setIsTemplateFieldDialogOpen] = useState(false);
|
|
const [currentTemplateId, setCurrentTemplateId] = useState<number | null>(null);
|
|
const [editingTemplateFieldId, setEditingTemplateFieldId] = useState<number | null>(null);
|
|
|
|
// 템플릿 필드 폼 상태
|
|
const [templateFieldName, setTemplateFieldName] = useState('');
|
|
const [templateFieldKey, setTemplateFieldKey] = useState('');
|
|
const [templateFieldInputType, setTemplateFieldInputType] = useState<string>('textbox');
|
|
const [templateFieldRequired, setTemplateFieldRequired] = useState(false);
|
|
const [templateFieldOptions, setTemplateFieldOptions] = useState('');
|
|
const [templateFieldDescription, setTemplateFieldDescription] = useState('');
|
|
const [templateFieldMultiColumn, setTemplateFieldMultiColumn] = useState(false);
|
|
const [templateFieldColumnCount, setTemplateFieldColumnCount] = useState(2);
|
|
const [templateFieldColumnNames, setTemplateFieldColumnNames] = useState<string[]>(['컬럼1', '컬럼2']);
|
|
|
|
// 템플릿 필드 마스터 항목 관련 - 'new'/'existing' 호환을 위해 유연하게 처리
|
|
const [templateFieldInputMode, setTemplateFieldInputMode] = useState<'custom' | 'master' | 'new' | 'existing'>('custom');
|
|
const [templateFieldShowMasterFieldList, setTemplateFieldShowMasterFieldList] = useState(false);
|
|
const [templateFieldSelectedMasterFieldId, setTemplateFieldSelectedMasterFieldId] = useState('');
|
|
|
|
// 섹션 템플릿 추가 (2025-11-26: 독립 섹션으로 생성하여 sectionsAsTemplates에 반영)
|
|
const handleAddSectionTemplate = async () => {
|
|
if (!newSectionTemplateTitle.trim()) {
|
|
toast.error('섹션 제목을 입력해주세요');
|
|
return;
|
|
}
|
|
|
|
// 2025-11-26: sectionsAsTemplates가 itemPages + independentSections에서 파생되므로
|
|
// 독립 섹션으로 생성해야 화면에 바로 반영됨
|
|
const newSectionData = {
|
|
title: newSectionTemplateTitle,
|
|
type: newSectionTemplateType as 'fields' | 'bom',
|
|
description: newSectionTemplateDescription || undefined,
|
|
is_template: true, // 섹션 탭에서 생성된 섹션은 템플릿으로 표시
|
|
is_default: false,
|
|
};
|
|
|
|
|
|
try {
|
|
await createIndependentSection(newSectionData);
|
|
resetSectionTemplateForm();
|
|
toast.success('섹션이 추가되었습니다!');
|
|
} catch (error) {
|
|
if (isNextRedirectError(error)) throw error;
|
|
console.error('섹션 추가 실패:', error);
|
|
toast.error('섹션 추가에 실패했습니다.');
|
|
}
|
|
};
|
|
|
|
// 섹션 템플릿 수정 시작
|
|
const handleEditSectionTemplate = (template: SectionTemplate) => {
|
|
setEditingSectionTemplateId(template.id);
|
|
setNewSectionTemplateTitle(template.template_name);
|
|
setNewSectionTemplateDescription(template.description || '');
|
|
setNewSectionTemplateCategory(template.category || []);
|
|
setNewSectionTemplateType(template.section_type === 'BOM' ? 'bom' : 'fields');
|
|
setIsSectionTemplateDialogOpen(true);
|
|
};
|
|
|
|
// 섹션 템플릿 업데이트 (2025-11-26: sectionsAsTemplates 사용으로 실제 섹션 API 호출)
|
|
const handleUpdateSectionTemplate = async () => {
|
|
if (!editingSectionTemplateId || !newSectionTemplateTitle.trim()) {
|
|
toast.error('섹션 제목을 입력해주세요');
|
|
return;
|
|
}
|
|
|
|
// sectionsAsTemplates가 itemPages에서 파생되므로, 실제 섹션을 업데이트해야 함
|
|
const updateData = {
|
|
title: newSectionTemplateTitle,
|
|
description: newSectionTemplateDescription || undefined,
|
|
section_type: (newSectionTemplateType === 'bom' ? 'BOM' : 'BASIC') as 'BASIC' | 'BOM' | 'CUSTOM'
|
|
};
|
|
|
|
try {
|
|
// updateSection 호출 (템플릿이 아닌 실제 섹션 API)
|
|
await updateSection(editingSectionTemplateId, updateData);
|
|
resetSectionTemplateForm();
|
|
toast.success('섹션이 수정되었습니다!');
|
|
} catch (error) {
|
|
if (isNextRedirectError(error)) throw error;
|
|
console.error('섹션 수정 실패:', error);
|
|
toast.error('섹션 수정에 실패했습니다.');
|
|
}
|
|
};
|
|
|
|
// 섹션 템플릿 삭제 (2025-11-26: sectionsAsTemplates 사용으로 실제 섹션 API 호출)
|
|
const handleDeleteSectionTemplate = async (id: number) => {
|
|
if (confirm('이 섹션을 삭제하시겠습니까?')) {
|
|
try {
|
|
// deleteSection 호출 (템플릿이 아닌 실제 섹션 API)
|
|
await deleteSection(id);
|
|
toast.success('섹션이 삭제되었습니다!');
|
|
} catch (error) {
|
|
if (isNextRedirectError(error)) throw error;
|
|
console.error('섹션 삭제 실패:', error);
|
|
toast.error('섹션 삭제에 실패했습니다.');
|
|
}
|
|
}
|
|
};
|
|
|
|
// 템플릿 불러오기
|
|
const handleLoadTemplate = (selectedPage: ItemPage | undefined) => {
|
|
if (!selectedTemplateId || !selectedPage) {
|
|
toast.error('템플릿을 선택해주세요');
|
|
return;
|
|
}
|
|
|
|
const template = sectionTemplates.find(t => t.id === Number(selectedTemplateId));
|
|
if (!template) {
|
|
toast.error('템플릿을 찾을 수 없습니다');
|
|
return;
|
|
}
|
|
|
|
const newSection = {
|
|
page_id: selectedPage.id,
|
|
title: template.template_name,
|
|
section_type: template.section_type === 'BOM' ? 'BOM' as const : 'BASIC' as const,
|
|
description: template.description || undefined,
|
|
order_no: selectedPage.sections.length + 1,
|
|
is_template: false,
|
|
is_default: false,
|
|
is_collapsible: true,
|
|
is_default_open: true,
|
|
fields: [],
|
|
bom_items: template.section_type === 'BOM' ? [] : undefined
|
|
};
|
|
|
|
addSectionToPage(selectedPage.id, newSection);
|
|
setSelectedTemplateId(null);
|
|
setIsLoadTemplateDialogOpen(false);
|
|
toast.success('섹션이 불러와졌습니다');
|
|
};
|
|
|
|
// 템플릿 필드 추가/수정 (2025-11-27: API 사용으로 변경)
|
|
// sectionsAsTemplates가 itemPages + independentSections에서 파생되므로
|
|
// entity_relationships 기반 연결 API를 사용해야 실시간 반영됨
|
|
// 2025-12-01: custom/master 모드 분기 처리 추가
|
|
const handleAddTemplateField = async () => {
|
|
if (!currentTemplateId || !templateFieldName.trim() || !templateFieldKey.trim()) {
|
|
toast.error('모든 필수 항목을 입력해주세요');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// 수정 모드: 기존 필드 속성 업데이트
|
|
if (editingTemplateFieldId) {
|
|
// 2025-11-28: field_key 추가 (백엔드 요청에 포함)
|
|
const updateData = {
|
|
field_name: templateFieldName,
|
|
field_key: templateFieldKey, // 2025-11-28: field_key 추가
|
|
field_type: templateFieldInputType as FieldTypeValue,
|
|
is_required: templateFieldRequired,
|
|
placeholder: templateFieldDescription || null,
|
|
options: templateFieldInputType === 'dropdown' && templateFieldOptions.trim()
|
|
? templateFieldOptions.split(',').map(o => ({ label: o.trim(), value: o.trim() }))
|
|
: null,
|
|
properties: {
|
|
inputType: templateFieldInputType,
|
|
required: templateFieldRequired,
|
|
multiColumn: (templateFieldInputType === 'textbox' || templateFieldInputType === 'textarea') ? templateFieldMultiColumn : undefined,
|
|
columnCount: (templateFieldInputType === 'textbox' || templateFieldInputType === 'textarea') && templateFieldMultiColumn ? templateFieldColumnCount : undefined,
|
|
columnNames: (templateFieldInputType === 'textbox' || templateFieldInputType === 'textarea') && templateFieldMultiColumn ? templateFieldColumnNames : undefined,
|
|
},
|
|
};
|
|
|
|
await updateField(editingTemplateFieldId, updateData);
|
|
toast.success('항목이 수정되었습니다');
|
|
resetTemplateFieldForm();
|
|
return;
|
|
}
|
|
|
|
// 추가 모드: custom/master 모드에 따라 분기 처리
|
|
// 2025-12-01: custom 모드에서는 새 필드 생성 후 연결, master 모드에서는 기존 필드 연결
|
|
if (templateFieldInputMode === 'master') {
|
|
// master 모드: 기존 필드를 섹션에 연결
|
|
// templateFieldKey는 선택된 필드의 ID (handleSelectMasterField에서 설정됨)
|
|
const existingField = independentFields.find(f => f.id.toString() === templateFieldKey);
|
|
|
|
if (existingField) {
|
|
await linkFieldToSection(currentTemplateId, existingField.id);
|
|
toast.success('항목이 섹션에 연결되었습니다');
|
|
} else {
|
|
toast.error('항목 탭에서 먼저 항목을 생성해주세요');
|
|
return;
|
|
}
|
|
} else {
|
|
// custom 모드: 섹션에 직접 필드 추가 (계층구조 탭과 동일한 방식)
|
|
// 2025-12-01: createIndependentField + linkFieldToSection 대신 addFieldToSection 사용
|
|
// POST /sections/{id}/fields API를 사용하여 섹션에 바로 필드 생성
|
|
const newFieldData = {
|
|
section_id: currentTemplateId,
|
|
master_field_id: null,
|
|
field_name: templateFieldName,
|
|
field_key: templateFieldKey,
|
|
field_type: templateFieldInputType as FieldTypeValue,
|
|
order_no: 0,
|
|
is_required: templateFieldRequired,
|
|
placeholder: templateFieldDescription || null,
|
|
default_value: null,
|
|
display_condition: null,
|
|
validation_rules: null,
|
|
options: templateFieldInputType === 'dropdown' && templateFieldOptions.trim()
|
|
? templateFieldOptions.split(',').map(o => ({ label: o.trim(), value: o.trim() }))
|
|
: null,
|
|
properties: {
|
|
inputType: templateFieldInputType,
|
|
required: templateFieldRequired,
|
|
multiColumn: (templateFieldInputType === 'textbox' || templateFieldInputType === 'textarea') ? templateFieldMultiColumn : undefined,
|
|
columnCount: (templateFieldInputType === 'textbox' || templateFieldInputType === 'textarea') && templateFieldMultiColumn ? templateFieldColumnCount : undefined,
|
|
columnNames: (templateFieldInputType === 'textbox' || templateFieldInputType === 'textarea') && templateFieldMultiColumn ? templateFieldColumnNames : undefined,
|
|
},
|
|
};
|
|
|
|
// 섹션에 필드 추가 (계층구조 탭과 동일)
|
|
await addFieldToSection(currentTemplateId, newFieldData);
|
|
toast.success('항목이 섹션에 추가되었습니다');
|
|
}
|
|
|
|
resetTemplateFieldForm();
|
|
} catch (error) {
|
|
if (isNextRedirectError(error)) throw error;
|
|
console.error('항목 처리 실패:', error);
|
|
|
|
// 422 ValidationException 상세 메시지 처리 (field_key 중복/예약어, field_name 중복 등) → AlertDialog로 표시
|
|
if (error instanceof ApiError) {
|
|
// errors 객체에서 첫 번째 에러 메시지 추출
|
|
if (error.errors && Object.keys(error.errors).length > 0) {
|
|
const firstKey = Object.keys(error.errors)[0];
|
|
const firstError = error.errors[firstKey];
|
|
const errorMessage = Array.isArray(firstError) ? firstError[0] : firstError;
|
|
showErrorAlert(errorMessage, '항목 저장 실패');
|
|
} else {
|
|
showErrorAlert(error.message, '항목 저장 실패');
|
|
}
|
|
} else {
|
|
showErrorAlert('항목 처리에 실패했습니다', '오류');
|
|
}
|
|
}
|
|
};
|
|
|
|
// 템플릿 필드 수정 시작
|
|
// 2025-11-28: field_key 형식 {ID}_{사용자입력}에서 사용자입력 부분만 추출
|
|
const handleEditTemplateField = (templateId: number, field: TemplateField) => {
|
|
setCurrentTemplateId(templateId);
|
|
setEditingTemplateFieldId(Number(field.id));
|
|
setTemplateFieldName(field.name);
|
|
// 2025-12-01: templateService 사용으로 변경
|
|
setTemplateFieldKey(templateService.extractUserInputFromFieldKey(field.fieldKey || ''));
|
|
setTemplateFieldInputType(field.property.inputType);
|
|
setTemplateFieldRequired(field.property.required);
|
|
setTemplateFieldOptions(field.property.options?.join(', ') || '');
|
|
setTemplateFieldDescription(field.description || '');
|
|
setTemplateFieldMultiColumn(field.property.multiColumn || false);
|
|
setTemplateFieldColumnCount(field.property.columnCount || 2);
|
|
setTemplateFieldColumnNames(field.property.columnNames || ['컬럼1', '컬럼2']);
|
|
setIsTemplateFieldDialogOpen(true);
|
|
};
|
|
|
|
// 템플릿 필드 연결 해제 (2025-11-27: entity_relationships 기반으로 변경)
|
|
// sectionId = templateId (sectionsAsTemplates에서 섹션 ID로 사용)
|
|
// fieldId = 실제 item_fields의 ID
|
|
const handleDeleteTemplateField = async (templateId: number, fieldId: string) => {
|
|
if (!confirm('이 항목의 연결을 해제하시겠습니까?\n(항목 자체는 삭제되지 않고 항목 탭에 유지됩니다)')) return;
|
|
|
|
try {
|
|
// entity_relationships 기반 연결 해제 API 호출
|
|
await unlinkFieldFromSection(templateId, Number(fieldId));
|
|
toast.success('항목 연결이 해제되었습니다');
|
|
} catch (error) {
|
|
if (isNextRedirectError(error)) throw error;
|
|
console.error('항목 연결 해제 실패:', error);
|
|
toast.error('항목 연결 해제에 실패했습니다.');
|
|
}
|
|
};
|
|
|
|
// BOM 항목 추가 (2025-12-01: API 기반으로 변경)
|
|
// templateId = sectionId (sectionsAsTemplates에서 섹션 ID로 사용)
|
|
const handleAddBOMItemToTemplate = async (templateId: number, item: Omit<BOMItem, 'id' | 'created_at' | 'updated_at' | 'tenant_id' | 'section_id'>) => {
|
|
try {
|
|
// addBOMItem API 호출 (Context에서 itemPages/independentSections 자동 업데이트)
|
|
await addBOMItem(templateId, item);
|
|
// toast는 BOMManagementSection 컴포넌트에서 처리
|
|
} catch (error) {
|
|
if (isNextRedirectError(error)) throw error;
|
|
console.error('BOM 항목 추가 실패:', error);
|
|
toast.error('BOM 항목 추가에 실패했습니다');
|
|
}
|
|
};
|
|
|
|
// BOM 항목 수정 (2025-12-01: API 기반으로 변경)
|
|
const handleUpdateBOMItemInTemplate = async (templateId: number, itemId: number, item: Partial<BOMItem>) => {
|
|
try {
|
|
// updateBOMItem API 호출 (Context에서 itemPages/independentSections 자동 업데이트)
|
|
await updateBOMItem(itemId, item);
|
|
// toast는 BOMManagementSection 컴포넌트에서 처리
|
|
} catch (error) {
|
|
if (isNextRedirectError(error)) throw error;
|
|
console.error('BOM 항목 수정 실패:', error);
|
|
toast.error('BOM 항목 수정에 실패했습니다');
|
|
}
|
|
};
|
|
|
|
// BOM 항목 삭제 (2025-12-01: API 기반으로 변경)
|
|
const handleDeleteBOMItemFromTemplate = async (templateId: number, itemId: number) => {
|
|
try {
|
|
// deleteBOMItem API 호출 (Context에서 itemPages/independentSections 자동 업데이트)
|
|
await deleteBOMItem(itemId);
|
|
// toast는 BOMManagementSection 컴포넌트에서 처리
|
|
} catch (error) {
|
|
if (isNextRedirectError(error)) throw error;
|
|
console.error('BOM 항목 삭제 실패:', error);
|
|
toast.error('BOM 항목 삭제에 실패했습니다');
|
|
}
|
|
};
|
|
|
|
// 섹션 템플릿 폼 초기화
|
|
const resetSectionTemplateForm = () => {
|
|
setEditingSectionTemplateId(null);
|
|
setNewSectionTemplateTitle('');
|
|
setNewSectionTemplateDescription('');
|
|
setNewSectionTemplateCategory([]);
|
|
setNewSectionTemplateType('fields');
|
|
setIsSectionTemplateDialogOpen(false);
|
|
};
|
|
|
|
// 템플릿 필드 폼 초기화
|
|
const resetTemplateFieldForm = () => {
|
|
setTemplateFieldName('');
|
|
setTemplateFieldKey('');
|
|
setTemplateFieldInputType('textbox');
|
|
setTemplateFieldRequired(false);
|
|
setTemplateFieldOptions('');
|
|
setTemplateFieldDescription('');
|
|
setTemplateFieldMultiColumn(false);
|
|
setTemplateFieldColumnCount(2);
|
|
setTemplateFieldColumnNames(['컬럼1', '컬럼2']);
|
|
setEditingTemplateFieldId(null);
|
|
setTemplateFieldInputMode('custom');
|
|
setTemplateFieldShowMasterFieldList(false);
|
|
setTemplateFieldSelectedMasterFieldId('');
|
|
setIsTemplateFieldDialogOpen(false);
|
|
};
|
|
|
|
return {
|
|
// 섹션 템플릿 다이얼로그 상태
|
|
isSectionTemplateDialogOpen,
|
|
setIsSectionTemplateDialogOpen,
|
|
editingSectionTemplateId,
|
|
setEditingSectionTemplateId,
|
|
|
|
// 섹션 템플릿 폼 상태
|
|
newSectionTemplateTitle,
|
|
setNewSectionTemplateTitle,
|
|
newSectionTemplateDescription,
|
|
setNewSectionTemplateDescription,
|
|
newSectionTemplateCategory,
|
|
setNewSectionTemplateCategory,
|
|
newSectionTemplateType,
|
|
setNewSectionTemplateType,
|
|
|
|
// 템플릿 불러오기 다이얼로그
|
|
isLoadTemplateDialogOpen,
|
|
setIsLoadTemplateDialogOpen,
|
|
selectedTemplateId,
|
|
setSelectedTemplateId,
|
|
|
|
// 템플릿 필드 다이얼로그 상태
|
|
isTemplateFieldDialogOpen,
|
|
setIsTemplateFieldDialogOpen,
|
|
currentTemplateId,
|
|
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,
|
|
resetSectionTemplateForm,
|
|
resetTemplateFieldForm,
|
|
};
|
|
} |