fix: 품목기준관리 타입 에러 및 무한 로딩 버그 수정

- 55개 타입 에러 수정 (MasterOption/UnitOption 타입 통일)
- 다이얼로그 컴포넌트 import 누락 수정 (15개)
- useInitialDataLoading 무한 로딩 버그 수정 (useRef 적용)
- 미사용 변수 _ prefix 처리
- 미사용 ItemMasterDialogs export 제거

🤖 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 16:17:25 +09:00
parent 1664599cd5
commit 028932d815
5 changed files with 66 additions and 53 deletions

View File

@@ -18,6 +18,23 @@ import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
// 다이얼로그 컴포넌트 import
import { TabManagementDialogs } from './ItemMasterDataManagement/dialogs/TabManagementDialogs';
import { OptionDialog } from './ItemMasterDataManagement/dialogs/OptionDialog';
import { ColumnManageDialog } from './ItemMasterDataManagement/dialogs/ColumnManageDialog';
import { PathEditDialog } from './ItemMasterDataManagement/dialogs/PathEditDialog';
import { PageDialog } from './ItemMasterDataManagement/dialogs/PageDialog';
import { SectionDialog } from './ItemMasterDataManagement/dialogs/SectionDialog';
import { FieldDialog } from './ItemMasterDataManagement/dialogs/FieldDialog';
import { FieldDrawer } from './ItemMasterDataManagement/dialogs/FieldDrawer';
import { ColumnDialog } from './ItemMasterDataManagement/dialogs/ColumnDialog';
import { MasterFieldDialog } from './ItemMasterDataManagement/dialogs/MasterFieldDialog';
import { SectionTemplateDialog } from './ItemMasterDataManagement/dialogs/SectionTemplateDialog';
import { TemplateFieldDialog } from './ItemMasterDataManagement/dialogs/TemplateFieldDialog';
import { LoadTemplateDialog } from './ItemMasterDataManagement/dialogs/LoadTemplateDialog';
import { ImportSectionDialog } from './ItemMasterDataManagement/dialogs/ImportSectionDialog';
import { ImportFieldDialog } from './ItemMasterDataManagement/dialogs/ImportFieldDialog';
// 커스텀 훅 import // 커스텀 훅 import
import { import {
usePageManagement, usePageManagement,
@@ -54,30 +71,30 @@ const INPUT_TYPE_OPTIONS = [
export function ItemMasterDataManagement() { export function ItemMasterDataManagement() {
const { const {
itemPages, itemPages,
loadItemPages, loadItemPages: _loadItemPages,
updateItemPage, updateItemPage,
deleteItemPage, deleteItemPage: _deleteItemPage,
updateSection, updateSection,
deleteSection, deleteSection: _deleteSection,
reorderFields, reorderFields: _reorderFields,
itemMasterFields, itemMasterFields,
loadItemMasterFields, loadItemMasterFields: _loadItemMasterFields,
sectionTemplates, sectionTemplates,
loadSectionTemplates, loadSectionTemplates: _loadSectionTemplates,
resetAllData, resetAllData: _resetAllData,
// 2025-11-26 추가: 독립 엔티티 관리 // 2025-11-26 추가: 독립 엔티티 관리
independentSections, independentSections,
loadIndependentSections, loadIndependentSections: _loadIndependentSections,
loadIndependentFields, loadIndependentFields: _loadIndependentFields,
refreshIndependentSections, refreshIndependentSections,
refreshIndependentFields, refreshIndependentFields,
linkSectionToPage, linkSectionToPage: _linkSectionToPage,
linkFieldToSection, linkFieldToSection: _linkFieldToSection,
unlinkFieldFromSection, unlinkFieldFromSection: _unlinkFieldFromSection,
getSectionUsage, getSectionUsage,
getFieldUsage, getFieldUsage,
cloneSection, cloneSection: _cloneSection,
reorderSections, reorderSections: _reorderSections,
// 2025-11-27 추가: BOM 항목 API 함수 // 2025-11-27 추가: BOM 항목 API 함수
addBOMItem, addBOMItem,
updateBOMItem, updateBOMItem,
@@ -368,10 +385,10 @@ export function ItemMasterDataManagement() {
// 2025-12-24: 신규 훅에서 가져온 핸들러 래퍼 // 2025-12-24: 신규 훅에서 가져온 핸들러 래퍼
const handleImportSection = async () => handleImportSectionFromHook(selectedPageId); const handleImportSection = async () => handleImportSectionFromHook(selectedPageId);
const handleImportField = async () => handleImportFieldFromHook(selectedPage); const handleImportField = async () => handleImportFieldFromHook(selectedPage ?? null);
const moveSection = async (dragIndex: number, hoverIndex: number) => moveSectionFromHook(selectedPage, dragIndex, hoverIndex); const moveSection = async (dragIndex: number, hoverIndex: number) => moveSectionFromHook(selectedPage ?? null, dragIndex, hoverIndex);
const moveField = async (sectionId: number, dragFieldId: number, hoverFieldId: number) => moveFieldFromHook(selectedPage, sectionId, dragFieldId, hoverFieldId); const moveField = async (sectionId: number, dragFieldId: number, hoverFieldId: number) => moveFieldFromHook(selectedPage ?? null, sectionId, dragFieldId, hoverFieldId);
const handleResetAllData = () => handleResetAllDataFromHook( const _handleResetAllData = () => handleResetAllDataFromHook(
setUnitOptions, setUnitOptions,
setMaterialOptions, setMaterialOptions,
setSurfaceTreatmentOptions, setSurfaceTreatmentOptions,

View File

@@ -5,34 +5,14 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/com
import { Badge } from '@/components/ui/badge'; import { Badge } from '@/components/ui/badge';
import { Plus, Trash2, Settings, Package } from 'lucide-react'; import { Plus, Trash2, Settings, Package } from 'lucide-react';
import type { ItemMasterField } from '@/contexts/ItemMasterContext'; import type { ItemMasterField } from '@/contexts/ItemMasterContext';
import type { MasterOption, OptionColumn } from '../types';
import type { AttributeSubTab } from '../hooks/useTabManagement';
// 타입 정의 // UnitOption은 MasterOption으로 대체
export interface UnitOption { export type UnitOption = MasterOption;
id: string;
value: string;
label: string;
inputType?: string;
required?: boolean;
placeholder?: string;
defaultValue?: string;
options?: string[];
columnValues?: Record<string, string>;
}
export interface AttributeSubTab { // AttributeColumn은 OptionColumn으로 대체
id: string; export type AttributeColumn = OptionColumn;
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 getInputTypeLabel = (inputType: string | undefined): string => {
@@ -68,7 +48,7 @@ interface AttributeTabContentProps {
setManagingColumnType: (type: string) => void; setManagingColumnType: (type: string) => void;
setNewColumnName: (name: string) => void; setNewColumnName: (name: string) => void;
setNewColumnKey: (key: string) => void; setNewColumnKey: (key: string) => void;
setNewColumnType: (type: string) => void; setNewColumnType: (type: 'text' | 'number') => void;
setNewColumnRequired: (required: boolean) => void; setNewColumnRequired: (required: boolean) => void;
handleDeleteOption: (type: string, id: string) => void; handleDeleteOption: (type: string, id: string) => void;
} }

View File

@@ -3,5 +3,4 @@ export { DraggableField } from './DraggableField';
// 2025-12-24: Phase 2 UI 컴포넌트 분리 // 2025-12-24: Phase 2 UI 컴포넌트 분리
export { AttributeTabContent } from './AttributeTabContent'; export { AttributeTabContent } from './AttributeTabContent';
export { ItemMasterDialogs } from './ItemMasterDialogs'; // ItemMasterDialogs는 props가 너무 많아 사용하지 않음 (2025-12-24 결정)
export type { ItemMasterDialogsProps } from './ItemMasterDialogs';

View File

@@ -5,7 +5,12 @@ import { useItemMaster } from '@/contexts/ItemMasterContext';
import { toast } from 'sonner'; import { toast } from 'sonner';
import type { ItemPage, BOMItem } from '@/contexts/ItemMasterContext'; import type { ItemPage, BOMItem } from '@/contexts/ItemMasterContext';
import type { CustomTab, AttributeSubTab } from './useTabManagement'; import type { CustomTab, AttributeSubTab } from './useTabManagement';
import type { UnitOption, MaterialOption, SurfaceTreatmentOption } from './useAttributeManagement'; import type { MasterOption, OptionColumn } from '../types';
// 타입 alias (기존 호환성)
type UnitOption = MasterOption;
type MaterialOption = MasterOption;
type SurfaceTreatmentOption = MasterOption;
export interface UseDeleteManagementReturn { export interface UseDeleteManagementReturn {
handleDeletePage: (pageId: number) => void; handleDeletePage: (pageId: number) => void;
@@ -16,7 +21,7 @@ export interface UseDeleteManagementReturn {
setMaterialOptions: React.Dispatch<React.SetStateAction<MaterialOption[]>>, setMaterialOptions: React.Dispatch<React.SetStateAction<MaterialOption[]>>,
setSurfaceTreatmentOptions: React.Dispatch<React.SetStateAction<SurfaceTreatmentOption[]>>, setSurfaceTreatmentOptions: React.Dispatch<React.SetStateAction<SurfaceTreatmentOption[]>>,
setCustomAttributeOptions: React.Dispatch<React.SetStateAction<Record<string, UnitOption[]>>>, 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 }[]>>>, setAttributeColumns: React.Dispatch<React.SetStateAction<Record<string, OptionColumn[]>>>,
setBomItems: React.Dispatch<React.SetStateAction<BOMItem[]>>, setBomItems: React.Dispatch<React.SetStateAction<BOMItem[]>>,
setCustomTabs: React.Dispatch<React.SetStateAction<CustomTab[]>>, setCustomTabs: React.Dispatch<React.SetStateAction<CustomTab[]>>,
setAttributeSubTabs: React.Dispatch<React.SetStateAction<AttributeSubTab[]>>, setAttributeSubTabs: React.Dispatch<React.SetStateAction<AttributeSubTab[]>>,
@@ -70,7 +75,7 @@ export function useDeleteManagement({ itemPages }: UseDeleteManagementProps): Us
setMaterialOptions: React.Dispatch<React.SetStateAction<MaterialOption[]>>, setMaterialOptions: React.Dispatch<React.SetStateAction<MaterialOption[]>>,
setSurfaceTreatmentOptions: React.Dispatch<React.SetStateAction<SurfaceTreatmentOption[]>>, setSurfaceTreatmentOptions: React.Dispatch<React.SetStateAction<SurfaceTreatmentOption[]>>,
setCustomAttributeOptions: React.Dispatch<React.SetStateAction<Record<string, UnitOption[]>>>, 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 }[]>>>, setAttributeColumns: React.Dispatch<React.SetStateAction<Record<string, OptionColumn[]>>>,
setBomItems: React.Dispatch<React.SetStateAction<BOMItem[]>>, setBomItems: React.Dispatch<React.SetStateAction<BOMItem[]>>,
setCustomTabs: React.Dispatch<React.SetStateAction<CustomTab[]>>, setCustomTabs: React.Dispatch<React.SetStateAction<CustomTab[]>>,
setAttributeSubTabs: React.Dispatch<React.SetStateAction<AttributeSubTab[]>>, setAttributeSubTabs: React.Dispatch<React.SetStateAction<AttributeSubTab[]>>,

View File

@@ -1,6 +1,6 @@
'use client'; 'use client';
import { useState, useEffect, useCallback } from 'react'; import { useState, useEffect, useCallback, useRef } from 'react';
import { useItemMaster } from '@/contexts/ItemMasterContext'; import { useItemMaster } from '@/contexts/ItemMasterContext';
import { itemMasterApi } from '@/lib/api/item-master'; import { itemMasterApi } from '@/lib/api/item-master';
import { getErrorMessage, ApiError } from '@/lib/api/error-handler'; import { getErrorMessage, ApiError } from '@/lib/api/error-handler';
@@ -15,7 +15,10 @@ import {
} from '@/lib/api/transformers'; } from '@/lib/api/transformers';
import { toast } from 'sonner'; import { toast } from 'sonner';
import type { CustomTab } from './useTabManagement'; import type { CustomTab } from './useTabManagement';
import type { UnitOption } from './useAttributeManagement'; import type { MasterOption } from '../types';
// 타입 alias
type UnitOption = MasterOption;
export interface UseInitialDataLoadingReturn { export interface UseInitialDataLoadingReturn {
isInitialLoading: boolean; isInitialLoading: boolean;
@@ -43,6 +46,9 @@ export function useInitialDataLoading({
const [isInitialLoading, setIsInitialLoading] = useState(true); const [isInitialLoading, setIsInitialLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
// 초기 로딩이 이미 실행되었는지 추적하는 ref
const hasInitialLoadRun = useRef(false);
const loadInitialData = useCallback(async () => { const loadInitialData = useCallback(async () => {
try { try {
setIsInitialLoading(true); setIsInitialLoading(true);
@@ -138,9 +144,15 @@ export function useInitialDataLoading({
setUnitOptions, setUnitOptions,
]); ]);
// 초기 로딩은 한 번만 실행 (의존성 배열의 함수들이 불안정해도 무한 루프 방지)
useEffect(() => { useEffect(() => {
if (hasInitialLoadRun.current) {
return;
}
hasInitialLoadRun.current = true;
loadInitialData(); loadInitialData();
}, [loadInitialData]); // eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return { return {
isInitialLoading, isInitialLoading,