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:
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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';
|
|
||||||
@@ -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[]>>,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user