fix: 품목기준관리 실시간 동기화 수정
- BOM 항목 추가/수정/삭제 시 섹션탭 즉시 반영 - 섹션 복제 시 UI 즉시 업데이트 (null vs undefined 이슈 해결) - 항목 수정 기능 추가 (useTemplateManagement) - 실시간 동기화 문서 추가 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -27,11 +27,12 @@ export interface UseSectionManagementReturn {
|
||||
setExpandedSections: React.Dispatch<React.SetStateAction<Record<string, boolean>>>;
|
||||
|
||||
// 핸들러
|
||||
handleAddSection: (selectedPage: ItemPage | undefined) => void;
|
||||
handleLinkTemplate: (template: SectionTemplate, selectedPage: ItemPage | undefined) => void;
|
||||
handleAddSection: (selectedPage: ItemPage | undefined) => Promise<void>;
|
||||
handleLinkTemplate: (template: SectionTemplate, selectedPage: ItemPage | undefined) => Promise<void>;
|
||||
handleEditSectionTitle: (sectionId: number, currentTitle: string) => void;
|
||||
handleSaveSectionTitle: (selectedPage: ItemPage | undefined) => void;
|
||||
handleDeleteSection: (pageId: number, sectionId: number) => void;
|
||||
handleUnlinkSection: (pageId: number, sectionId: number) => void; // 계층구조 탭용 - 연결 해제
|
||||
handleDeleteSection: (pageId: number, sectionId: number) => void; // 섹션 탭용 - 실제 삭제
|
||||
toggleSection: (sectionId: string) => void;
|
||||
resetSectionForm: () => void;
|
||||
}
|
||||
@@ -42,8 +43,8 @@ export function useSectionManagement(): UseSectionManagementReturn {
|
||||
addSectionToPage,
|
||||
updateSection,
|
||||
deleteSection,
|
||||
addSectionTemplate,
|
||||
tenantId,
|
||||
linkSectionToPage, // 2025-11-26: 기존 섹션을 페이지에 연결 (entity_relationships)
|
||||
unlinkSectionFromPage, // 2025-11-26: EntityRelationship API 사용
|
||||
} = useItemMaster();
|
||||
|
||||
// 상태
|
||||
@@ -58,112 +59,84 @@ export function useSectionManagement(): UseSectionManagementReturn {
|
||||
const [expandedSections, setExpandedSections] = useState<Record<string, boolean>>({});
|
||||
|
||||
// 섹션 추가
|
||||
const handleAddSection = (selectedPage: ItemPage | undefined) => {
|
||||
const handleAddSection = async (selectedPage: ItemPage | undefined) => {
|
||||
if (!selectedPage || !newSectionTitle.trim()) {
|
||||
toast.error('하위섹션 제목을 입력해주세요');
|
||||
return;
|
||||
}
|
||||
|
||||
const sectionType: 'BASIC' | 'BOM' | 'CUSTOM' = newSectionType === 'bom' ? 'BOM' : 'BASIC';
|
||||
const newSection: ItemSection = {
|
||||
id: Date.now(),
|
||||
const newSection: Omit<ItemSection, 'id' | 'created_at' | 'updated_at'> = {
|
||||
page_id: selectedPage.id,
|
||||
section_name: newSectionTitle,
|
||||
title: newSectionTitle,
|
||||
section_type: sectionType,
|
||||
description: newSectionDescription || undefined,
|
||||
order_no: selectedPage.sections.length + 1,
|
||||
is_template: false,
|
||||
is_default: false,
|
||||
is_collapsible: true,
|
||||
is_default_open: true,
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
fields: [],
|
||||
bomItems: sectionType === 'BOM' ? [] : undefined
|
||||
bom_items: sectionType === 'BOM' ? [] : undefined
|
||||
};
|
||||
|
||||
console.log('Adding section to page:', {
|
||||
pageId: selectedPage.id,
|
||||
page_name: selectedPage.page_name,
|
||||
sectionTitle: newSection.section_name,
|
||||
sectionTitle: newSection.title,
|
||||
sectionType: newSection.section_type,
|
||||
currentSectionCount: selectedPage.sections.length,
|
||||
newSection: newSection
|
||||
});
|
||||
|
||||
// 1. 페이지에 섹션 추가
|
||||
addSectionToPage(selectedPage.id, newSection);
|
||||
try {
|
||||
// 페이지에 섹션 추가 (API 호출)
|
||||
// 2025-11-26: sectionsAsTemplates가 itemPages에서 useMemo로 파생되므로
|
||||
// 별도의 addSectionTemplate 호출 불필요 (자동 동기화)
|
||||
await addSectionToPage(selectedPage.id, newSection);
|
||||
|
||||
// 2. 섹션관리 탭에도 템플릿으로 자동 추가
|
||||
const newTemplateData = {
|
||||
tenant_id: tenantId ?? 0,
|
||||
template_name: newSection.section_name,
|
||||
section_type: newSection.section_type as 'BASIC' | 'BOM' | 'CUSTOM',
|
||||
description: newSection.description ?? null,
|
||||
default_fields: null,
|
||||
created_by: null,
|
||||
updated_by: null,
|
||||
};
|
||||
addSectionTemplate(newTemplateData);
|
||||
console.log('Section added to page:', {
|
||||
sectionTitle: newSection.title
|
||||
});
|
||||
|
||||
console.log('Section added to both page and template:', {
|
||||
sectionId: newSection.id,
|
||||
templateTitle: newTemplateData.template_name
|
||||
});
|
||||
|
||||
resetSectionForm();
|
||||
toast.success(`${newSectionType === 'bom' ? 'BOM' : '일반'} 섹션이 페이지에 추가되고 템플릿으로도 등록되었습니다!`);
|
||||
resetSectionForm();
|
||||
toast.success(`${newSectionType === 'bom' ? 'BOM' : '일반'} 섹션이 추가되었습니다!`);
|
||||
} catch (error) {
|
||||
console.error('섹션 추가 실패:', error);
|
||||
toast.error('섹션 추가에 실패했습니다. 다시 시도해주세요.');
|
||||
}
|
||||
};
|
||||
|
||||
// 섹션 템플릿을 페이지에 연결
|
||||
const handleLinkTemplate = (template: SectionTemplate, selectedPage: ItemPage | undefined) => {
|
||||
// 기존 섹션을 페이지에 연결 (entity_relationships 테이블 사용)
|
||||
// 2025-11-26: 새 섹션 생성이 아닌, 기존 섹션을 연결만 함
|
||||
const handleLinkTemplate = async (template: SectionTemplate, selectedPage: ItemPage | undefined) => {
|
||||
if (!selectedPage) {
|
||||
toast.error('페이지를 먼저 선택해주세요');
|
||||
return;
|
||||
}
|
||||
|
||||
// 템플릿을 섹션으로 변환하여 페이지에 추가
|
||||
const newSection: Omit<ItemSection, 'id' | 'created_at' | 'updated_at'> = {
|
||||
page_id: selectedPage.id,
|
||||
section_name: template.template_name,
|
||||
section_type: template.section_type,
|
||||
description: template.description || undefined,
|
||||
order_no: selectedPage.sections.length + 1,
|
||||
is_collapsible: true,
|
||||
is_default_open: true,
|
||||
fields: template.fields ? template.fields.map((field, idx) => ({
|
||||
id: Date.now() + idx,
|
||||
section_id: 0, // 추후 업데이트됨
|
||||
field_name: field.name,
|
||||
field_type: field.property.inputType,
|
||||
order_no: idx + 1,
|
||||
is_required: field.property.required,
|
||||
placeholder: field.description || null,
|
||||
default_value: null,
|
||||
display_condition: null,
|
||||
validation_rules: null,
|
||||
options: field.property.options
|
||||
? field.property.options.map(opt => ({ label: opt, value: opt }))
|
||||
: null,
|
||||
properties: field.property.multiColumn ? {
|
||||
multiColumn: true,
|
||||
columnCount: field.property.columnCount,
|
||||
columnNames: field.property.columnNames
|
||||
} : null,
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString()
|
||||
})) : [],
|
||||
bomItems: template.section_type === 'BOM' ? (template.bomItems || []) : undefined
|
||||
};
|
||||
// 이미 연결된 섹션인지 확인
|
||||
const isAlreadyLinked = selectedPage.sections.some(s => s.id === template.id);
|
||||
if (isAlreadyLinked) {
|
||||
toast.error('이미 페이지에 연결된 섹션입니다');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Linking template to page:', {
|
||||
templateId: template.id,
|
||||
templateName: template.template_name,
|
||||
console.log('Linking existing section to page:', {
|
||||
sectionId: template.id,
|
||||
sectionName: template.template_name,
|
||||
pageId: selectedPage.id,
|
||||
newSection
|
||||
orderNo: selectedPage.sections.length + 1,
|
||||
});
|
||||
|
||||
addSectionToPage(selectedPage.id, newSection);
|
||||
resetSectionForm();
|
||||
toast.success(`"${template.template_name}" 템플릿이 페이지에 연결되었습니다!`);
|
||||
try {
|
||||
// 기존 섹션을 페이지에 연결 (entity_relationships에 레코드 추가)
|
||||
await linkSectionToPage(selectedPage.id, template.id, selectedPage.sections.length + 1);
|
||||
resetSectionForm();
|
||||
toast.success(`"${template.template_name}" 섹션이 페이지에 연결되었습니다!`);
|
||||
} catch (error) {
|
||||
console.error('섹션 연결 실패:', error);
|
||||
toast.error('섹션 연결에 실패했습니다. 다시 시도해주세요.');
|
||||
}
|
||||
};
|
||||
|
||||
// 섹션 제목 수정 시작
|
||||
@@ -173,30 +146,53 @@ export function useSectionManagement(): UseSectionManagementReturn {
|
||||
};
|
||||
|
||||
// 섹션 제목 저장
|
||||
const handleSaveSectionTitle = (selectedPage: ItemPage | undefined) => {
|
||||
const handleSaveSectionTitle = async (selectedPage: ItemPage | undefined) => {
|
||||
if (!selectedPage || !editingSectionId || !editingSectionTitle.trim()) {
|
||||
toast.error('하위섹션 제목을 입력해주세요');
|
||||
return;
|
||||
}
|
||||
|
||||
updateSection(editingSectionId, { section_name: editingSectionTitle });
|
||||
setEditingSectionId(null);
|
||||
setEditingSectionTitle('');
|
||||
toast.success('하위섹션 제목이 수정되었습니다 (저장 필요)');
|
||||
try {
|
||||
await updateSection(editingSectionId, { title: editingSectionTitle });
|
||||
setEditingSectionId(null);
|
||||
setEditingSectionTitle('');
|
||||
toast.success('섹션 제목이 수정되었습니다!');
|
||||
} catch (error) {
|
||||
console.error('섹션 제목 수정 실패:', error);
|
||||
toast.error('섹션 제목 수정에 실패했습니다.');
|
||||
}
|
||||
};
|
||||
|
||||
// 섹션 삭제
|
||||
const handleDeleteSection = (pageId: number, sectionId: number) => {
|
||||
// 섹션 연결 해제 (계층구조 탭용 - 페이지에서만 분리, 섹션 데이터는 유지)
|
||||
// 2025-11-26: EntityRelationship API 사용 (DELETE /pages/{pageId}/unlink-section/{sectionId})
|
||||
const handleUnlinkSection = async (pageId: number, sectionId: number) => {
|
||||
try {
|
||||
await unlinkSectionFromPage(pageId, sectionId);
|
||||
console.log('섹션 연결 해제 완료:', { pageId, sectionId });
|
||||
toast.success('섹션 연결이 해제되었습니다');
|
||||
} catch (error) {
|
||||
console.error('섹션 연결 해제 실패:', error);
|
||||
toast.error('섹션 연결 해제에 실패했습니다.');
|
||||
}
|
||||
};
|
||||
|
||||
// 섹션 삭제 (섹션 탭용 - 실제 데이터 삭제)
|
||||
const handleDeleteSection = async (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(sectionId);
|
||||
|
||||
console.log('섹션 삭제 완료:', {
|
||||
sectionId,
|
||||
removedFields: fieldIds.length
|
||||
});
|
||||
try {
|
||||
await deleteSection(sectionId);
|
||||
console.log('섹션 삭제 완료:', {
|
||||
sectionId,
|
||||
removedFields: fieldIds.length
|
||||
});
|
||||
toast.success('섹션이 삭제되었습니다!');
|
||||
} catch (error) {
|
||||
console.error('섹션 삭제 실패:', error);
|
||||
toast.error('섹션 삭제에 실패했습니다.');
|
||||
}
|
||||
};
|
||||
|
||||
// 섹션 확장/축소 토글
|
||||
@@ -240,6 +236,7 @@ export function useSectionManagement(): UseSectionManagementReturn {
|
||||
handleLinkTemplate,
|
||||
handleEditSectionTitle,
|
||||
handleSaveSectionTitle,
|
||||
handleUnlinkSection,
|
||||
handleDeleteSection,
|
||||
toggleSection,
|
||||
resetSectionForm,
|
||||
|
||||
Reference in New Issue
Block a user