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