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,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,
};
}