diff --git a/src/app/[locale]/(protected)/items-management-test/components/HierarchyTab.tsx b/src/app/[locale]/(protected)/items-management-test/components/HierarchyTab.tsx index c66e8831..73f84af9 100644 --- a/src/app/[locale]/(protected)/items-management-test/components/HierarchyTab.tsx +++ b/src/app/[locale]/(protected)/items-management-test/components/HierarchyTab.tsx @@ -150,6 +150,16 @@ export function HierarchyTab() { .map((id) => entities.sections[id]) .filter(Boolean) || []; + // ๐Ÿ” DEBUG: ์ƒํƒœ ๋ณ€๊ฒฝ ์ถ”์  + console.log('[HierarchyTab] ๋ Œ๋”๋ง:', { + selectedPageId, + selectedPageName: selectedPage?.page_name, + sectionIds: selectedPage?.sectionIds, + pageSectionsCount: pageSections.length, + entitiesPagesCount: Object.keys(entities.pages).length, + entitiesSectionsCount: Object.keys(entities.sections).length, + }); + // ์„น์…˜ ์ ‘ํž˜ ํ† ๊ธ€ const toggleSection = (sectionId: number) => { setCollapsedSections((prev) => ({ diff --git a/src/components/items/ItemMasterDataManagement.tsx b/src/components/items/ItemMasterDataManagement.tsx index b984357c..06dc106e 100644 --- a/src/components/items/ItemMasterDataManagement.tsx +++ b/src/components/items/ItemMasterDataManagement.tsx @@ -51,6 +51,9 @@ import { useDeleteManagement, } from './ItemMasterDataManagement/hooks'; +// ์—๋Ÿฌ ์•Œ๋ฆผ Context +import { ErrorAlertProvider } from './ItemMasterDataManagement/contexts'; + const ITEM_TYPE_OPTIONS = [ { value: 'FG', label: '์ œํ’ˆ (FG)' }, { value: 'PT', label: '๋ถ€ํ’ˆ (PT)' }, @@ -68,7 +71,17 @@ const INPUT_TYPE_OPTIONS = [ { value: 'textarea', label: 'ํ…์ŠคํŠธ์˜์—ญ' } ]; +// Wrapper ์ปดํฌ๋„ŒํŠธ: ErrorAlertProvider๋ฅผ ๋จผ์ € ์ œ๊ณต export function ItemMasterDataManagement() { + return ( + + + + ); +} + +// ์‹ค์ œ ๋กœ์ง์„ ๋‹ด๋Š” ๋‚ด๋ถ€ ์ปดํฌ๋„ŒํŠธ +function ItemMasterDataManagementContent() { const { itemPages, loadItemPages: _loadItemPages, diff --git a/src/components/items/ItemMasterDataManagement/components/ErrorAlertDialog.tsx b/src/components/items/ItemMasterDataManagement/components/ErrorAlertDialog.tsx new file mode 100644 index 00000000..8c50f38c --- /dev/null +++ b/src/components/items/ItemMasterDataManagement/components/ErrorAlertDialog.tsx @@ -0,0 +1,51 @@ +'use client'; + +import { + AlertDialog, + AlertDialogAction, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from '@/components/ui/alert-dialog'; +import { AlertCircle } from 'lucide-react'; + +interface ErrorAlertDialogProps { + open: boolean; + onClose: () => void; + title?: string; + message: string; +} + +/** + * ์—๋Ÿฌ ์•Œ๋ฆผ ๋‹ค์ด์–ผ๋กœ๊ทธ ์ปดํฌ๋„ŒํŠธ + * 422 ValidationException ๋“ฑ์˜ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ํ‘œ์‹œ + */ +export function ErrorAlertDialog({ + open, + onClose, + title = '์˜ค๋ฅ˜', + message, +}: ErrorAlertDialogProps) { + return ( + !isOpen && onClose()}> + + + + + {title} + + + {message} + + + + + ํ™•์ธ + + + + + ); +} \ No newline at end of file diff --git a/src/components/items/ItemMasterDataManagement/contexts/ErrorAlertContext.tsx b/src/components/items/ItemMasterDataManagement/contexts/ErrorAlertContext.tsx new file mode 100644 index 00000000..3505b26b --- /dev/null +++ b/src/components/items/ItemMasterDataManagement/contexts/ErrorAlertContext.tsx @@ -0,0 +1,93 @@ +'use client'; + +import { createContext, useContext, useState, useCallback, ReactNode } from 'react'; +import { + AlertDialog, + AlertDialogAction, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from '@/components/ui/alert-dialog'; +import { AlertCircle } from 'lucide-react'; + +interface ErrorAlertState { + open: boolean; + title: string; + message: string; +} + +interface ErrorAlertContextType { + showErrorAlert: (message: string, title?: string) => void; +} + +const ErrorAlertContext = createContext(null); + +/** + * ์—๋Ÿฌ ์•Œ๋ฆผ Context ์‚ฌ์šฉ ํ›… + */ +export function useErrorAlert() { + const context = useContext(ErrorAlertContext); + if (!context) { + throw new Error('useErrorAlert must be used within ErrorAlertProvider'); + } + return context; +} + +interface ErrorAlertProviderProps { + children: ReactNode; +} + +/** + * ์—๋Ÿฌ ์•Œ๋ฆผ Provider + * ItemMasterDataManagement ์ปดํฌ๋„ŒํŠธ์—์„œ ์‚ฌ์šฉ + */ +export function ErrorAlertProvider({ children }: ErrorAlertProviderProps) { + const [errorAlert, setErrorAlert] = useState({ + open: false, + title: '์˜ค๋ฅ˜', + message: '', + }); + + const showErrorAlert = useCallback((message: string, title: string = '์˜ค๋ฅ˜') => { + setErrorAlert({ + open: true, + title, + message, + }); + }, []); + + const closeErrorAlert = useCallback(() => { + setErrorAlert(prev => ({ + ...prev, + open: false, + })); + }, []); + + return ( + + {children} + + {/* ์—๋Ÿฌ ์•Œ๋ฆผ ๋‹ค์ด์–ผ๋กœ๊ทธ */} + !isOpen && closeErrorAlert()}> + + + + + {errorAlert.title} + + + {errorAlert.message} + + + + + ํ™•์ธ + + + + + + ); +} diff --git a/src/components/items/ItemMasterDataManagement/contexts/index.ts b/src/components/items/ItemMasterDataManagement/contexts/index.ts new file mode 100644 index 00000000..1f705b8f --- /dev/null +++ b/src/components/items/ItemMasterDataManagement/contexts/index.ts @@ -0,0 +1 @@ +export { ErrorAlertProvider, useErrorAlert } from './ErrorAlertContext'; \ No newline at end of file diff --git a/src/components/items/ItemMasterDataManagement/hooks/useErrorAlert.ts b/src/components/items/ItemMasterDataManagement/hooks/useErrorAlert.ts new file mode 100644 index 00000000..179a921d --- /dev/null +++ b/src/components/items/ItemMasterDataManagement/hooks/useErrorAlert.ts @@ -0,0 +1,48 @@ +'use client'; + +import { useState, useCallback } from 'react'; + +export interface ErrorAlertState { + open: boolean; + title: string; + message: string; +} + +export interface UseErrorAlertReturn { + errorAlert: ErrorAlertState; + showErrorAlert: (message: string, title?: string) => void; + closeErrorAlert: () => void; +} + +/** + * ์—๋Ÿฌ ์•Œ๋ฆผ ๋‹ค์ด์–ผ๋กœ๊ทธ ์ƒํƒœ ๊ด€๋ฆฌ ํ›… + * AlertDialog๋กœ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ํ‘œ์‹œํ•  ๋•Œ ์‚ฌ์šฉ + */ +export function useErrorAlert(): UseErrorAlertReturn { + const [errorAlert, setErrorAlert] = useState({ + open: false, + title: '์˜ค๋ฅ˜', + message: '', + }); + + const showErrorAlert = useCallback((message: string, title: string = '์˜ค๋ฅ˜') => { + setErrorAlert({ + open: true, + title, + message, + }); + }, []); + + const closeErrorAlert = useCallback(() => { + setErrorAlert(prev => ({ + ...prev, + open: false, + })); + }, []); + + return { + errorAlert, + showErrorAlert, + closeErrorAlert, + }; +} \ No newline at end of file diff --git a/src/components/items/ItemMasterDataManagement/hooks/useFieldManagement.ts b/src/components/items/ItemMasterDataManagement/hooks/useFieldManagement.ts index 53aaab9f..36e700f3 100644 --- a/src/components/items/ItemMasterDataManagement/hooks/useFieldManagement.ts +++ b/src/components/items/ItemMasterDataManagement/hooks/useFieldManagement.ts @@ -3,9 +3,11 @@ import { useState, useEffect } from 'react'; import { toast } from 'sonner'; import { useItemMaster } from '@/contexts/ItemMasterContext'; +import { useErrorAlert } from '../contexts'; import type { ItemPage, ItemField, ItemMasterField, FieldDisplayCondition } from '@/contexts/ItemMasterContext'; import { type ConditionalFieldConfig } from '../components/ConditionalDisplayUI'; import { fieldService } from '../services'; +import { ApiError } from '@/lib/api/error-handler'; export interface UseFieldManagementReturn { // ๋‹ค์ด์–ผ๋กœ๊ทธ ์ƒํƒœ @@ -79,6 +81,9 @@ export function useFieldManagement(): UseFieldManagementReturn { updateItemMasterField, } = useItemMaster(); + // ์—๋Ÿฌ ์•Œ๋ฆผ (AlertDialog๋กœ ํ‘œ์‹œ) + const { showErrorAlert } = useErrorAlert(); + // ๋‹ค์ด์–ผ๋กœ๊ทธ ์ƒํƒœ const [isFieldDialogOpen, setIsFieldDialogOpen] = useState(false); const [selectedSectionForField, setSelectedSectionForField] = useState(null); @@ -238,7 +243,23 @@ export function useFieldManagement(): UseFieldManagementReturn { resetFieldForm(); } catch (error) { console.error('ํ•„๋“œ ์ฒ˜๋ฆฌ ์‹คํŒจ:', error); - toast.error('ํ•ญ๋ชฉ ์ฒ˜๋ฆฌ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค'); + + // 422 ValidationException ์ƒ์„ธ ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ (field_key ์ค‘๋ณต/์˜ˆ์•ฝ์–ด, field_name ์ค‘๋ณต ๋“ฑ) + if (error instanceof ApiError) { + console.log('๐Ÿ” ApiError.errors:', error.errors); // ๋””๋ฒ„๊น…์šฉ + + // errors ๊ฐ์ฒด์—์„œ ์ฒซ ๋ฒˆ์งธ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ์ถ”์ถœ โ†’ AlertDialog๋กœ ํ‘œ์‹œ + 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('ํ•ญ๋ชฉ ์ฒ˜๋ฆฌ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค', '์˜ค๋ฅ˜'); + } } }; diff --git a/src/components/items/ItemMasterDataManagement/hooks/useMasterFieldManagement.ts b/src/components/items/ItemMasterDataManagement/hooks/useMasterFieldManagement.ts index 256ff94c..5fff123d 100644 --- a/src/components/items/ItemMasterDataManagement/hooks/useMasterFieldManagement.ts +++ b/src/components/items/ItemMasterDataManagement/hooks/useMasterFieldManagement.ts @@ -3,8 +3,10 @@ import { useState } from 'react'; import { toast } from 'sonner'; import { useItemMaster } from '@/contexts/ItemMasterContext'; +import { useErrorAlert } from '../contexts'; import type { ItemMasterField } from '@/contexts/ItemMasterContext'; import { masterFieldService } from '../services'; +import { ApiError } from '@/lib/api/error-handler'; /** * @deprecated 2025-11-27: item_fields๋กœ ํ†ตํ•ฉ๋จ. @@ -44,10 +46,10 @@ export interface UseMasterFieldManagementReturn { setNewMasterFieldColumnNames: React.Dispatch>; // ํ•ธ๋“ค๋Ÿฌ - handleAddMasterField: () => void; + handleAddMasterField: () => Promise; handleEditMasterField: (field: ItemMasterField) => void; - handleUpdateMasterField: () => void; - handleDeleteMasterField: (id: number) => void; + handleUpdateMasterField: () => Promise; + handleDeleteMasterField: (id: number) => Promise; resetMasterFieldForm: () => void; } @@ -59,6 +61,9 @@ export function useMasterFieldManagement(): UseMasterFieldManagementReturn { deleteItemMasterField, } = useItemMaster(); + // ์—๋Ÿฌ ์•Œ๋ฆผ (AlertDialog๋กœ ํ‘œ์‹œ) + const { showErrorAlert } = useErrorAlert(); + // ๋‹ค์ด์–ผ๋กœ๊ทธ ์ƒํƒœ const [isMasterFieldDialogOpen, setIsMasterFieldDialogOpen] = useState(false); const [editingMasterFieldId, setEditingMasterFieldId] = useState(null); @@ -77,7 +82,7 @@ export function useMasterFieldManagement(): UseMasterFieldManagementReturn { const [newMasterFieldColumnNames, setNewMasterFieldColumnNames] = useState(['์ปฌ๋Ÿผ1', '์ปฌ๋Ÿผ2']); // ๋งˆ์Šคํ„ฐ ํ•ญ๋ชฉ ์ถ”๊ฐ€ - const handleAddMasterField = () => { + const handleAddMasterField = async () => { if (!newMasterFieldName.trim() || !newMasterFieldKey.trim()) { toast.error('ํ•ญ๋ชฉ๋ช…๊ณผ ํ•„๋“œ ํ‚ค๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”'); return; @@ -106,9 +111,30 @@ export function useMasterFieldManagement(): UseMasterFieldManagementReturn { }, }; - addItemMasterField(newMasterFieldData as any); - resetMasterFieldForm(); - toast.success('ํ•ญ๋ชฉ์ด ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค'); + try { + await addItemMasterField(newMasterFieldData as any); + resetMasterFieldForm(); + toast.success('ํ•ญ๋ชฉ์ด ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค'); + } catch (error) { + console.error('ํ•ญ๋ชฉ ์ถ”๊ฐ€ ์‹คํŒจ:', error); + + // 422 ValidationException ์ƒ์„ธ ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ (field_key ์ค‘๋ณต/์˜ˆ์•ฝ์–ด) โ†’ AlertDialog๋กœ ํ‘œ์‹œ + if (error instanceof ApiError) { + console.log('๐Ÿ” ApiError.errors:', error.errors); // ๋””๋ฒ„๊น…์šฉ + + // 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('ํ•ญ๋ชฉ ์ถ”๊ฐ€์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค', '์˜ค๋ฅ˜'); + } + } }; // ๋งˆ์Šคํ„ฐ ํ•ญ๋ชฉ ์ˆ˜์ • ์‹œ์ž‘ @@ -134,7 +160,7 @@ export function useMasterFieldManagement(): UseMasterFieldManagementReturn { }; // ๋งˆ์Šคํ„ฐ ํ•ญ๋ชฉ ์—…๋ฐ์ดํŠธ - const handleUpdateMasterField = () => { + const handleUpdateMasterField = async () => { if (!editingMasterFieldId || !newMasterFieldName.trim() || !newMasterFieldKey.trim()) { toast.error('ํ•ญ๋ชฉ๋ช…๊ณผ ํ•„๋“œ ํ‚ค๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”'); return; @@ -159,16 +185,47 @@ export function useMasterFieldManagement(): UseMasterFieldManagementReturn { }, }; - updateItemMasterField(editingMasterFieldId, updateData); - resetMasterFieldForm(); - toast.success('ํ•ญ๋ชฉ์ด ์ˆ˜์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค'); + try { + await updateItemMasterField(editingMasterFieldId, updateData); + resetMasterFieldForm(); + toast.success('ํ•ญ๋ชฉ์ด ์ˆ˜์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค'); + } catch (error) { + console.error('ํ•ญ๋ชฉ ์ˆ˜์ • ์‹คํŒจ:', error); + + // 422 ValidationException ์ƒ์„ธ ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ (field_key ์ค‘๋ณต/์˜ˆ์•ฝ์–ด, field_name ์ค‘๋ณต ๋“ฑ) โ†’ AlertDialog๋กœ ํ‘œ์‹œ + if (error instanceof ApiError) { + console.log('๐Ÿ” ApiError.errors:', error.errors); // ๋””๋ฒ„๊น…์šฉ + + // 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-27: ๋งˆ์Šคํ„ฐ ํ•ญ๋ชฉ โ†’ ํ•ญ๋ชฉ์œผ๋กœ ํ†ตํ•ฉ) - const handleDeleteMasterField = (id: number) => { + const handleDeleteMasterField = async (id: number) => { if (confirm('์ด ํ•ญ๋ชฉ์„ ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?\n(์„น์…˜์—์„œ ์‚ฌ์šฉ ์ค‘์ธ ๊ฒฝ์šฐ ์—ฐ๊ฒฐ๋„ ํ•จ๊ป˜ ํ•ด์ œ๋ฉ๋‹ˆ๋‹ค)')) { - deleteItemMasterField(id); - toast.success('ํ•ญ๋ชฉ์ด ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค'); + try { + await deleteItemMasterField(id); + toast.success('ํ•ญ๋ชฉ์ด ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค'); + } catch (error) { + console.error('ํ•ญ๋ชฉ ์‚ญ์ œ ์‹คํŒจ:', error); + + if (error instanceof ApiError) { + toast.error(error.message); + } else { + toast.error('ํ•ญ๋ชฉ ์‚ญ์ œ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค'); + } + } } }; diff --git a/src/components/items/ItemMasterDataManagement/hooks/useTemplateManagement.ts b/src/components/items/ItemMasterDataManagement/hooks/useTemplateManagement.ts index c083e2c5..b2de6f90 100644 --- a/src/components/items/ItemMasterDataManagement/hooks/useTemplateManagement.ts +++ b/src/components/items/ItemMasterDataManagement/hooks/useTemplateManagement.ts @@ -3,8 +3,10 @@ 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'; export interface UseTemplateManagementReturn { // ์„น์…˜ ํ…œํ”Œ๋ฆฟ ๋‹ค์ด์–ผ๋กœ๊ทธ ์ƒํƒœ @@ -112,6 +114,9 @@ export function useTemplateManagement(): UseTemplateManagementReturn { deleteBOMItem, } = useItemMaster(); + // ์—๋Ÿฌ ์•Œ๋ฆผ (AlertDialog๋กœ ํ‘œ์‹œ) + const { showErrorAlert } = useErrorAlert(); + // ์„น์…˜ ํ…œํ”Œ๋ฆฟ ๋‹ค์ด์–ผ๋กœ๊ทธ ์ƒํƒœ const [isSectionTemplateDialogOpen, setIsSectionTemplateDialogOpen] = useState(false); const [editingSectionTemplateId, setEditingSectionTemplateId] = useState(null); @@ -348,7 +353,23 @@ export function useTemplateManagement(): UseTemplateManagementReturn { resetTemplateFieldForm(); } catch (error) { console.error('ํ•ญ๋ชฉ ์ฒ˜๋ฆฌ ์‹คํŒจ:', error); - toast.error('ํ•ญ๋ชฉ ์ฒ˜๋ฆฌ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค'); + + // 422 ValidationException ์ƒ์„ธ ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ (field_key ์ค‘๋ณต/์˜ˆ์•ฝ์–ด, field_name ์ค‘๋ณต ๋“ฑ) โ†’ AlertDialog๋กœ ํ‘œ์‹œ + if (error instanceof ApiError) { + console.log('๐Ÿ” ApiError.errors:', error.errors); // ๋””๋ฒ„๊น…์šฉ + + // 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('ํ•ญ๋ชฉ ์ฒ˜๋ฆฌ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค', '์˜ค๋ฅ˜'); + } } }; diff --git a/src/contexts/ItemMasterContext.tsx b/src/contexts/ItemMasterContext.tsx index 54b93912..60053a98 100644 --- a/src/contexts/ItemMasterContext.tsx +++ b/src/contexts/ItemMasterContext.tsx @@ -22,392 +22,56 @@ import type { FieldUsageResponse, } from '@/types/item-master-api'; -// ===== Type Definitions ===== +// ํƒ€์ž… ์ •์˜๋Š” ๋ณ„๋„ ํŒŒ์ผ์—์„œ import +export type { + BendingDetail, + BOMLine, + SpecificationMaster, + MaterialItemName, + ItemRevision, + ItemMaster, + ItemCategory, + ItemUnit, + ItemMaterial, + SurfaceTreatment, + PartTypeOption, + PartUsageOption, + GuideRailOption, + ItemFieldProperty, + ItemMasterField, + FieldDisplayCondition, + ItemField, + BOMItem, + ItemSection, + ItemPage, + TemplateField, + SectionTemplate, +} from '@/types/item-master.types'; -// ์ „๊ฐœ๋„ ์ƒ์„ธ ์ •๋ณด -export interface BendingDetail { - id: string; - no: number; // ๋ฒˆํ˜ธ - input: number; // ์ž…๋ ฅ - elongation: number; // ์—ฐ์‹ ์œจ (๊ธฐ๋ณธ๊ฐ’ -1) - calculated: number; // ์—ฐ์‹ ์œจ ๊ณ„์‚ฐ ํ›„ - sum: number; // ํ•ฉ๊ณ„ - shaded: boolean; // ์Œ์˜ ์—ฌ๋ถ€ - aAngle?: number; // A๊ฐ -} - -// ๋ถ€ํ’ˆ๊ตฌ์„ฑํ‘œ(BOM, Bill of Materials) - ์ž์žฌ ๋ช…์„ธ์„œ -export interface BOMLine { - id: string; - childItemCode: string; // ๊ตฌ์„ฑ ํ’ˆ๋ชฉ ์ฝ”๋“œ - childItemName: string; // ๊ตฌ์„ฑ ํ’ˆ๋ชฉ๋ช… - quantity: number; // ๊ธฐ์ค€ ์ˆ˜๋Ÿ‰ - unit: string; // ๋‹จ์œ„ - unitPrice?: number; // ๋‹จ๊ฐ€ - quantityFormula?: string; // ์ˆ˜๋Ÿ‰ ๊ณ„์‚ฐ์‹ (์˜ˆ: "W * 2", "H + 100") - note?: string; // ๋น„๊ณ  - // ์ ˆ๊ณกํ’ˆ ๊ด€๋ จ (ํ•˜์œ„ ์ ˆ๊ณก ๋ถ€ํ’ˆ์šฉ) - isBending?: boolean; - bendingDiagram?: string; // ์ „๊ฐœ๋„ ์ด๋ฏธ์ง€ URL - bendingDetails?: BendingDetail[]; // ์ „๊ฐœ๋„ ์ƒ์„ธ ๋ฐ์ดํ„ฐ -} - -// ๊ทœ๊ฒฉ ๋งˆ์Šคํ„ฐ (์›์ž์žฌ/๋ถ€์ž์žฌ์šฉ) -export interface SpecificationMaster { - id: string; - specificationCode: string; // ๊ทœ๊ฒฉ ์ฝ”๋“œ (์˜ˆ: 1.6T x 1219 x 2438) - itemType: 'RM' | 'SM'; // ์›์ž์žฌ | ๋ถ€์ž์žฌ - itemName?: string; // ํ’ˆ๋ชฉ๋ช… (์˜ˆ: SPHC-SD, SPCC-SD) - ํ’ˆ๋ชฉ๋ช…๋ณ„ ๊ทœ๊ฒฉ ํ•„ํ„ฐ๋ง์šฉ - fieldCount: '1' | '2' | '3'; // ๋„ˆ๋น„ ์ž…๋ ฅ ๊ฐœ์ˆ˜ - thickness: string; // ๋‘๊ป˜ - widthA: string; // ๋„ˆ๋น„A - widthB?: string; // ๋„ˆ๋น„B - widthC?: string; // ๋„ˆ๋น„C - length: string; // ๊ธธ์ด - description?: string; // ์„ค๋ช… - isActive: boolean; // ํ™œ์„ฑ ์—ฌ๋ถ€ - createdAt?: string; - updatedAt?: string; -} - -// ์›์ž์žฌ/๋ถ€์ž์žฌ ํ’ˆ๋ชฉ๋ช… ๋งˆ์Šคํ„ฐ -export interface MaterialItemName { - id: string; - itemType: 'RM' | 'SM'; // ์›์ž์žฌ | ๋ถ€์ž์žฌ - itemName: string; // ํ’ˆ๋ชฉ๋ช… (์˜ˆ: "SPHC-SD", "STS430") - category?: string; // ๋ถ„๋ฅ˜ (์˜ˆ: "๋ƒ‰์—ฐ", "์—ด์—ฐ", "์Šคํ…Œ์ธ๋ฆฌ์Šค") - description?: string; // ์„ค๋ช… - isActive: boolean; // ํ™œ์„ฑ ์—ฌ๋ถ€ - createdAt: string; - updatedAt?: string; -} - -// ํ’ˆ๋ชฉ ์ˆ˜์ • ์ด๋ ฅ -export interface ItemRevision { - revisionNumber: number; // ์ˆ˜์ • ์ฐจ์ˆ˜ (1์ฐจ, 2์ฐจ, 3์ฐจ...) - revisionDate: string; // ์ˆ˜์ •์ผ - revisionBy: string; // ์ˆ˜์ •์ž - revisionReason?: string; // ์ˆ˜์ • ์‚ฌ์œ  - previousData: any; // ์ด์ „ ๋ฒ„์ „์˜ ์ „์ฒด ๋ฐ์ดํ„ฐ -} - -// ํ’ˆ๋ชฉ ๋งˆ์Šคํ„ฐ -export interface ItemMaster { - id: string; - itemCode: string; - itemName: string; - itemType: 'FG' | 'PT' | 'SM' | 'RM' | 'CS'; // ์ œํ’ˆ, ๋ถ€ํ’ˆ, ๋ถ€์ž์žฌ, ์›์ž์žฌ, ์†Œ๋ชจํ’ˆ - productCategory?: 'SCREEN' | 'STEEL'; // ์ œํ’ˆ ์นดํ…Œ๊ณ ๋ฆฌ (์Šคํฌ๋ฆฐ/์ฒ ์žฌ) - partType?: 'ASSEMBLY' | 'BENDING' | 'PURCHASED'; // ๋ถ€ํ’ˆ ์œ ํ˜• (์กฐ๋ฆฝ/์ ˆ๊ณก/๊ตฌ๋งค) - partUsage?: 'GUIDE_RAIL' | 'BOTTOM_FINISH' | 'CASE' | 'DOOR' | 'BRACKET' | 'GENERAL'; // ๋ถ€ํ’ˆ ์šฉ๋„ - unit: string; - category1?: string; - category2?: string; - category3?: string; - specification?: string; - isVariableSize?: boolean; - isActive?: boolean; // ํ’ˆ๋ชฉ ํ™œ์„ฑ/๋น„ํ™œ์„ฑ (์ œํ’ˆ/๋ถ€ํ’ˆ/์›์ž์žฌ/๋ถ€์ž์žฌ๋งŒ ์‚ฌ์šฉ) - lotAbbreviation?: string; // ๋กœํŠธ ์•ฝ์ž (์ œํ’ˆ๋งŒ ์‚ฌ์šฉ) - purchasePrice?: number; - marginRate?: number; - processingCost?: number; - laborCost?: number; - installCost?: number; - salesPrice?: number; - safetyStock?: number; - leadTime?: number; - bom?: BOMLine[]; // ๋ถ€ํ’ˆ๊ตฌ์„ฑํ‘œ(BOM) - ์ž์žฌ ๋ช…์„ธ์„œ - bomCategories?: string[]; // ๊ฒฌ์ ์‚ฐ์ถœ์šฉ ์ƒ˜ํ”Œ ์ œํ’ˆ์˜ BOM ์นดํ…Œ๊ณ ๋ฆฌ (์˜ˆ: ['motor', 'guide-rail']) - - // ์ธ์ • ์ •๋ณด - certificationNumber?: string; // ์ธ์ •๋ฒˆํ˜ธ - certificationStartDate?: string; // ์ธ์ • ์œ ํšจ๊ธฐ๊ฐ„ ์‹œ์ž‘์ผ - certificationEndDate?: string; // ์ธ์ • ์œ ํšจ๊ธฐ๊ฐ„ ์ข…๋ฃŒ์ผ - specificationFile?: string; // ์‹œ๋ฐฉ์„œ ํŒŒ์ผ (Base64 ๋˜๋Š” URL) - specificationFileName?: string; // ์‹œ๋ฐฉ์„œ ํŒŒ์ผ๋ช… - certificationFile?: string; // ์ธ์ •์„œ ํŒŒ์ผ (Base64 ๋˜๋Š” URL) - certificationFileName?: string; // ์ธ์ •์„œ ํŒŒ์ผ๋ช… - note?: string; // ๋น„๊ณ  (์ œํ’ˆ๋งŒ ์‚ฌ์šฉ) - - // ์กฐ๋ฆฝ ๋ถ€ํ’ˆ ๊ด€๋ จ ํ•„๋“œ - installationType?: string; // ์„ค์น˜ ์œ ํ˜• (wall: ๋ฒฝ๋ฉดํ˜•, side: ์ธก๋ฉดํ˜•, steel: ์Šคํ‹ธ, iron: ์ฒ ์žฌ) - assemblyType?: string; // ์ข…๋ฅ˜ (M, T, C, D, S, U ๋“ฑ) - sideSpecWidth?: string; // ์ธก๋ฉด ๊ทœ๊ฒฉ ๊ฐ€๋กœ (mm) - sideSpecHeight?: string; // ์ธก๋ฉด ๊ทœ๊ฒฉ ์„ธ๋กœ (mm) - assemblyLength?: string; // ๊ธธ์ด (2438, 3000, 3500, 4000, 4300 ๋“ฑ) - - // ๊ฐ€์ด๋“œ๋ ˆ์ผ ๊ด€๋ จ ํ•„๋“œ - guideRailModelType?: string; // ๊ฐ€์ด๋“œ๋ ˆ์ผ ๋ชจ๋ธ ์œ ํ˜• - guideRailModel?: string; // ๊ฐ€์ด๋“œ๋ ˆ์ผ ๋ชจ๋ธ - - // ์ ˆ๊ณกํ’ˆ ๊ด€๋ จ (๋ถ€ํ’ˆ ์œ ํ˜•์ด BENDING์ธ ๊ฒฝ์šฐ) - bendingDiagram?: string; // ์ „๊ฐœ๋„ ์ด๋ฏธ์ง€ URL - bendingDetails?: BendingDetail[]; // ์ „๊ฐœ๋„ ์ƒ์„ธ ๋ฐ์ดํ„ฐ - material?: string; // ์žฌ์งˆ (EGI 1.55T, SUS 1.2T ๋“ฑ) - length?: string; // ๊ธธ์ด/๋ชฉํ•จ (mm) - - // ๋ฒ„์ „ ๊ด€๋ฆฌ - currentRevision: number; // ํ˜„์žฌ ์ฐจ์ˆ˜ (0 = ์ตœ์ดˆ, 1 = 1์ฐจ ์ˆ˜์ •...) - revisions?: ItemRevision[]; // ์ˆ˜์ • ์ด๋ ฅ - isFinal: boolean; // ์ตœ์ข… ํ™•์ • ์—ฌ๋ถ€ - finalizedDate?: string; // ์ตœ์ข… ํ™•์ •์ผ - finalizedBy?: string; // ์ตœ์ข… ํ™•์ •์ž - - createdAt: string; -} - -// ํ’ˆ๋ชฉ ๊ธฐ์ค€์ •๋ณด ๊ด€๋ฆฌ (Master Data) -export interface ItemCategory { - id: string; - categoryType: 'PRODUCT' | 'PART' | 'MATERIAL' | 'SUB_MATERIAL'; // ํ’ˆ๋ชฉ ๊ตฌ๋ถ„ - category1: string; // ๋Œ€๋ถ„๋ฅ˜ - category2?: string; // ์ค‘๋ถ„๋ฅ˜ - category3?: string; // ์†Œ๋ถ„๋ฅ˜ - code?: string; // ์ฝ”๋“œ (์ž๋™์ƒ์„ฑ ๋˜๋Š” ์ˆ˜๋™์ž…๋ ฅ) - description?: string; - isActive: boolean; - createdAt: string; - updatedAt?: string; -} - -export interface ItemUnit { - id: string; - unitCode: string; // ๋‹จ์œ„ ์ฝ”๋“œ (EA, SET, M, KG, L ๋“ฑ) - unitName: string; // ๋‹จ์œ„๋ช… - description?: string; - isActive: boolean; - createdAt: string; - updatedAt?: string; -} - -export interface ItemMaterial { - id: string; - materialCode: string; // ์žฌ์งˆ ์ฝ”๋“œ - materialName: string; // ์žฌ์งˆ๋ช… (EGI 1.55T, SUS 1.2T ๋“ฑ) - materialType: 'STEEL' | 'ALUMINUM' | 'PLASTIC' | 'OTHER'; // ์žฌ์งˆ ์œ ํ˜• - thickness?: string; // ๋‘๊ป˜ (1.2T, 1.6T ๋“ฑ) - description?: string; - isActive: boolean; - createdAt: string; - updatedAt?: string; -} - -export interface SurfaceTreatment { - id: string; - treatmentCode: string; // ์ฒ˜๋ฆฌ ์ฝ”๋“œ - treatmentName: string; // ์ฒ˜๋ฆฌ๋ช… (๋ฌด๋„์žฅ, ํŒŒ์šฐ๋”๋„์žฅ, ์•„๋…ธ๋‹ค์ด์ง• ๋“ฑ) - treatmentType: 'PAINTING' | 'COATING' | 'PLATING' | 'NONE'; // ์ฒ˜๋ฆฌ ์œ ํ˜• - description?: string; - isActive: boolean; - createdAt: string; - updatedAt?: string; -} - -export interface PartTypeOption { - id: string; - partType: 'ASSEMBLY' | 'BENDING' | 'PURCHASED'; // ๋ถ€ํ’ˆ ์œ ํ˜• - optionCode: string; // ์˜ต์…˜ ์ฝ”๋“œ - optionName: string; // ์˜ต์…˜๋ช… - description?: string; - isActive: boolean; - createdAt: string; - updatedAt?: string; -} - -export interface PartUsageOption { - id: string; - usageCode: string; // ์šฉ๋„ ์ฝ”๋“œ - usageName: string; // ์šฉ๋„๋ช… (๊ฐ€์ด๋“œ๋ ˆ์ผ, ํ•˜๋‹จ๋งˆ๊ฐ์žฌ, ์ผ€์ด์Šค ๋“ฑ) - description?: string; - isActive: boolean; - createdAt: string; - updatedAt?: string; -} - -export interface GuideRailOption { - id: string; - optionType: 'MODEL_TYPE' | 'MODEL' | 'CERTIFICATION' | 'SHAPE' | 'FINISH' | 'LENGTH'; // ์˜ต์…˜ ์œ ํ˜• - optionCode: string; // ์˜ต์…˜ ์ฝ”๋“œ - optionName: string; // ์˜ต์…˜๋ช… - parentOption?: string; // ์ƒ์œ„ ์˜ต์…˜ (์ข…์† ๊ด€๊ณ„) - description?: string; - isActive: boolean; - createdAt: string; - updatedAt?: string; -} - -// ===== ํ’ˆ๋ชฉ๊ธฐ์ค€๊ด€๋ฆฌ ๊ณ„์ธต๊ตฌ์กฐ ===== - -// ํ•ญ๋ชฉ ์†์„ฑ -export interface ItemFieldProperty { - id?: string; // ์†์„ฑ ID (properties ๋ฐฐ์—ด์—์„œ ์‚ฌ์šฉ) - key?: string; // ์†์„ฑ ํ‚ค (properties ๋ฐฐ์—ด์—์„œ ์‚ฌ์šฉ) - label?: string; // ์†์„ฑ ๋ผ๋ฒจ (properties ๋ฐฐ์—ด์—์„œ ์‚ฌ์šฉ) - type?: 'textbox' | 'dropdown' | 'checkbox' | 'number' | 'date' | 'textarea'; // ์†์„ฑ ํƒ€์ž… (properties ๋ฐฐ์—ด์—์„œ ์‚ฌ์šฉ) - inputType: 'textbox' | 'dropdown' | 'checkbox' | 'number' | 'date' | 'textarea' | 'section'; // ์ž…๋ ฅ๋ฐฉ์‹ - required: boolean; // ํ•„์ˆ˜ ์—ฌ๋ถ€ - row: number; // ํ–‰ ์œ„์น˜ - col: number; // ์—ด ์œ„์น˜ - options?: string[]; // ๋“œ๋กญ๋‹ค์šด ์˜ต์…˜ (์ž…๋ ฅ๋ฐฉ์‹์ด dropdown์ผ ๊ฒฝ์šฐ) - defaultValue?: string; // ๊ธฐ๋ณธ๊ฐ’ - placeholder?: string; // ํ”Œ๋ ˆ์ด์Šคํ™€๋” - multiColumn?: boolean; // ๋‹ค์ค‘ ์ปฌ๋Ÿผ ์‚ฌ์šฉ ์—ฌ๋ถ€ - columnCount?: number; // ์ปฌ๋Ÿผ ๊ฐœ์ˆ˜ - columnNames?: string[]; // ๊ฐ ์ปฌ๋Ÿผ์˜ ์ด๋ฆ„ -} - -// ํ•ญ๋ชฉ ๋งˆ์Šคํ„ฐ (์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ํ•ญ๋ชฉ ํ…œํ”Œ๋ฆฟ) - MasterFieldResponse์™€ ์ •ํ™•ํžˆ ์ผ์น˜ -export interface ItemMasterField { - id: number; - tenant_id: number; - field_name: string; - field_key?: string | null; // 2025-11-28: field_key ์ถ”๊ฐ€ (ํ˜•์‹: {ID}_{์‚ฌ์šฉ์ž์ž…๋ ฅ}) - field_type: 'textbox' | 'number' | 'dropdown' | 'checkbox' | 'date' | 'textarea'; // API์™€ ๋™์ผ - category: string | null; - description: string | null; - is_common: boolean; // ๊ณตํ†ต ํ•„๋“œ ์—ฌ๋ถ€ - is_required?: boolean; // ํ•„์ˆ˜ ์—ฌ๋ถ€ (API์—์„œ ๋ฐ˜ํ™˜) - default_value: string | null; // ๊ธฐ๋ณธ๊ฐ’ - options: Array<{ label: string; value: string }> | null; // dropdown ์˜ต์…˜ - validation_rules: Record | null; // ๊ฒ€์ฆ ๊ทœ์น™ - properties: Record | null; // ์ถ”๊ฐ€ ์†์„ฑ - created_by: number | null; - updated_by: number | null; - created_at: string; - updated_at: string; -} - -// ์กฐ๊ฑด๋ถ€ ํ‘œ์‹œ ์„ค์ • -export interface FieldDisplayCondition { - targetType: 'field' | 'section'; // ์กฐ๊ฑด ๋Œ€์ƒ ํƒ€์ž… - // ์ผ๋ฐ˜ํ•ญ๋ชฉ ์กฐ๊ฑด (์—ฌ๋Ÿฌ ๊ฐœ ๊ฐ€๋Šฅ) - fieldConditions?: Array<{ - fieldKey: string; // ์กฐ๊ฑด์ด ๋˜๋Š” ํ•„๋“œ์˜ ํ‚ค - expectedValue: string; // ์˜ˆ์ƒ๋˜๋Š” ๊ฐ’ - }>; - // ์„น์…˜ ์กฐ๊ฑด (์—ฌ๋Ÿฌ ๊ฐœ ๊ฐ€๋Šฅ) - sectionIds?: string[]; // ํ‘œ์‹œํ•  ์„น์…˜ ID ๋ฐฐ์—ด -} - -// ํ•ญ๋ชฉ (Field) - API ์‘๋‹ต ๊ตฌ์กฐ์— ๋งž์ถฐ ์ˆ˜์ • -export interface ItemField { - id: number; // ์„œ๋ฒ„ ์ƒ์„ฑ ID (string โ†’ number) - tenant_id?: number; // ๋ฐฑ์—”๋“œ์—์„œ ์ž๋™ ์ถ”๊ฐ€ - group_id?: number | null; // ๊ทธ๋ฃน ID (๋…๋ฆฝ ํ•„๋“œ์šฉ) - section_id: number | null; // ์™ธ๋ž˜ํ‚ค - ์„น์…˜ ID (๋…๋ฆฝ ํ•„๋“œ๋Š” null) - master_field_id?: number | null; // ๋งˆ์Šคํ„ฐ ํ•ญ๋ชฉ ID (๋งˆ์Šคํ„ฐ์—์„œ ๊ฐ€์ ธ์˜จ ๊ฒฝ์šฐ) - field_name: string; // ํ•ญ๋ชฉ๋ช… (name โ†’ field_name) - field_key?: string | null; // 2025-11-28: ํ•„๋“œ ํ‚ค (ํ˜•์‹: {ID}_{์‚ฌ์šฉ์ž์ž…๋ ฅ}, ๋ฐฑ์—”๋“œ์—์„œ ์ƒ์„ฑ) - field_type: 'textbox' | 'number' | 'dropdown' | 'checkbox' | 'date' | 'textarea'; // ํ•„๋“œ ํƒ€์ž… - order_no: number; // ํ•ญ๋ชฉ ์ˆœ์„œ (order โ†’ order_no, required) - is_required: boolean; // ํ•„์ˆ˜ ์—ฌ๋ถ€ - placeholder?: string | null; // ํ”Œ๋ ˆ์ด์Šคํ™€๋” - default_value?: string | null; // ๊ธฐ๋ณธ๊ฐ’ - display_condition?: Record | null; // ์กฐ๊ฑด๋ถ€ ํ‘œ์‹œ ์„ค์ • (displayCondition โ†’ display_condition) - validation_rules?: Record | null; // ๊ฒ€์ฆ ๊ทœ์น™ - options?: Array<{ label: string; value: string }> | null; // dropdown ์˜ต์…˜ - properties?: Record | null; // ์ถ”๊ฐ€ ์†์„ฑ - // 2025-11-28 ์ถ”๊ฐ€: ์ž ๊ธˆ ๊ธฐ๋Šฅ - is_locked?: boolean; // ์ž ๊ธˆ ์—ฌ๋ถ€ - locked_by?: number | null; // ์ž ๊ธˆ ์„ค์ •์ž - locked_at?: string | null; // ์ž ๊ธˆ ์‹œ๊ฐ„ - created_by?: number | null; // ์ƒ์„ฑ์ž ID ์ถ”๊ฐ€ - updated_by?: number | null; // ์ˆ˜์ •์ž ID ์ถ”๊ฐ€ - created_at: string; // ์ƒ์„ฑ์ผ (camelCase โ†’ snake_case) - updated_at: string; // ์ˆ˜์ •์ผ ์ถ”๊ฐ€ -} - -// BOM ์•„์ดํ…œ ํƒ€์ž… - API ์‘๋‹ต ๊ตฌ์กฐ์— ๋งž์ถฐ ์ˆ˜์ • -export interface BOMItem { - id: number; // ์„œ๋ฒ„ ์ƒ์„ฑ ID (string โ†’ number) - tenant_id?: number; // ๋ฐฑ์—”๋“œ์—์„œ ์ž๋™ ์ถ”๊ฐ€ - group_id?: number | null; // ๊ทธ๋ฃน ID (๋…๋ฆฝ BOM์šฉ) - section_id: number | null; // ์™ธ๋ž˜ํ‚ค - ์„น์…˜ ID (๋…๋ฆฝ BOM์€ null) - item_code?: string | null; // ํ’ˆ๋ชฉ ์ฝ”๋“œ (itemCode โ†’ item_code, optional) - item_name: string; // ํ’ˆ๋ชฉ๋ช… (itemName โ†’ item_name) - quantity: number; // ์ˆ˜๋Ÿ‰ - unit?: string | null; // ๋‹จ์œ„ (optional) - unit_price?: number | null; // ๋‹จ๊ฐ€ ์ถ”๊ฐ€ - total_price?: number | null; // ์ด์•ก ์ถ”๊ฐ€ - spec?: string | null; // ๊ทœ๊ฒฉ/์‚ฌ์–‘ ์ถ”๊ฐ€ - note?: string | null; // ๋น„๊ณ  (optional) - created_by?: number | null; // ์ƒ์„ฑ์ž ID ์ถ”๊ฐ€ - updated_by?: number | null; // ์ˆ˜์ •์ž ID ์ถ”๊ฐ€ - created_at: string; // ์ƒ์„ฑ์ผ (createdAt โ†’ created_at) - updated_at: string; // ์ˆ˜์ •์ผ ์ถ”๊ฐ€ -} - -// ์„น์…˜ (Section) - API ์‘๋‹ต ๊ตฌ์กฐ์— ๋งž์ถฐ ์ˆ˜์ • -export interface ItemSection { - id: number; // ์„œ๋ฒ„ ์ƒ์„ฑ ID (string โ†’ number) - tenant_id?: number; // ๋ฐฑ์—”๋“œ์—์„œ ์ž๋™ ์ถ”๊ฐ€ - group_id?: number | null; // ๊ทธ๋ฃน ID (๋…๋ฆฝ ์„น์…˜ ๊ทธ๋ฃนํ™”์šฉ) - 2025-11-26 ์ถ”๊ฐ€ - page_id: number | null; // ์™ธ๋ž˜ํ‚ค - ํŽ˜์ด์ง€ ID (null์ด๋ฉด ๋…๋ฆฝ ์„น์…˜) - 2025-11-26 ์ˆ˜์ • - title: string; // ์„น์…˜ ์ œ๋ชฉ (API ํ•„๋“œ๋ช…๊ณผ ์ผ์น˜ํ•˜๋„๋ก section_name โ†’ title) - section_type: 'BASIC' | 'BOM' | 'CUSTOM'; // ์„น์…˜ ํƒ€์ž… (type โ†’ section_type, ๊ฐ’ ๋ณ€๊ฒฝ) - description?: string | null; // ์„ค๋ช… - order_no: number; // ์„น์…˜ ์ˆœ์„œ (order โ†’ order_no) - is_template: boolean; // ํ…œํ”Œ๋ฆฟ ์—ฌ๋ถ€ (section_templates ํ†ตํ•ฉ) - 2025-11-26 ์ถ”๊ฐ€ - is_default: boolean; // ๊ธฐ๋ณธ ํ…œํ”Œ๋ฆฟ ์—ฌ๋ถ€ - 2025-11-26 ์ถ”๊ฐ€ - is_collapsible?: boolean; // ์ ‘๊ธฐ/ํŽผ์น˜๊ธฐ ๊ฐ€๋Šฅ ์—ฌ๋ถ€ (ํ”„๋ก ํŠธ์—”๋“œ ์ „์šฉ, optional) - is_default_open?: boolean; // ๊ธฐ๋ณธ ์—ด๋ฆผ ์ƒํƒœ (ํ”„๋ก ํŠธ์—”๋“œ ์ „์šฉ, optional) - created_by?: number | null; // ์ƒ์„ฑ์ž ID ์ถ”๊ฐ€ - updated_by?: number | null; // ์ˆ˜์ •์ž ID ์ถ”๊ฐ€ - created_at: string; // ์ƒ์„ฑ์ผ (camelCase โ†’ snake_case) - updated_at: string; // ์ˆ˜์ •์ผ ์ถ”๊ฐ€ - fields?: ItemField[]; // ์„น์…˜์— ํฌํ•จ๋œ ํ•ญ๋ชฉ๋“ค (optional๋กœ ๋ณ€๊ฒฝ) - bom_items?: BOMItem[]; // BOM ํƒ€์ž…์ผ ๊ฒฝ์šฐ BOM ํ’ˆ๋ชฉ ๋ชฉ๋ก (bomItems โ†’ bom_items) -} - -// ํŽ˜์ด์ง€ (Page) - API ์‘๋‹ต ๊ตฌ์กฐ์— ๋งž์ถฐ ์ˆ˜์ • -export interface ItemPage { - id: number; // ์„œ๋ฒ„ ์ƒ์„ฑ ID (string โ†’ number) - tenant_id?: number; // ๋ฐฑ์—”๋“œ์—์„œ ์ž๋™ ์ถ”๊ฐ€ - page_name: string; // ํŽ˜์ด์ง€๋ช… (camelCase โ†’ snake_case) - item_type: 'FG' | 'PT' | 'SM' | 'RM' | 'CS'; // ํ’ˆ๋ชฉ์œ ํ˜• - description?: string | null; // ์„ค๋ช… ์ถ”๊ฐ€ - absolute_path: string; // ์ ˆ๋Œ€๊ฒฝ๋กœ (camelCase โ†’ snake_case) - is_active: boolean; // ์‚ฌ์šฉ ์—ฌ๋ถ€ (camelCase โ†’ snake_case) - order_no: number; // ์ˆœ์„œ ๋ฒˆํ˜ธ ์ถ”๊ฐ€ - created_by?: number | null; // ์ƒ์„ฑ์ž ID ์ถ”๊ฐ€ - updated_by?: number | null; // ์ˆ˜์ •์ž ID ์ถ”๊ฐ€ - created_at: string; // ์ƒ์„ฑ์ผ (camelCase โ†’ snake_case) - updated_at: string; // ์ˆ˜์ •์ผ (camelCase โ†’ snake_case) - sections: ItemSection[]; // ํŽ˜์ด์ง€์— ํฌํ•จ๋œ ์„น์…˜๋“ค (Nested) -} - -// ํ…œํ”Œ๋ฆฟ ํ•„๋“œ (๋กœ์ปฌ ๊ด€๋ฆฌ์šฉ - API์—์„œ ์ œ๊ณตํ•˜์ง€ ์•Š์Œ) -export interface TemplateField { - id: string; - name: string; - fieldKey: string; - property: { - inputType: 'textbox' | 'number' | 'dropdown' | 'checkbox' | 'date' | 'textarea'; - required: boolean; - options?: string[]; - multiColumn?: boolean; - columnCount?: number; - columnNames?: string[]; - }; - description?: string; -} - -// ์„น์…˜ ํ…œํ”Œ๋ฆฟ (์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์„น์…˜) - Transformer ์ถœ๋ ฅ๊ณผ UI ์š”๊ตฌ์‚ฌํ•ญ์— ๋งž์ถค -export interface SectionTemplate { - id: number; - tenant_id: number; - template_name: string; // transformer๊ฐ€ title โ†’ template_name์œผ๋กœ ๋ณ€ํ™˜ - section_type: 'BASIC' | 'BOM' | 'CUSTOM'; // transformer๊ฐ€ type โ†’ section_type์œผ๋กœ ๋ณ€ํ™˜ - description: string | null; - default_fields: TemplateField[] | null; // ๊ธฐ๋ณธ ํ•„๋“œ (๋กœ์ปฌ ๊ด€๋ฆฌ) - category?: string[]; // ์ ์šฉ ์นดํ…Œ๊ณ ๋ฆฌ (๋กœ์ปฌ ๊ด€๋ฆฌ) - fields?: TemplateField[]; // ํ…œํ”Œ๋ฆฟ์— ํฌํ•จ๋œ ํ•„๋“œ (๋กœ์ปฌ ๊ด€๋ฆฌ) - bomItems?: BOMItem[]; // BOM ํƒ€์ž…์ผ ๊ฒฝ์šฐ BOM ํ’ˆ๋ชฉ (๋กœ์ปฌ ๊ด€๋ฆฌ) - created_by: number | null; - updated_by: number | null; - created_at: string; - updated_at: string; -} +import type { + BendingDetail, + BOMLine, + SpecificationMaster, + MaterialItemName, + ItemRevision, + ItemMaster, + ItemCategory, + ItemUnit, + ItemMaterial, + SurfaceTreatment, + PartTypeOption, + PartUsageOption, + GuideRailOption, + ItemFieldProperty, + ItemMasterField, + FieldDisplayCondition, + ItemField, + BOMItem, + ItemSection, + ItemPage, + TemplateField, + SectionTemplate, +} from '@/types/item-master.types'; // ===== Context Type ===== interface ItemMasterContextType { @@ -1295,11 +959,22 @@ export function ItemMasterProvider({ children }: { children: ReactNode }) { throw new Error(response.message || 'ํŽ˜์ด์ง€ ์ˆ˜์ • ์‹คํŒจ'); } - // ์‘๋‹ต ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜ ๋ฐ state ์—…๋ฐ์ดํŠธ - const updatedPage = transformPageResponse(response.data); - setItemPages(prev => prev.map(page => page.id === id ? updatedPage : page)); + // โš ๏ธ 2026-01-06: ๋ณ€๊ฒฝ ์š”์ฒญํ•œ ํ•„๋“œ๋งŒ ์—…๋ฐ์ดํŠธ + // API ์‘๋‹ต(response.data)์— sections๊ฐ€ ๋นˆ ๋ฐฐ์—ด๋กœ ์˜ค๊ธฐ ๋•Œ๋ฌธ์— + // ์‘๋‹ต ์ „์ฒด๋ฅผ ๋ฎ์–ด์“ฐ๋ฉด ๊ธฐ์กด ์„น์…˜์ด ์‚ฌ๋ผ์ง€๋Š” ๋ฒ„๊ทธ ๋ฐœ์ƒ + // โ†’ ๋ณ€๊ฒฝํ•œ ํ•„๋“œ(page_name, absolute_path)๋งŒ ์—…๋ฐ์ดํŠธํ•˜๊ณ  ๋‚˜๋จธ์ง€๋Š” ๊ธฐ์กด ๊ฐ’ ์œ ์ง€ + setItemPages(prev => prev.map(page => { + if (page.id === id) { + return { + ...page, + page_name: updates.page_name ?? page.page_name, + absolute_path: updates.absolute_path ?? page.absolute_path, + }; + } + return page; + })); - console.log('[ItemMasterContext] ํŽ˜์ด์ง€ ์ˆ˜์ • ์„ฑ๊ณต:', updatedPage); + console.log('[ItemMasterContext] ํŽ˜์ด์ง€ ์ˆ˜์ • ์„ฑ๊ณต:', { id, updates }); } catch (error) { const errorMessage = getErrorMessage(error); console.error('[ItemMasterContext] ํŽ˜์ด์ง€ ์ˆ˜์ • ์‹คํŒจ:', errorMessage); diff --git a/src/stores/item-master/useItemMasterStore.ts b/src/stores/item-master/useItemMasterStore.ts index 15f038a0..2f8ce512 100644 --- a/src/stores/item-master/useItemMasterStore.ts +++ b/src/stores/item-master/useItemMasterStore.ts @@ -124,16 +124,26 @@ export const useItemMasterStore = create()( updatePage: async (id, updates) => { try { + console.log('[ItemMasterStore] updatePage ์‹œ์ž‘:', { id, updates }); + // โœ… Phase 3: API ์—ฐ๋™ const apiData = denormalizePageForRequest(updates); - const response = await itemMasterApi.pages.update(id, apiData); + await itemMasterApi.pages.update(id, apiData); - // API ์‘๋‹ต์œผ๋กœ ๋กœ์ปฌ ์ƒํƒœ ์—…๋ฐ์ดํŠธ + // โœ… ๋ณ€๊ฒฝ๋œ ํ•„๋“œ๋งŒ ๋กœ์ปฌ ์ƒํƒœ ์—…๋ฐ์ดํŠธ (sectionIds๋Š” ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š์Œ!) + // API ์‘๋‹ต์— sections๊ฐ€ ๋นˆ ๋ฐฐ์—ด๋กœ ์˜ค๊ธฐ ๋•Œ๋ฌธ์— initFromApi() ์‚ฌ์šฉ ์•ˆ ํ•จ set((state) => { - if (state.entities.pages[id]) { - Object.assign(state.entities.pages[id], updates, { - updated_at: response.data?.updated_at || new Date().toISOString(), - }); + const page = state.entities.pages[id]; + if (page) { + // ๋ณ€๊ฒฝ ์š”์ฒญ๋œ ํ•„๋“œ๋“ค๋งŒ ์—…๋ฐ์ดํŠธ + if (updates.page_name !== undefined) page.page_name = updates.page_name; + if (updates.description !== undefined) page.description = updates.description; + if (updates.item_type !== undefined) page.item_type = updates.item_type; + if (updates.absolute_path !== undefined) page.absolute_path = updates.absolute_path; + if (updates.is_active !== undefined) page.is_active = updates.is_active; + if (updates.order_no !== undefined) page.order_no = updates.order_no; + // sectionIds๋Š” ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š์Œ - ํŽ˜์ด์ง€ ์ •๋ณด๋งŒ ์ˆ˜์ •ํ•œ ๊ฑฐ๋‹ˆ๊นŒ! + page.updated_at = new Date().toISOString(); } }); @@ -243,16 +253,28 @@ export const useItemMasterStore = create()( updateSection: async (id, updates) => { try { + console.log('[ItemMasterStore] updateSection ์‹œ์ž‘:', { id, updates }); + // โœ… Phase 3: API ์—ฐ๋™ const apiData = denormalizeSectionForRequest(updates); - const response = await itemMasterApi.sections.update(id, apiData); + await itemMasterApi.sections.update(id, apiData); - // โญ ํ•ต์‹ฌ: 1๊ณณ๋งŒ ์ˆ˜์ •ํ•˜๋ฉด ๋! + // โœ… ๋ณ€๊ฒฝ๋œ ํ•„๋“œ๋งŒ ๋กœ์ปฌ ์ƒํƒœ ์—…๋ฐ์ดํŠธ (fieldIds, bomItemIds๋Š” ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š์Œ!) + // API ์‘๋‹ต์— fields๊ฐ€ ๋นˆ ๋ฐฐ์—ด๋กœ ์˜ค๊ธฐ ๋•Œ๋ฌธ์— initFromApi() ์‚ฌ์šฉ ์•ˆ ํ•จ set((state) => { - if (state.entities.sections[id]) { - Object.assign(state.entities.sections[id], updates, { - updated_at: response.data?.updated_at || new Date().toISOString(), - }); + const section = state.entities.sections[id]; + if (section) { + // ๋ณ€๊ฒฝ ์š”์ฒญ๋œ ํ•„๋“œ๋“ค๋งŒ ์—…๋ฐ์ดํŠธ + if (updates.title !== undefined) section.title = updates.title; + if (updates.description !== undefined) section.description = updates.description; + if (updates.section_type !== undefined) section.section_type = updates.section_type; + if (updates.order_no !== undefined) section.order_no = updates.order_no; + if (updates.is_template !== undefined) section.is_template = updates.is_template; + if (updates.is_default !== undefined) section.is_default = updates.is_default; + if (updates.is_collapsible !== undefined) section.is_collapsible = updates.is_collapsible; + if (updates.is_default_open !== undefined) section.is_default_open = updates.is_default_open; + // fieldIds, bomItemIds๋Š” ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š์Œ - ์„น์…˜ ์ •๋ณด๋งŒ ์ˆ˜์ •ํ•œ ๊ฑฐ๋‹ˆ๊นŒ! + section.updated_at = new Date().toISOString(); } }); diff --git a/src/types/item-master.types.ts b/src/types/item-master.types.ts new file mode 100644 index 00000000..dbb3ac39 --- /dev/null +++ b/src/types/item-master.types.ts @@ -0,0 +1,392 @@ +/** + * ํ’ˆ๋ชฉ๊ธฐ์ค€๊ด€๋ฆฌ ํƒ€์ž… ์ •์˜ + * ItemMasterContext์—์„œ ๋ถ„๋ฆฌ๋จ (2026-01-06) + */ + +// ===== ๊ธฐ๋ณธ ํƒ€์ž… ===== + +// ์ „๊ฐœ๋„ ์ƒ์„ธ ์ •๋ณด +export interface BendingDetail { + id: string; + no: number; // ๋ฒˆํ˜ธ + input: number; // ์ž…๋ ฅ + elongation: number; // ์—ฐ์‹ ์œจ (๊ธฐ๋ณธ๊ฐ’ -1) + calculated: number; // ์—ฐ์‹ ์œจ ๊ณ„์‚ฐ ํ›„ + sum: number; // ํ•ฉ๊ณ„ + shaded: boolean; // ์Œ์˜ ์—ฌ๋ถ€ + aAngle?: number; // A๊ฐ +} + +// ๋ถ€ํ’ˆ๊ตฌ์„ฑํ‘œ(BOM, Bill of Materials) - ์ž์žฌ ๋ช…์„ธ์„œ +export interface BOMLine { + id: string; + childItemCode: string; // ๊ตฌ์„ฑ ํ’ˆ๋ชฉ ์ฝ”๋“œ + childItemName: string; // ๊ตฌ์„ฑ ํ’ˆ๋ชฉ๋ช… + quantity: number; // ๊ธฐ์ค€ ์ˆ˜๋Ÿ‰ + unit: string; // ๋‹จ์œ„ + unitPrice?: number; // ๋‹จ๊ฐ€ + quantityFormula?: string; // ์ˆ˜๋Ÿ‰ ๊ณ„์‚ฐ์‹ (์˜ˆ: "W * 2", "H + 100") + note?: string; // ๋น„๊ณ  + // ์ ˆ๊ณกํ’ˆ ๊ด€๋ จ (ํ•˜์œ„ ์ ˆ๊ณก ๋ถ€ํ’ˆ์šฉ) + isBending?: boolean; + bendingDiagram?: string; // ์ „๊ฐœ๋„ ์ด๋ฏธ์ง€ URL + bendingDetails?: BendingDetail[]; // ์ „๊ฐœ๋„ ์ƒ์„ธ ๋ฐ์ดํ„ฐ +} + +// ๊ทœ๊ฒฉ ๋งˆ์Šคํ„ฐ (์›์ž์žฌ/๋ถ€์ž์žฌ์šฉ) +export interface SpecificationMaster { + id: string; + specificationCode: string; // ๊ทœ๊ฒฉ ์ฝ”๋“œ (์˜ˆ: 1.6T x 1219 x 2438) + itemType: 'RM' | 'SM'; // ์›์ž์žฌ | ๋ถ€์ž์žฌ + itemName?: string; // ํ’ˆ๋ชฉ๋ช… (์˜ˆ: SPHC-SD, SPCC-SD) - ํ’ˆ๋ชฉ๋ช…๋ณ„ ๊ทœ๊ฒฉ ํ•„ํ„ฐ๋ง์šฉ + fieldCount: '1' | '2' | '3'; // ๋„ˆ๋น„ ์ž…๋ ฅ ๊ฐœ์ˆ˜ + thickness: string; // ๋‘๊ป˜ + widthA: string; // ๋„ˆ๋น„A + widthB?: string; // ๋„ˆ๋น„B + widthC?: string; // ๋„ˆ๋น„C + length: string; // ๊ธธ์ด + description?: string; // ์„ค๋ช… + isActive: boolean; // ํ™œ์„ฑ ์—ฌ๋ถ€ + createdAt?: string; + updatedAt?: string; +} + +// ์›์ž์žฌ/๋ถ€์ž์žฌ ํ’ˆ๋ชฉ๋ช… ๋งˆ์Šคํ„ฐ +export interface MaterialItemName { + id: string; + itemType: 'RM' | 'SM'; // ์›์ž์žฌ | ๋ถ€์ž์žฌ + itemName: string; // ํ’ˆ๋ชฉ๋ช… (์˜ˆ: "SPHC-SD", "STS430") + category?: string; // ๋ถ„๋ฅ˜ (์˜ˆ: "๋ƒ‰์—ฐ", "์—ด์—ฐ", "์Šคํ…Œ์ธ๋ฆฌ์Šค") + description?: string; // ์„ค๋ช… + isActive: boolean; // ํ™œ์„ฑ ์—ฌ๋ถ€ + createdAt: string; + updatedAt?: string; +} + +// ํ’ˆ๋ชฉ ์ˆ˜์ • ์ด๋ ฅ +export interface ItemRevision { + revisionNumber: number; // ์ˆ˜์ • ์ฐจ์ˆ˜ (1์ฐจ, 2์ฐจ, 3์ฐจ...) + revisionDate: string; // ์ˆ˜์ •์ผ + revisionBy: string; // ์ˆ˜์ •์ž + revisionReason?: string; // ์ˆ˜์ • ์‚ฌ์œ  + previousData: any; // ์ด์ „ ๋ฒ„์ „์˜ ์ „์ฒด ๋ฐ์ดํ„ฐ +} + +// ํ’ˆ๋ชฉ ๋งˆ์Šคํ„ฐ +export interface ItemMaster { + id: string; + itemCode: string; + itemName: string; + itemType: 'FG' | 'PT' | 'SM' | 'RM' | 'CS'; // ์ œํ’ˆ, ๋ถ€ํ’ˆ, ๋ถ€์ž์žฌ, ์›์ž์žฌ, ์†Œ๋ชจํ’ˆ + productCategory?: 'SCREEN' | 'STEEL'; // ์ œํ’ˆ ์นดํ…Œ๊ณ ๋ฆฌ (์Šคํฌ๋ฆฐ/์ฒ ์žฌ) + partType?: 'ASSEMBLY' | 'BENDING' | 'PURCHASED'; // ๋ถ€ํ’ˆ ์œ ํ˜• (์กฐ๋ฆฝ/์ ˆ๊ณก/๊ตฌ๋งค) + partUsage?: 'GUIDE_RAIL' | 'BOTTOM_FINISH' | 'CASE' | 'DOOR' | 'BRACKET' | 'GENERAL'; // ๋ถ€ํ’ˆ ์šฉ๋„ + unit: string; + category1?: string; + category2?: string; + category3?: string; + specification?: string; + isVariableSize?: boolean; + isActive?: boolean; // ํ’ˆ๋ชฉ ํ™œ์„ฑ/๋น„ํ™œ์„ฑ (์ œํ’ˆ/๋ถ€ํ’ˆ/์›์ž์žฌ/๋ถ€์ž์žฌ๋งŒ ์‚ฌ์šฉ) + lotAbbreviation?: string; // ๋กœํŠธ ์•ฝ์ž (์ œํ’ˆ๋งŒ ์‚ฌ์šฉ) + purchasePrice?: number; + marginRate?: number; + processingCost?: number; + laborCost?: number; + installCost?: number; + salesPrice?: number; + safetyStock?: number; + leadTime?: number; + bom?: BOMLine[]; // ๋ถ€ํ’ˆ๊ตฌ์„ฑํ‘œ(BOM) - ์ž์žฌ ๋ช…์„ธ์„œ + bomCategories?: string[]; // ๊ฒฌ์ ์‚ฐ์ถœ์šฉ ์ƒ˜ํ”Œ ์ œํ’ˆ์˜ BOM ์นดํ…Œ๊ณ ๋ฆฌ (์˜ˆ: ['motor', 'guide-rail']) + + // ์ธ์ • ์ •๋ณด + certificationNumber?: string; // ์ธ์ •๋ฒˆํ˜ธ + certificationStartDate?: string; // ์ธ์ • ์œ ํšจ๊ธฐ๊ฐ„ ์‹œ์ž‘์ผ + certificationEndDate?: string; // ์ธ์ • ์œ ํšจ๊ธฐ๊ฐ„ ์ข…๋ฃŒ์ผ + specificationFile?: string; // ์‹œ๋ฐฉ์„œ ํŒŒ์ผ (Base64 ๋˜๋Š” URL) + specificationFileName?: string; // ์‹œ๋ฐฉ์„œ ํŒŒ์ผ๋ช… + certificationFile?: string; // ์ธ์ •์„œ ํŒŒ์ผ (Base64 ๋˜๋Š” URL) + certificationFileName?: string; // ์ธ์ •์„œ ํŒŒ์ผ๋ช… + note?: string; // ๋น„๊ณ  (์ œํ’ˆ๋งŒ ์‚ฌ์šฉ) + + // ์กฐ๋ฆฝ ๋ถ€ํ’ˆ ๊ด€๋ จ ํ•„๋“œ + installationType?: string; // ์„ค์น˜ ์œ ํ˜• (wall: ๋ฒฝ๋ฉดํ˜•, side: ์ธก๋ฉดํ˜•, steel: ์Šคํ‹ธ, iron: ์ฒ ์žฌ) + assemblyType?: string; // ์ข…๋ฅ˜ (M, T, C, D, S, U ๋“ฑ) + sideSpecWidth?: string; // ์ธก๋ฉด ๊ทœ๊ฒฉ ๊ฐ€๋กœ (mm) + sideSpecHeight?: string; // ์ธก๋ฉด ๊ทœ๊ฒฉ ์„ธ๋กœ (mm) + assemblyLength?: string; // ๊ธธ์ด (2438, 3000, 3500, 4000, 4300 ๋“ฑ) + + // ๊ฐ€์ด๋“œ๋ ˆ์ผ ๊ด€๋ จ ํ•„๋“œ + guideRailModelType?: string; // ๊ฐ€์ด๋“œ๋ ˆ์ผ ๋ชจ๋ธ ์œ ํ˜• + guideRailModel?: string; // ๊ฐ€์ด๋“œ๋ ˆ์ผ ๋ชจ๋ธ + + // ์ ˆ๊ณกํ’ˆ ๊ด€๋ จ (๋ถ€ํ’ˆ ์œ ํ˜•์ด BENDING์ธ ๊ฒฝ์šฐ) + bendingDiagram?: string; // ์ „๊ฐœ๋„ ์ด๋ฏธ์ง€ URL + bendingDetails?: BendingDetail[]; // ์ „๊ฐœ๋„ ์ƒ์„ธ ๋ฐ์ดํ„ฐ + material?: string; // ์žฌ์งˆ (EGI 1.55T, SUS 1.2T ๋“ฑ) + length?: string; // ๊ธธ์ด/๋ชฉํ•จ (mm) + + // ๋ฒ„์ „ ๊ด€๋ฆฌ + currentRevision: number; // ํ˜„์žฌ ์ฐจ์ˆ˜ (0 = ์ตœ์ดˆ, 1 = 1์ฐจ ์ˆ˜์ •...) + revisions?: ItemRevision[]; // ์ˆ˜์ • ์ด๋ ฅ + isFinal: boolean; // ์ตœ์ข… ํ™•์ • ์—ฌ๋ถ€ + finalizedDate?: string; // ์ตœ์ข… ํ™•์ •์ผ + finalizedBy?: string; // ์ตœ์ข… ํ™•์ •์ž + + createdAt: string; +} + +// ===== ํ’ˆ๋ชฉ ๊ธฐ์ค€์ •๋ณด ๊ด€๋ฆฌ (Master Data) ===== + +export interface ItemCategory { + id: string; + categoryType: 'PRODUCT' | 'PART' | 'MATERIAL' | 'SUB_MATERIAL'; // ํ’ˆ๋ชฉ ๊ตฌ๋ถ„ + category1: string; // ๋Œ€๋ถ„๋ฅ˜ + category2?: string; // ์ค‘๋ถ„๋ฅ˜ + category3?: string; // ์†Œ๋ถ„๋ฅ˜ + code?: string; // ์ฝ”๋“œ (์ž๋™์ƒ์„ฑ ๋˜๋Š” ์ˆ˜๋™์ž…๋ ฅ) + description?: string; + isActive: boolean; + createdAt: string; + updatedAt?: string; +} + +export interface ItemUnit { + id: string; + unitCode: string; // ๋‹จ์œ„ ์ฝ”๋“œ (EA, SET, M, KG, L ๋“ฑ) + unitName: string; // ๋‹จ์œ„๋ช… + description?: string; + isActive: boolean; + createdAt: string; + updatedAt?: string; +} + +export interface ItemMaterial { + id: string; + materialCode: string; // ์žฌ์งˆ ์ฝ”๋“œ + materialName: string; // ์žฌ์งˆ๋ช… (EGI 1.55T, SUS 1.2T ๋“ฑ) + materialType: 'STEEL' | 'ALUMINUM' | 'PLASTIC' | 'OTHER'; // ์žฌ์งˆ ์œ ํ˜• + thickness?: string; // ๋‘๊ป˜ (1.2T, 1.6T ๋“ฑ) + description?: string; + isActive: boolean; + createdAt: string; + updatedAt?: string; +} + +export interface SurfaceTreatment { + id: string; + treatmentCode: string; // ์ฒ˜๋ฆฌ ์ฝ”๋“œ + treatmentName: string; // ์ฒ˜๋ฆฌ๋ช… (๋ฌด๋„์žฅ, ํŒŒ์šฐ๋”๋„์žฅ, ์•„๋…ธ๋‹ค์ด์ง• ๋“ฑ) + treatmentType: 'PAINTING' | 'COATING' | 'PLATING' | 'NONE'; // ์ฒ˜๋ฆฌ ์œ ํ˜• + description?: string; + isActive: boolean; + createdAt: string; + updatedAt?: string; +} + +export interface PartTypeOption { + id: string; + partType: 'ASSEMBLY' | 'BENDING' | 'PURCHASED'; // ๋ถ€ํ’ˆ ์œ ํ˜• + optionCode: string; // ์˜ต์…˜ ์ฝ”๋“œ + optionName: string; // ์˜ต์…˜๋ช… + description?: string; + isActive: boolean; + createdAt: string; + updatedAt?: string; +} + +export interface PartUsageOption { + id: string; + usageCode: string; // ์šฉ๋„ ์ฝ”๋“œ + usageName: string; // ์šฉ๋„๋ช… (๊ฐ€์ด๋“œ๋ ˆ์ผ, ํ•˜๋‹จ๋งˆ๊ฐ์žฌ, ์ผ€์ด์Šค ๋“ฑ) + description?: string; + isActive: boolean; + createdAt: string; + updatedAt?: string; +} + +export interface GuideRailOption { + id: string; + optionType: 'MODEL_TYPE' | 'MODEL' | 'CERTIFICATION' | 'SHAPE' | 'FINISH' | 'LENGTH'; // ์˜ต์…˜ ์œ ํ˜• + optionCode: string; // ์˜ต์…˜ ์ฝ”๋“œ + optionName: string; // ์˜ต์…˜๋ช… + parentOption?: string; // ์ƒ์œ„ ์˜ต์…˜ (์ข…์† ๊ด€๊ณ„) + description?: string; + isActive: boolean; + createdAt: string; + updatedAt?: string; +} + +// ===== ํ’ˆ๋ชฉ๊ธฐ์ค€๊ด€๋ฆฌ ๊ณ„์ธต๊ตฌ์กฐ ===== + +// ํ•ญ๋ชฉ ์†์„ฑ +export interface ItemFieldProperty { + id?: string; // ์†์„ฑ ID (properties ๋ฐฐ์—ด์—์„œ ์‚ฌ์šฉ) + key?: string; // ์†์„ฑ ํ‚ค (properties ๋ฐฐ์—ด์—์„œ ์‚ฌ์šฉ) + label?: string; // ์†์„ฑ ๋ผ๋ฒจ (properties ๋ฐฐ์—ด์—์„œ ์‚ฌ์šฉ) + type?: 'textbox' | 'dropdown' | 'checkbox' | 'number' | 'date' | 'textarea'; // ์†์„ฑ ํƒ€์ž… (properties ๋ฐฐ์—ด์—์„œ ์‚ฌ์šฉ) + inputType: 'textbox' | 'dropdown' | 'checkbox' | 'number' | 'date' | 'textarea' | 'section'; // ์ž…๋ ฅ๋ฐฉ์‹ + required: boolean; // ํ•„์ˆ˜ ์—ฌ๋ถ€ + row: number; // ํ–‰ ์œ„์น˜ + col: number; // ์—ด ์œ„์น˜ + options?: string[]; // ๋“œ๋กญ๋‹ค์šด ์˜ต์…˜ (์ž…๋ ฅ๋ฐฉ์‹์ด dropdown์ผ ๊ฒฝ์šฐ) + defaultValue?: string; // ๊ธฐ๋ณธ๊ฐ’ + placeholder?: string; // ํ”Œ๋ ˆ์ด์Šคํ™€๋” + multiColumn?: boolean; // ๋‹ค์ค‘ ์ปฌ๋Ÿผ ์‚ฌ์šฉ ์—ฌ๋ถ€ + columnCount?: number; // ์ปฌ๋Ÿผ ๊ฐœ์ˆ˜ + columnNames?: string[]; // ๊ฐ ์ปฌ๋Ÿผ์˜ ์ด๋ฆ„ +} + +// ํ•ญ๋ชฉ ๋งˆ์Šคํ„ฐ (์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ํ•ญ๋ชฉ ํ…œํ”Œ๋ฆฟ) - MasterFieldResponse์™€ ์ •ํ™•ํžˆ ์ผ์น˜ +export interface ItemMasterField { + id: number; + tenant_id: number; + field_name: string; + field_key?: string | null; // 2025-11-28: field_key ์ถ”๊ฐ€ (ํ˜•์‹: {ID}_{์‚ฌ์šฉ์ž์ž…๋ ฅ}) + field_type: 'textbox' | 'number' | 'dropdown' | 'checkbox' | 'date' | 'textarea'; // API์™€ ๋™์ผ + category: string | null; + description: string | null; + is_common: boolean; // ๊ณตํ†ต ํ•„๋“œ ์—ฌ๋ถ€ + is_required?: boolean; // ํ•„์ˆ˜ ์—ฌ๋ถ€ (API์—์„œ ๋ฐ˜ํ™˜) + default_value: string | null; // ๊ธฐ๋ณธ๊ฐ’ + options: Array<{ label: string; value: string }> | null; // dropdown ์˜ต์…˜ + validation_rules: Record | null; // ๊ฒ€์ฆ ๊ทœ์น™ + properties: Record | null; // ์ถ”๊ฐ€ ์†์„ฑ + created_by: number | null; + updated_by: number | null; + created_at: string; + updated_at: string; +} + +// ์กฐ๊ฑด๋ถ€ ํ‘œ์‹œ ์„ค์ • +export interface FieldDisplayCondition { + targetType: 'field' | 'section'; // ์กฐ๊ฑด ๋Œ€์ƒ ํƒ€์ž… + // ์ผ๋ฐ˜ํ•ญ๋ชฉ ์กฐ๊ฑด (์—ฌ๋Ÿฌ ๊ฐœ ๊ฐ€๋Šฅ) + fieldConditions?: Array<{ + fieldKey: string; // ์กฐ๊ฑด์ด ๋˜๋Š” ํ•„๋“œ์˜ ํ‚ค + expectedValue: string; // ์˜ˆ์ƒ๋˜๋Š” ๊ฐ’ + }>; + // ์„น์…˜ ์กฐ๊ฑด (์—ฌ๋Ÿฌ ๊ฐœ ๊ฐ€๋Šฅ) + sectionIds?: string[]; // ํ‘œ์‹œํ•  ์„น์…˜ ID ๋ฐฐ์—ด +} + +// ํ•ญ๋ชฉ (Field) - API ์‘๋‹ต ๊ตฌ์กฐ์— ๋งž์ถฐ ์ˆ˜์ • +export interface ItemField { + id: number; // ์„œ๋ฒ„ ์ƒ์„ฑ ID (string โ†’ number) + tenant_id?: number; // ๋ฐฑ์—”๋“œ์—์„œ ์ž๋™ ์ถ”๊ฐ€ + group_id?: number | null; // ๊ทธ๋ฃน ID (๋…๋ฆฝ ํ•„๋“œ์šฉ) + section_id: number | null; // ์™ธ๋ž˜ํ‚ค - ์„น์…˜ ID (๋…๋ฆฝ ํ•„๋“œ๋Š” null) + master_field_id?: number | null; // ๋งˆ์Šคํ„ฐ ํ•ญ๋ชฉ ID (๋งˆ์Šคํ„ฐ์—์„œ ๊ฐ€์ ธ์˜จ ๊ฒฝ์šฐ) + field_name: string; // ํ•ญ๋ชฉ๋ช… (name โ†’ field_name) + field_key?: string | null; // 2025-11-28: ํ•„๋“œ ํ‚ค (ํ˜•์‹: {ID}_{์‚ฌ์šฉ์ž์ž…๋ ฅ}, ๋ฐฑ์—”๋“œ์—์„œ ์ƒ์„ฑ) + field_type: 'textbox' | 'number' | 'dropdown' | 'checkbox' | 'date' | 'textarea'; // ํ•„๋“œ ํƒ€์ž… + order_no: number; // ํ•ญ๋ชฉ ์ˆœ์„œ (order โ†’ order_no, required) + is_required: boolean; // ํ•„์ˆ˜ ์—ฌ๋ถ€ + placeholder?: string | null; // ํ”Œ๋ ˆ์ด์Šคํ™€๋” + default_value?: string | null; // ๊ธฐ๋ณธ๊ฐ’ + display_condition?: Record | null; // ์กฐ๊ฑด๋ถ€ ํ‘œ์‹œ ์„ค์ • (displayCondition โ†’ display_condition) + validation_rules?: Record | null; // ๊ฒ€์ฆ ๊ทœ์น™ + options?: Array<{ label: string; value: string }> | null; // dropdown ์˜ต์…˜ + properties?: Record | null; // ์ถ”๊ฐ€ ์†์„ฑ + // 2025-11-28 ์ถ”๊ฐ€: ์ž ๊ธˆ ๊ธฐ๋Šฅ + is_locked?: boolean; // ์ž ๊ธˆ ์—ฌ๋ถ€ + locked_by?: number | null; // ์ž ๊ธˆ ์„ค์ •์ž + locked_at?: string | null; // ์ž ๊ธˆ ์‹œ๊ฐ„ + created_by?: number | null; // ์ƒ์„ฑ์ž ID ์ถ”๊ฐ€ + updated_by?: number | null; // ์ˆ˜์ •์ž ID ์ถ”๊ฐ€ + created_at: string; // ์ƒ์„ฑ์ผ (camelCase โ†’ snake_case) + updated_at: string; // ์ˆ˜์ •์ผ ์ถ”๊ฐ€ +} + +// BOM ์•„์ดํ…œ ํƒ€์ž… - API ์‘๋‹ต ๊ตฌ์กฐ์— ๋งž์ถฐ ์ˆ˜์ • +export interface BOMItem { + id: number; // ์„œ๋ฒ„ ์ƒ์„ฑ ID (string โ†’ number) + tenant_id?: number; // ๋ฐฑ์—”๋“œ์—์„œ ์ž๋™ ์ถ”๊ฐ€ + group_id?: number | null; // ๊ทธ๋ฃน ID (๋…๋ฆฝ BOM์šฉ) + section_id: number | null; // ์™ธ๋ž˜ํ‚ค - ์„น์…˜ ID (๋…๋ฆฝ BOM์€ null) + item_code?: string | null; // ํ’ˆ๋ชฉ ์ฝ”๋“œ (itemCode โ†’ item_code, optional) + item_name: string; // ํ’ˆ๋ชฉ๋ช… (itemName โ†’ item_name) + quantity: number; // ์ˆ˜๋Ÿ‰ + unit?: string | null; // ๋‹จ์œ„ (optional) + unit_price?: number | null; // ๋‹จ๊ฐ€ ์ถ”๊ฐ€ + total_price?: number | null; // ์ด์•ก ์ถ”๊ฐ€ + spec?: string | null; // ๊ทœ๊ฒฉ/์‚ฌ์–‘ ์ถ”๊ฐ€ + note?: string | null; // ๋น„๊ณ  (optional) + created_by?: number | null; // ์ƒ์„ฑ์ž ID ์ถ”๊ฐ€ + updated_by?: number | null; // ์ˆ˜์ •์ž ID ์ถ”๊ฐ€ + created_at: string; // ์ƒ์„ฑ์ผ (createdAt โ†’ created_at) + updated_at: string; // ์ˆ˜์ •์ผ ์ถ”๊ฐ€ +} + +// ์„น์…˜ (Section) - API ์‘๋‹ต ๊ตฌ์กฐ์— ๋งž์ถฐ ์ˆ˜์ • +export interface ItemSection { + id: number; // ์„œ๋ฒ„ ์ƒ์„ฑ ID (string โ†’ number) + tenant_id?: number; // ๋ฐฑ์—”๋“œ์—์„œ ์ž๋™ ์ถ”๊ฐ€ + group_id?: number | null; // ๊ทธ๋ฃน ID (๋…๋ฆฝ ์„น์…˜ ๊ทธ๋ฃนํ™”์šฉ) - 2025-11-26 ์ถ”๊ฐ€ + page_id: number | null; // ์™ธ๋ž˜ํ‚ค - ํŽ˜์ด์ง€ ID (null์ด๋ฉด ๋…๋ฆฝ ์„น์…˜) - 2025-11-26 ์ˆ˜์ • + title: string; // ์„น์…˜ ์ œ๋ชฉ (API ํ•„๋“œ๋ช…๊ณผ ์ผ์น˜ํ•˜๋„๋ก section_name โ†’ title) + section_type: 'BASIC' | 'BOM' | 'CUSTOM'; // ์„น์…˜ ํƒ€์ž… (type โ†’ section_type, ๊ฐ’ ๋ณ€๊ฒฝ) + description?: string | null; // ์„ค๋ช… + order_no: number; // ์„น์…˜ ์ˆœ์„œ (order โ†’ order_no) + is_template: boolean; // ํ…œํ”Œ๋ฆฟ ์—ฌ๋ถ€ (section_templates ํ†ตํ•ฉ) - 2025-11-26 ์ถ”๊ฐ€ + is_default: boolean; // ๊ธฐ๋ณธ ํ…œํ”Œ๋ฆฟ ์—ฌ๋ถ€ - 2025-11-26 ์ถ”๊ฐ€ + is_collapsible?: boolean; // ์ ‘๊ธฐ/ํŽผ์น˜๊ธฐ ๊ฐ€๋Šฅ ์—ฌ๋ถ€ (ํ”„๋ก ํŠธ์—”๋“œ ์ „์šฉ, optional) + is_default_open?: boolean; // ๊ธฐ๋ณธ ์—ด๋ฆผ ์ƒํƒœ (ํ”„๋ก ํŠธ์—”๋“œ ์ „์šฉ, optional) + created_by?: number | null; // ์ƒ์„ฑ์ž ID ์ถ”๊ฐ€ + updated_by?: number | null; // ์ˆ˜์ •์ž ID ์ถ”๊ฐ€ + created_at: string; // ์ƒ์„ฑ์ผ (camelCase โ†’ snake_case) + updated_at: string; // ์ˆ˜์ •์ผ ์ถ”๊ฐ€ + fields?: ItemField[]; // ์„น์…˜์— ํฌํ•จ๋œ ํ•ญ๋ชฉ๋“ค (optional๋กœ ๋ณ€๊ฒฝ) + bom_items?: BOMItem[]; // BOM ํƒ€์ž…์ผ ๊ฒฝ์šฐ BOM ํ’ˆ๋ชฉ ๋ชฉ๋ก (bomItems โ†’ bom_items) +} + +// ํŽ˜์ด์ง€ (Page) - API ์‘๋‹ต ๊ตฌ์กฐ์— ๋งž์ถฐ ์ˆ˜์ • +export interface ItemPage { + id: number; // ์„œ๋ฒ„ ์ƒ์„ฑ ID (string โ†’ number) + tenant_id?: number; // ๋ฐฑ์—”๋“œ์—์„œ ์ž๋™ ์ถ”๊ฐ€ + page_name: string; // ํŽ˜์ด์ง€๋ช… (camelCase โ†’ snake_case) + item_type: 'FG' | 'PT' | 'SM' | 'RM' | 'CS'; // ํ’ˆ๋ชฉ์œ ํ˜• + description?: string | null; // ์„ค๋ช… ์ถ”๊ฐ€ + absolute_path: string; // ์ ˆ๋Œ€๊ฒฝ๋กœ (camelCase โ†’ snake_case) + is_active: boolean; // ์‚ฌ์šฉ ์—ฌ๋ถ€ (camelCase โ†’ snake_case) + order_no: number; // ์ˆœ์„œ ๋ฒˆํ˜ธ ์ถ”๊ฐ€ + created_by?: number | null; // ์ƒ์„ฑ์ž ID ์ถ”๊ฐ€ + updated_by?: number | null; // ์ˆ˜์ •์ž ID ์ถ”๊ฐ€ + created_at: string; // ์ƒ์„ฑ์ผ (camelCase โ†’ snake_case) + updated_at: string; // ์ˆ˜์ •์ผ (camelCase โ†’ snake_case) + sections: ItemSection[]; // ํŽ˜์ด์ง€์— ํฌํ•จ๋œ ์„น์…˜๋“ค (Nested) +} + +// ํ…œํ”Œ๋ฆฟ ํ•„๋“œ (๋กœ์ปฌ ๊ด€๋ฆฌ์šฉ - API์—์„œ ์ œ๊ณตํ•˜์ง€ ์•Š์Œ) +export interface TemplateField { + id: string; + name: string; + fieldKey: string; + property: { + inputType: 'textbox' | 'number' | 'dropdown' | 'checkbox' | 'date' | 'textarea'; + required: boolean; + options?: string[]; + multiColumn?: boolean; + columnCount?: number; + columnNames?: string[]; + }; + description?: string; +} + +// ์„น์…˜ ํ…œํ”Œ๋ฆฟ (์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์„น์…˜) - Transformer ์ถœ๋ ฅ๊ณผ UI ์š”๊ตฌ์‚ฌํ•ญ์— ๋งž์ถค +export interface SectionTemplate { + id: number; + tenant_id: number; + template_name: string; // transformer๊ฐ€ title โ†’ template_name์œผ๋กœ ๋ณ€ํ™˜ + section_type: 'BASIC' | 'BOM' | 'CUSTOM'; // transformer๊ฐ€ type โ†’ section_type์œผ๋กœ ๋ณ€ํ™˜ + description: string | null; + default_fields: TemplateField[] | null; // ๊ธฐ๋ณธ ํ•„๋“œ (๋กœ์ปฌ ๊ด€๋ฆฌ) + category?: string[]; // ์ ์šฉ ์นดํ…Œ๊ณ ๋ฆฌ (๋กœ์ปฌ ๊ด€๋ฆฌ) + fields?: TemplateField[]; // ํ…œํ”Œ๋ฆฟ์— ํฌํ•จ๋œ ํ•„๋“œ (๋กœ์ปฌ ๊ด€๋ฆฌ) + bomItems?: BOMItem[]; // BOM ํƒ€์ž…์ผ ๊ฒฝ์šฐ BOM ํ’ˆ๋ชฉ (๋กœ์ปฌ ๊ด€๋ฆฌ) + created_by: number | null; + updated_by: number | null; + created_at: string; + updated_at: string; +} \ No newline at end of file