"use client"; import React, { useState, useEffect, useCallback, useMemo, useRef } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Checkbox } from "@/components/ui/checkbox"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; import { ChevronDown, ChevronUp, ChevronRight, Plus, Trash2, Loader2 } from "lucide-react"; import { toast } from "sonner"; import { cn } from "@/lib/utils"; import { apiClient } from "@/lib/api/client"; import { allocateNumberingCode, previewNumberingCode } from "@/lib/api/numberingRule"; import { useCascadingDropdown } from "@/hooks/useCascadingDropdown"; import { CascadingDropdownConfig } from "@/types/screen-management"; import { UniversalFormModalComponentProps, UniversalFormModalConfig, FormSectionConfig, FormFieldConfig, FormDataState, RepeatSectionItem, SelectOptionConfig, OptionalFieldGroupConfig, } from "./types"; import { defaultConfig, generateUniqueId } from "./config"; import { TableSectionRenderer } from "./TableSectionRenderer"; /** * ๐Ÿ”— ์—ฐ์‡„ ๋“œ๋กญ๋‹ค์šด Select ํ•„๋“œ ์ปดํฌ๋„ŒํŠธ */ interface CascadingSelectFieldProps { fieldId: string; config: CascadingDropdownConfig; parentValue?: string | number | null; value?: string; onChange: (value: string) => void; placeholder?: string; disabled?: boolean; } const CascadingSelectField: React.FC = ({ fieldId, config, parentValue, value, onChange, placeholder, disabled, }) => { const { options, loading } = useCascadingDropdown({ config, parentValue, }); const getPlaceholder = () => { if (!parentValue) { return config.emptyParentMessage || "์ƒ์œ„ ํ•ญ๋ชฉ์„ ๋จผ์ € ์„ ํƒํ•˜์„ธ์š”"; } if (loading) { return config.loadingMessage || "๋กœ๋”ฉ ์ค‘..."; } if (options.length === 0) { return config.noOptionsMessage || "์„ ํƒ ๊ฐ€๋Šฅํ•œ ํ•ญ๋ชฉ์ด ์—†์Šต๋‹ˆ๋‹ค"; } return placeholder || "์„ ํƒํ•˜์„ธ์š”"; }; const isDisabled = disabled || !parentValue || loading; return ( ); }; /** * ๋ฒ”์šฉ ํผ ๋ชจ๋‹ฌ ์ปดํฌ๋„ŒํŠธ * * ์„น์…˜ ๊ธฐ๋ฐ˜ ํผ ๋ ˆ์ด์•„์›ƒ, ์ฑ„๋ฒˆ๊ทœ์น™, ๋‹ค์ค‘ ํ–‰ ์ €์žฅ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. */ export function UniversalFormModalComponent({ component, config: propConfig, isDesignMode = false, isSelected = false, className, style, initialData: propInitialData, // DynamicComponentRenderer์—์„œ ์ „๋‹ฌ๋˜๋Š” props (DOM ์ „๋‹ฌ ๋ฐฉ์ง€๋ฅผ ์œ„ํ•ด _ prefix ์‚ฌ์šฉ) _initialData, _originalData, _groupedData, onSave, onCancel, onChange, ...restProps // ๋‚˜๋จธ์ง€ props๋Š” DOM์— ์ „๋‹ฌํ•˜์ง€ ์•Š์Œ }: UniversalFormModalComponentProps & { _initialData?: any; _originalData?: any; _groupedData?: any }) { // initialData ์šฐ์„ ์ˆœ์œ„: ์ง์ ‘ ์ „๋‹ฌ๋œ prop > DynamicComponentRenderer์—์„œ ์ „๋‹ฌ๋œ prop const initialData = propInitialData || _initialData; // ์„ค์ • ๋ณ‘ํ•ฉ const config: UniversalFormModalConfig = useMemo(() => { const componentConfig = component?.config || {}; return { ...defaultConfig, ...propConfig, ...componentConfig, modal: { ...defaultConfig.modal, ...propConfig?.modal, ...componentConfig.modal, }, saveConfig: { ...defaultConfig.saveConfig, ...propConfig?.saveConfig, ...componentConfig.saveConfig, afterSave: { ...defaultConfig.saveConfig.afterSave, ...propConfig?.saveConfig?.afterSave, ...componentConfig.saveConfig?.afterSave, }, }, }; }, [component?.config, propConfig]); // ํผ ๋ฐ์ดํ„ฐ ์ƒํƒœ const [formData, setFormData] = useState({}); const [, setOriginalData] = useState>({}); // ๋ฐ˜๋ณต ์„น์…˜ ๋ฐ์ดํ„ฐ const [repeatSections, setRepeatSections] = useState<{ [sectionId: string]: RepeatSectionItem[]; }>({}); // ์„น์…˜ ์ ‘ํž˜ ์ƒํƒœ const [collapsedSections, setCollapsedSections] = useState>(new Set()); // ์˜ต์…”๋„ ํ•„๋“œ ๊ทธ๋ฃน ํ™œ์„ฑํ™” ์ƒํƒœ (์„น์…˜ID-๊ทธ๋ฃนID ์กฐํ•ฉ) const [activatedOptionalFieldGroups, setActivatedOptionalFieldGroups] = useState>(new Set()); // Select ์˜ต์…˜ ์บ์‹œ const [selectOptionsCache, setSelectOptionsCache] = useState<{ [key: string]: { value: string; label: string }[]; }>({}); // ์—ฐ๋™ ํ•„๋“œ ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ ์บ์‹œ (ํ…Œ์ด๋ธ”๋ณ„ ๋ฐ์ดํ„ฐ) const [linkedFieldDataCache, setLinkedFieldDataCache] = useState<{ [tableKey: string]: Record[]; }>({}); // ์ฑ„๋ฒˆ๊ทœ์น™ ์›๋ณธ ๊ฐ’ ์ถ”์  (์ˆ˜๋™ ๋ชจ๋“œ ๊ฐ์ง€์šฉ) // key: columnName, value: ์ž๋™ ์ƒ์„ฑ๋œ ์›๋ณธ ๊ฐ’ const [numberingOriginalValues, setNumberingOriginalValues] = useState>({}); // ๐Ÿ†• ์ˆ˜์ • ๋ชจ๋“œ: ์›๋ณธ ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ (INSERT/UPDATE/DELETE ์ถ”์ ์šฉ) const [originalGroupedData, setOriginalGroupedData] = useState([]); const groupedDataInitializedRef = useRef(false); // ์‚ญ์ œ ํ™•์ธ ๋‹ค์ด์–ผ๋กœ๊ทธ const [deleteDialog, setDeleteDialog] = useState<{ open: boolean; sectionId: string; itemId: string; }>({ open: false, sectionId: "", itemId: "" }); // ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ๋ฅผ ํ•œ ๋ฒˆ๋งŒ ์บก์ฒ˜ (์ปดํฌ๋„ŒํŠธ ๋งˆ์šดํŠธ ์‹œ) const capturedInitialData = useRef | undefined>(undefined); const hasInitialized = useRef(false); // ๋งˆ์ง€๋ง‰์œผ๋กœ ์ดˆ๊ธฐํ™”๋œ ๋ฐ์ดํ„ฐ์˜ ID๋ฅผ ์ถ”์  (์ˆ˜์ • ๋ชจ๋‹ฌ์—์„œ ๋‹ค๋ฅธ ํ•ญ๋ชฉ ์„ ํƒ ์‹œ ์žฌ์ดˆ๊ธฐํ™” ํ•„์š”) const lastInitializedId = useRef(undefined); // ์ดˆ๊ธฐํ™” - ์ตœ์ดˆ ๋งˆ์šดํŠธ ์‹œ ๋˜๋Š” initialData๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ์„ ๋•Œ ์‹คํ–‰ useEffect(() => { // console.log("[UniversalFormModal] useEffect ์‹œ์ž‘", { // initialData, // hasInitialized: hasInitialized.current, // lastInitializedId: lastInitializedId.current, // }); // initialData์—์„œ ID ๊ฐ’ ์ถ”์ถœ (id, ID, objid ๋“ฑ) const currentId = initialData?.id || initialData?.ID || initialData?.objid; const currentIdString = currentId !== undefined ? String(currentId) : undefined; // ์ƒ์„ฑ ๋ชจ๋“œ์—์„œ ๋ถ€๋ชจ๋กœ๋ถ€ํ„ฐ ์ „๋‹ฌ๋ฐ›์€ ๋ฐ์ดํ„ฐ ํ•ด์‹œ (ID๊ฐ€ ์—†์„ ๋•Œ๋งŒ) const createModeDataHash = !currentIdString && initialData && Object.keys(initialData).length > 0 ? JSON.stringify(initialData) : undefined; // ์ด๋ฏธ ์ดˆ๊ธฐํ™”๋˜์—ˆ๊ณ , ID๊ฐ€ ๋™์ผํ•˜๊ณ , ์ƒ์„ฑ ๋ชจ๋“œ ๋ฐ์ดํ„ฐ๋„ ๋™์ผํ•˜๋ฉด ์Šคํ‚ต if (hasInitialized.current && lastInitializedId.current === currentIdString) { // ์ƒ์„ฑ ๋ชจ๋“œ์—์„œ ๋ฐ์ดํ„ฐ๊ฐ€ ์ƒˆ๋กœ ์ „๋‹ฌ๋œ ๊ฒฝ์šฐ๋Š” ์žฌ์ดˆ๊ธฐํ™” ํ•„์š” if (!createModeDataHash || capturedInitialData.current) { // console.log("[UniversalFormModal] ์ดˆ๊ธฐํ™” ์Šคํ‚ต - ์ด๋ฏธ ์ดˆ๊ธฐํ™”๋จ"); // ๐Ÿ†• ์ฑ„๋ฒˆ ํ”Œ๋ž˜๊ทธ๊ฐ€ true์ธ๋ฐ formData์— ๊ฐ’์ด ์—†์œผ๋ฉด ์žฌ์ƒ์„ฑ ํ•„์š” // (์ปดํฌ๋„ŒํŠธ remount๋กœ ์ธํ•ด state๊ฐ€ ์ดˆ๊ธฐํ™”๋œ ๊ฒฝ์šฐ) return; } } // ๐Ÿ†• ์ปดํฌ๋„ŒํŠธ remount ๊ฐ์ง€: hasInitialized๊ฐ€ true์ธ๋ฐ formData๊ฐ€ ๋น„์–ด์žˆ์œผ๋ฉด ์žฌ์ดˆ๊ธฐํ™” // (React์˜ Strict Mode๋‚˜ EmbeddedScreen ๋ฆฌ๋ Œ๋”๋ง์œผ๋กœ ์ธํ•œ remount) if (hasInitialized.current && !currentIdString) { // console.log("[UniversalFormModal] ์ปดํฌ๋„ŒํŠธ remount ๊ฐ์ง€ - ์ฑ„๋ฒˆ ํ”Œ๋ž˜๊ทธ ์ดˆ๊ธฐํ™”"); numberingGeneratedRef.current = false; isGeneratingRef.current = false; } // ๐Ÿ†• ์ˆ˜์ • ๋ชจ๋“œ: initialData์— ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์œผ๋ฉด์„œ ID๊ฐ€ ๋ณ€๊ฒฝ๋œ ๊ฒฝ์šฐ ์žฌ์ดˆ๊ธฐํ™” if (hasInitialized.current && currentIdString && lastInitializedId.current !== currentIdString) { // console.log("[UniversalFormModal] ID ๋ณ€๊ฒฝ ๊ฐ์ง€ - ์žฌ์ดˆ๊ธฐํ™”:", { // prevId: lastInitializedId.current, // newId: currentIdString, // initialData: initialData, // }); // ์ฑ„๋ฒˆ ํ”Œ๋ž˜๊ทธ ์ดˆ๊ธฐํ™” (์ƒˆ ํ•ญ๋ชฉ์ด๋ฏ€๋กœ) numberingGeneratedRef.current = false; isGeneratingRef.current = false; } // ์ตœ์ดˆ initialData ์บก์ฒ˜ (์ดํ›„ ๋ณ€๊ฒฝ๋˜์–ด๋„ ์ด ๊ฐ’ ์‚ฌ์šฉ) if (initialData && Object.keys(initialData).length > 0) { capturedInitialData.current = JSON.parse(JSON.stringify(initialData)); // ๊นŠ์€ ๋ณต์‚ฌ lastInitializedId.current = currentIdString; // console.log("[UniversalFormModal] ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ ์บก์ฒ˜:", capturedInitialData.current); } // console.log("[UniversalFormModal] initializeForm ํ˜ธ์ถœ ์˜ˆ์ •"); hasInitialized.current = true; initializeForm(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [initialData]); // initialData ์ „์ฒด ๋ณ€๊ฒฝ ์‹œ ์žฌ์ดˆ๊ธฐํ™” // config ๋ณ€๊ฒฝ ์‹œ์—๋งŒ ์žฌ์ดˆ๊ธฐํ™” (initialData ๋ณ€๊ฒฝ์€ ๋ฌด์‹œ) - ์ฑ„๋ฒˆ๊ทœ์น™ ์ œ์™ธ useEffect(() => { if (!hasInitialized.current) return; // ์ตœ์ดˆ ์ดˆ๊ธฐํ™” ์ „์ด๋ฉด ์Šคํ‚ต // console.log("[useEffect config ๋ณ€๊ฒฝ] ์žฌ์ดˆ๊ธฐํ™” ์Šคํ‚ต (์ฑ„๋ฒˆ ์ค‘๋ณต ๋ฐฉ์ง€)"); // initializeForm(); // ์ฃผ์„ ์ฒ˜๋ฆฌ - config ๋ณ€๊ฒฝ ์‹œ ์žฌ์ดˆ๊ธฐํ™” ์•ˆ ํ•จ (์ฑ„๋ฒˆ ์ค‘๋ณต ๋ฐฉ์ง€) // eslint-disable-next-line react-hooks/exhaustive-deps }, [config]); // ์ปดํฌ๋„ŒํŠธ unmount ์‹œ ์ฑ„๋ฒˆ ํ”Œ๋ž˜๊ทธ ์ดˆ๊ธฐํ™” useEffect(() => { return () => { // console.log("[์ฑ„๋ฒˆ] ์ปดํฌ๋„ŒํŠธ unmount - ํ”Œ๋ž˜๊ทธ ์ดˆ๊ธฐํ™”"); numberingGeneratedRef.current = false; isGeneratingRef.current = false; }; }, []); // ๐Ÿ†• beforeFormSave ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ - ButtonPrimary ์ €์žฅ ์‹œ formData๋ฅผ ์ „๋‹ฌ // ์„ค์ •๋œ ํ•„๋“œ(columnName)๋งŒ ๋ณ‘ํ•ฉํ•˜์—ฌ ์˜๋„์น˜ ์•Š์€ ๋ฎ์–ด์“ฐ๊ธฐ ๋ฐฉ์ง€ useEffect(() => { const handleBeforeFormSave = (event: Event) => { if (!(event instanceof CustomEvent) || !event.detail?.formData) return; // ์„ค์ •์— ์ •์˜๋œ ํ•„๋“œ columnName ๋ชฉ๋ก ์ˆ˜์ง‘ const configuredFields = new Set(); config.sections.forEach((section) => { (section.fields || []).forEach((field) => { if (field.columnName) { configuredFields.add(field.columnName); } }); }); console.log("[UniversalFormModal] beforeFormSave ์ด๋ฒคํŠธ ์ˆ˜์‹ "); console.log("[UniversalFormModal] ์„ค์ •๋œ ํ•„๋“œ ๋ชฉ๋ก:", Array.from(configuredFields)); // ๐Ÿ†• ์‹œ์Šคํ…œ ํ•„๋“œ ๋ณ‘ํ•ฉ: id๋Š” ์„ค์ • ์—ฌ๋ถ€์™€ ๊ด€๊ณ„์—†์ด ํ•ญ์ƒ ์ „๋‹ฌ (UPDATE/INSERT ํŒ๋‹จ์šฉ) // - ์‹ ๊ทœ ๋“ฑ๋ก: formData.id๊ฐ€ ์—†์œผ๋ฏ€๋กœ ์˜ํ–ฅ ์—†์Œ // - ํŽธ์ง‘ ๋ชจ๋“œ: formData.id๊ฐ€ ์žˆ์œผ๋ฉด ๋ฉ”์ธ ํ…Œ์ด๋ธ” UPDATE์— ์‚ฌ์šฉ if (formData.id !== undefined && formData.id !== null && formData.id !== "") { event.detail.formData.id = formData.id; console.log(`[UniversalFormModal] ์‹œ์Šคํ…œ ํ•„๋“œ ๋ณ‘ํ•ฉ: id =`, formData.id); } // UniversalFormModal์— ์„ค์ •๋œ ํ•„๋“œ๋งŒ ๋ณ‘ํ•ฉ (์ฑ„๋ฒˆ ๊ทœ์น™ ํฌํ•จ) // ์™ธ๋ถ€ formData์— ์ด๋ฏธ ๊ฐ’์ด ์žˆ์–ด๋„ UniversalFormModal ๊ฐ’์œผ๋กœ ๋ฎ์–ด์”€ // (UniversalFormModal์ด ํ•ด๋‹น ํ•„๋“œ์˜ ์ฃผ์ธ์ด๋ฏ€๋กœ) for (const [key, value] of Object.entries(formData)) { // ์„ค์ •์— ์ •์˜๋œ ํ•„๋“œ ๋˜๋Š” ์ฑ„๋ฒˆ ๊ทœ์น™ ID ํ•„๋“œ๋งŒ ๋ณ‘ํ•ฉ const isConfiguredField = configuredFields.has(key); const isNumberingRuleId = key.endsWith("_numberingRuleId"); if (isConfiguredField || isNumberingRuleId) { if (value !== undefined && value !== null && value !== "") { event.detail.formData[key] = value; console.log(`[UniversalFormModal] ํ•„๋“œ ๋ณ‘ํ•ฉ: ${key} =`, value); } } } // ๋ฐ˜๋ณต ์„น์…˜ ๋ฐ์ดํ„ฐ๋„ ๋ณ‘ํ•ฉ (ํ•„์š”ํ•œ ๊ฒฝ์šฐ) if (Object.keys(repeatSections).length > 0) { for (const [sectionId, items] of Object.entries(repeatSections)) { const sectionKey = `_repeatSection_${sectionId}`; event.detail.formData[sectionKey] = items; console.log(`[UniversalFormModal] ๋ฐ˜๋ณต ์„น์…˜ ๋ณ‘ํ•ฉ: ${sectionKey}`, items); } } // ๐Ÿ†• ํ…Œ์ด๋ธ” ์„น์…˜ ๋ฐ์ดํ„ฐ ๋ณ‘ํ•ฉ (ํ’ˆ๋ชฉ ๋ฆฌ์ŠคํŠธ ๋“ฑ) for (const [key, value] of Object.entries(formData)) { if (key.startsWith("_tableSection_") && Array.isArray(value)) { event.detail.formData[key] = value; console.log(`[UniversalFormModal] ํ…Œ์ด๋ธ” ์„น์…˜ ๋ณ‘ํ•ฉ: ${key}, ${value.length}๊ฐœ ํ•ญ๋ชฉ`); } } // ๐Ÿ†• ์ˆ˜์ • ๋ชจ๋“œ: ์›๋ณธ ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ ์ „๋‹ฌ (UPDATE/DELETE ์ถ”์ ์šฉ) if (originalGroupedData.length > 0) { event.detail.formData._originalGroupedData = originalGroupedData; console.log(`[UniversalFormModal] ์›๋ณธ ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ ๋ณ‘ํ•ฉ: ${originalGroupedData.length}๊ฐœ`); } }; window.addEventListener("beforeFormSave", handleBeforeFormSave as EventListener); return () => { window.removeEventListener("beforeFormSave", handleBeforeFormSave as EventListener); }; }, [formData, repeatSections, config.sections, originalGroupedData]); // ๐Ÿ†• ์ˆ˜์ • ๋ชจ๋“œ: _groupedData๊ฐ€ ์žˆ์œผ๋ฉด ํ…Œ์ด๋ธ” ์„น์…˜ ์ดˆ๊ธฐํ™” useEffect(() => { if (!_groupedData || _groupedData.length === 0) return; if (groupedDataInitializedRef.current) return; // ์ด๋ฏธ ์ดˆ๊ธฐํ™”๋จ // ํ…Œ์ด๋ธ” ํƒ€์ž… ์„น์…˜ ์ฐพ๊ธฐ const tableSection = config.sections.find((s) => s.type === "table"); if (!tableSection) { return; } // ์›๋ณธ ๋ฐ์ดํ„ฐ ์ €์žฅ (์ˆ˜์ •/์‚ญ์ œ ์ถ”์ ์šฉ) setOriginalGroupedData(JSON.parse(JSON.stringify(_groupedData))); // ํ…Œ์ด๋ธ” ์„น์…˜ ๋ฐ์ดํ„ฐ ์„ค์ • const tableSectionKey = `_tableSection_${tableSection.id}`; setFormData((prev) => ({ ...prev, [tableSectionKey]: _groupedData, })); groupedDataInitializedRef.current = true; }, [_groupedData, config.sections]); // ํ•„๋“œ ๋ ˆ๋ฒจ linkedFieldGroup ๋ฐ์ดํ„ฐ ๋กœ๋“œ useEffect(() => { const loadData = async () => { const tablesToLoad = new Set(); // ๋ชจ๋“  ์„น์…˜์˜ ํ•„๋“œ์—์„œ linkedFieldGroup.sourceTable ์ˆ˜์ง‘ config.sections.forEach((section) => { (section.fields || []).forEach((field) => { if (field.linkedFieldGroup?.enabled && field.linkedFieldGroup?.sourceTable) { tablesToLoad.add(field.linkedFieldGroup.sourceTable); } }); }); // ๊ฐ ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ ๋กœ๋“œ for (const tableName of tablesToLoad) { if (!linkedFieldDataCache[tableName]) { await loadLinkedFieldData(tableName); } } }; loadData(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [config.sections]); // ์ฑ„๋ฒˆ๊ทœ์น™ ์ž๋™ ์ƒ์„ฑ (์ค‘๋ณต ํ˜ธ์ถœ ๋ฐฉ์ง€) // ์ค‘์š”: initializeForm์—์„œ ํ˜ธ์ถœ๋˜๋ฏ€๋กœ ๋ฐ˜๋“œ์‹œ initializeForm๋ณด๋‹ค ๋จผ์ € ์„ ์–ธํ•ด์•ผ ํ•จ const numberingGeneratedRef = useRef(false); const isGeneratingRef = useRef(false); // ์ง„ํ–‰ ์ค‘ ํ”Œ๋ž˜๊ทธ ์ถ”๊ฐ€ const generateNumberingValues = useCallback( async (currentFormData: FormDataState) => { // ์ด๋ฏธ ์ƒ์„ฑ๋˜์—ˆ๊ฑฐ๋‚˜ ์ง„ํ–‰ ์ค‘์ด๋ฉด ์Šคํ‚ต if (numberingGeneratedRef.current) { console.log("[์ฑ„๋ฒˆ] ์ด๋ฏธ ์ƒ์„ฑ๋จ - ์Šคํ‚ต"); return; } if (isGeneratingRef.current) { // console.log("[์ฑ„๋ฒˆ] ์ƒ์„ฑ ์ง„ํ–‰ ์ค‘ - ์Šคํ‚ต"); return; } isGeneratingRef.current = true; // ์ง„ํ–‰ ์ค‘ ํ‘œ์‹œ // console.log("[์ฑ„๋ฒˆ] ์ƒ์„ฑ ์‹œ์ž‘", { sectionsCount: config.sections.length }); const updatedData = { ...currentFormData }; let hasChanges = false; for (const section of config.sections) { // console.log("[์ฑ„๋ฒˆ] ์„น์…˜ ๊ฒ€์‚ฌ:", section.title, { type: section.type, repeatable: section.repeatable, fieldsCount: section.fields?.length }); if (section.repeatable || section.type === "table") continue; for (const field of section.fields || []) { // generateOnOpen์€ ๊ธฐ๋ณธ๊ฐ’ true (undefined์ผ ๊ฒฝ์šฐ true๋กœ ์ฒ˜๋ฆฌ) const shouldGenerateOnOpen = field.numberingRule?.generateOnOpen !== false; // console.log("[์ฑ„๋ฒˆ] ํ•„๋“œ ๊ฒ€์‚ฌ:", field.columnName, { // hasNumberingRule: !!field.numberingRule, // enabled: field.numberingRule?.enabled, // generateOnOpen: field.numberingRule?.generateOnOpen, // shouldGenerateOnOpen, // ruleId: field.numberingRule?.ruleId, // currentValue: updatedData[field.columnName], // }); if ( field.numberingRule?.enabled && shouldGenerateOnOpen && field.numberingRule?.ruleId && !updatedData[field.columnName] ) { try { // console.log(`[์ฑ„๋ฒˆ ๋ฏธ๋ฆฌ๋ณด๊ธฐ API ํ˜ธ์ถœ] ${field.columnName}, ruleId: ${field.numberingRule.ruleId}`); // generateOnOpen: ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋งŒ ํ‘œ์‹œ (DB ์‹œํ€€์Šค ์ฆ๊ฐ€ ์•ˆ ํ•จ) const response = await previewNumberingCode(field.numberingRule.ruleId); if (response.success && response.data?.generatedCode) { const generatedCode = response.data.generatedCode; updatedData[field.columnName] = generatedCode; // ์ €์žฅ ์‹œ ์‹ค์ œ ํ• ๋‹น์„ ์œ„ํ•ด ruleId ์ €์žฅ (TextInput๊ณผ ๋™์ผํ•œ ํ‚ค ํ˜•์‹) const ruleIdKey = `${field.columnName}_numberingRuleId`; updatedData[ruleIdKey] = field.numberingRule.ruleId; // ์›๋ณธ ์ฑ„๋ฒˆ ๊ฐ’ ์ €์žฅ (์ˆ˜๋™ ๋ชจ๋“œ ๊ฐ์ง€์šฉ) setNumberingOriginalValues((prev) => ({ ...prev, [field.columnName]: generatedCode, })); hasChanges = true; numberingGeneratedRef.current = true; // ์ƒ์„ฑ ์™„๋ฃŒ ํ‘œ์‹œ // console.log( // `[์ฑ„๋ฒˆ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์™„๋ฃŒ] ${field.columnName} = ${response.data.generatedCode} (์ €์žฅ ์‹œ ์‹ค์ œ ํ• ๋‹น)`, // ); // console.log(`[์ฑ„๋ฒˆ ๊ทœ์น™ ID ์ €์žฅ] ${ruleIdKey} = ${field.numberingRule.ruleId}`); // ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์—๋„ ruleId ์ „๋‹ฌ (ModalRepeaterTable โ†’ ScreenModal) if (onChange) { onChange({ ...updatedData, [ruleIdKey]: field.numberingRule.ruleId, }); // console.log(`[์ฑ„๋ฒˆ] ๋ถ€๋ชจ์—๊ฒŒ ruleId ์ „๋‹ฌ: ${ruleIdKey}`); } } } catch (error) { console.error(`์ฑ„๋ฒˆ๊ทœ์น™ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์‹คํŒจ (${field.columnName}):`, error); } } } } isGeneratingRef.current = false; // ์ง„ํ–‰ ์™„๋ฃŒ if (hasChanges) { setFormData(updatedData); } }, [config, onChange], ); // ํผ ์ดˆ๊ธฐํ™” const initializeForm = useCallback(async () => { // console.log("[initializeForm] ์‹œ์ž‘"); // ์บก์ฒ˜๋œ initialData ์‚ฌ์šฉ (props๋กœ ์ „๋‹ฌ๋œ initialData๊ฐ€ ์•„๋‹Œ) const effectiveInitialData = capturedInitialData.current || initialData; // console.log("[initializeForm] ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ:", { // capturedInitialData: capturedInitialData.current, // initialData: initialData, // effectiveInitialData: effectiveInitialData, // hasData: effectiveInitialData && Object.keys(effectiveInitialData).length > 0, // }); const newFormData: FormDataState = {}; const newRepeatSections: { [sectionId: string]: RepeatSectionItem[] } = {}; const newCollapsed = new Set(); const newActivatedGroups = new Set(); // ์„น์…˜๋ณ„ ์ดˆ๊ธฐํ™” for (const section of config.sections) { // ์ ‘ํž˜ ์ƒํƒœ ์ดˆ๊ธฐํ™” if (section.defaultCollapsed) { newCollapsed.add(section.id); } if (section.repeatable) { // ๋ฐ˜๋ณต ์„น์…˜ ์ดˆ๊ธฐํ™” const minItems = section.repeatConfig?.minItems || 0; const items: RepeatSectionItem[] = []; for (let i = 0; i < minItems; i++) { items.push(createRepeatItem(section, i)); } newRepeatSections[section.id] = items; } else if (section.type === "table") { // ํ…Œ์ด๋ธ” ์„น์…˜์€ ํ•„๋“œ ์ดˆ๊ธฐํ™” ์Šคํ‚ต (TableSectionRenderer์—์„œ ์ฒ˜๋ฆฌ) continue; } else { // ์ผ๋ฐ˜ ์„น์…˜ ํ•„๋“œ ์ดˆ๊ธฐํ™” for (const field of section.fields || []) { // ๊ธฐ๋ณธ๊ฐ’ ์„ค์ • let value = field.defaultValue ?? ""; // ๋ถ€๋ชจ์—์„œ ์ „๋‹ฌ๋ฐ›์€ ๊ฐ’ ์ ์šฉ (receiveFromParent ๋˜๋Š” effectiveInitialData์— ํ•ด๋‹น ๊ฐ’์ด ์žˆ์œผ๋ฉด) if (effectiveInitialData) { const parentField = field.parentFieldName || field.columnName; if (effectiveInitialData[parentField] !== undefined) { // receiveFromParent๊ฐ€ true์ด๊ฑฐ๋‚˜, effectiveInitialData์— ๊ฐ’์ด ์žˆ์œผ๋ฉด ์ ์šฉ if (field.receiveFromParent || value === "" || value === undefined) { value = effectiveInitialData[parentField]; } } } newFormData[field.columnName] = value; } // ์˜ต์…”๋„ ํ•„๋“œ ๊ทธ๋ฃน ์ฒ˜๋ฆฌ if (section.optionalFieldGroups) { for (const group of section.optionalFieldGroups) { const key = `${section.id}-${group.id}`; // ์ˆ˜์ • ๋ชจ๋“œ: triggerField ๊ฐ’์ด triggerValueOnAdd์™€ ์ผ์น˜ํ•˜๋ฉด ๊ทธ๋ฃน ์ž๋™ ํ™œ์„ฑํ™” if (effectiveInitialData && group.triggerField && group.triggerValueOnAdd !== undefined) { const triggerValue = effectiveInitialData[group.triggerField]; if (triggerValue === group.triggerValueOnAdd) { newActivatedGroups.add(key); console.log( `[initializeForm] ์˜ต์…”๋„ ๊ทธ๋ฃน ์ž๋™ ํ™œ์„ฑํ™”: ${key}, triggerField=${group.triggerField}, value=${triggerValue}`, ); // ํ™œ์„ฑํ™”๋œ ๊ทธ๋ฃน์˜ ํ•„๋“œ๊ฐ’๋„ ์ดˆ๊ธฐํ™” for (const field of group.fields || []) { let value = field.defaultValue ?? ""; const parentField = field.parentFieldName || field.columnName; if (effectiveInitialData[parentField] !== undefined) { value = effectiveInitialData[parentField]; } newFormData[field.columnName] = value; } } } // ์‹ ๊ทœ ๋“ฑ๋ก ๋ชจ๋“œ: triggerValueOnRemove๋ฅผ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์„ค์ • if (group.triggerField && group.triggerValueOnRemove !== undefined) { // effectiveInitialData์— ํ•ด๋‹น ๊ฐ’์ด ์—†๋Š” ๊ฒฝ์šฐ์—๋งŒ ๊ธฐ๋ณธ๊ฐ’ ์„ค์ • if (!effectiveInitialData || effectiveInitialData[group.triggerField] === undefined) { newFormData[group.triggerField] = group.triggerValueOnRemove; } } } } } } // ๐Ÿ†• ํ…Œ์ด๋ธ” ์„น์…˜(type: "table") ๋””ํ…Œ์ผ ๋ฐ์ดํ„ฐ ๋กœ๋“œ (๋งˆ์Šคํ„ฐ-๋””ํ…Œ์ผ ๊ตฌ์กฐ) // ์ˆ˜์ • ๋ชจ๋“œ์ผ ๋•Œ ๋””ํ…Œ์ผ ํ…Œ์ด๋ธ”์—์„œ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ if (effectiveInitialData) { console.log("[initializeForm] ํ…Œ์ด๋ธ” ์„น์…˜ ๋””ํ…Œ์ผ ๋กœ๋“œ ์‹œ์ž‘", { sectionsCount: config.sections.length, effectiveInitialDataKeys: Object.keys(effectiveInitialData), }); for (const section of config.sections) { if (section.type !== "table" || !section.tableConfig) { continue; } const tableConfig = section.tableConfig; // editConfig๋Š” ํƒ€์ž…์— ์ •์˜๋˜์ง€ ์•Š์•˜์ง€๋งŒ ๋Ÿฐํƒ€์ž„์— ์กด์žฌํ•  ์ˆ˜ ์žˆ์Œ const editConfig = (tableConfig as any).editConfig; const saveConfig = tableConfig.saveConfig; console.log(`[initializeForm] ํ…Œ์ด๋ธ” ์„น์…˜ ${section.id} ๊ฒ€์‚ฌ:`, { hasEditConfig: !!editConfig, loadOnEdit: editConfig?.loadOnEdit, hasSaveConfig: !!saveConfig, targetTable: saveConfig?.targetTable, linkColumn: editConfig?.linkColumn, }); // ์ˆ˜์ • ๋ชจ๋“œ ๋กœ๋“œ ์„ค์ • ํ™•์ธ (๊ธฐ๋ณธ๊ฐ’: true) if (editConfig?.loadOnEdit === false) { console.log(`[initializeForm] ํ…Œ์ด๋ธ” ์„น์…˜ ${section.id}: loadOnEdit=false, ์Šคํ‚ต`); continue; } // ๋””ํ…Œ์ผ ํ…Œ์ด๋ธ”๊ณผ ์—ฐ๊ฒฐ ์ •๋ณด ํ™•์ธ const detailTable = saveConfig?.targetTable; let linkColumn = editConfig?.linkColumn; if (!detailTable) { console.log(`[initializeForm] ํ…Œ์ด๋ธ” ์„น์…˜ ${section.id}: saveConfig.targetTable ๋ฏธ์„ค์ •, ์Šคํ‚ต`); continue; } // linkColumn์ด ์„ค์ •๋˜์ง€ ์•Š์•˜์œผ๋ฉด, ๋””ํ…Œ์ผ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ์ •๋ณด ์กฐํšŒํ•˜์—ฌ ์ž๋™ ๊ฐ์ง€ if (!linkColumn?.masterField || !linkColumn?.detailField) { try { // ๋งˆ์Šคํ„ฐ ํ…Œ์ด๋ธ”๋ช… ํ™•์ธ (saveConfig์—์„œ) // 1. customApiSave.multiTable.mainTable.tableName (๋‹ค์ค‘ ํ…Œ์ด๋ธ” ์ €์žฅ) // 2. saveConfig.tableName (๋‹จ์ผ ํ…Œ์ด๋ธ” ์ €์žฅ) const masterTable = config.saveConfig?.customApiSave?.multiTable?.mainTable?.tableName || config.saveConfig?.tableName; // ๋””ํ…Œ์ผ ํ…Œ์ด๋ธ”์˜ ์ปฌ๋Ÿผ ๋ชฉ๋ก ์กฐํšŒ const columnsResponse = await apiClient.get(`/table-management/tables/${detailTable}/columns`); if (columnsResponse.data?.success && columnsResponse.data?.data) { // API ์‘๋‹ต ๊ตฌ์กฐ: { success, data: { columns: [...], total, page, ... } } const columnsArray = columnsResponse.data.data.columns || columnsResponse.data.data || []; const detailColumnsData = Array.isArray(columnsArray) ? columnsArray : []; const detailColumns = detailColumnsData.map((col: any) => col.column_name || col.columnName); const masterKeys = Object.keys(effectiveInitialData); console.log(`[initializeForm] ํ…Œ์ด๋ธ” ์„น์…˜ ${section.id}: ์—ฐ๊ฒฐ ํ•„๋“œ ์ž๋™ ๊ฐ์ง€`, { masterTable, detailTable, detailColumnsCount: detailColumnsData.length, }); // ๋ฐฉ๋ฒ• 1: ์—”ํ‹ฐํ‹ฐ ๊ด€๊ณ„ ๊ธฐ๋ฐ˜ ๊ฐ์ง€ (์ •ํ™•) // ๋””ํ…Œ์ผ ํ…Œ์ด๋ธ”์—์„œ ๋งˆ์Šคํ„ฐ ํ…Œ์ด๋ธ”์„ ์ฐธ์กฐํ•˜๋Š” ์—”ํ‹ฐํ‹ฐ ์ปฌ๋Ÿผ ์ฐพ๊ธฐ if (masterTable) { for (const col of detailColumnsData) { const colName = col.column_name || col.columnName; const inputType = col.input_type || col.inputType; // ์—”ํ‹ฐํ‹ฐ ํƒ€์ž… ์ปฌ๋Ÿผ ํ™•์ธ if (inputType === "entity") { // reference_table ๋˜๋Š” detail_settings์—์„œ ์ฐธ์กฐ ํ…Œ์ด๋ธ” ํ™•์ธ let refTable = col.reference_table || col.referenceTable; // detail_settings์—์„œ referenceTable ํ™•์ธ if (!refTable && col.detail_settings) { try { const settings = typeof col.detail_settings === "string" ? JSON.parse(col.detail_settings) : col.detail_settings; refTable = settings.referenceTable; } catch { // JSON ํŒŒ์‹ฑ ์‹คํŒจ ๋ฌด์‹œ } } // ๋งˆ์Šคํ„ฐ ํ…Œ์ด๋ธ”์„ ์ฐธ์กฐํ•˜๋Š” ์ปฌ๋Ÿผ ๋ฐœ๊ฒฌ if (refTable === masterTable) { // ์ฐธ์กฐ ์ปฌ๋Ÿผ ํ™•์ธ (๋งˆ์Šคํ„ฐ ํ…Œ์ด๋ธ”์˜ ์–ด๋–ค ์ปฌ๋Ÿผ์„ ์ฐธ์กฐํ•˜๋Š”์ง€) let refColumn = col.reference_column || col.referenceColumn; if (!refColumn && col.detail_settings) { try { const settings = typeof col.detail_settings === "string" ? JSON.parse(col.detail_settings) : col.detail_settings; refColumn = settings.referenceColumn; } catch { // JSON ํŒŒ์‹ฑ ์‹คํŒจ ๋ฌด์‹œ } } // ๋งˆ์Šคํ„ฐ ๋ฐ์ดํ„ฐ์— ํ•ด๋‹น ์ปฌ๋Ÿผ ๊ฐ’์ด ์žˆ๋Š”์ง€ ํ™•์ธ if (refColumn && effectiveInitialData[refColumn]) { console.log( `[initializeForm] ํ…Œ์ด๋ธ” ์„น์…˜ ${section.id}: ์—”ํ‹ฐํ‹ฐ ๊ด€๊ณ„ ๊ฐ์ง€ - ${colName} โ†’ ${masterTable}.${refColumn}`, ); linkColumn = { masterField: refColumn, detailField: colName }; break; } } } } } // ๋ฐฉ๋ฒ• 2: ๊ณตํ†ต ์ปฌ๋Ÿผ ํŒจํ„ด ๊ธฐ๋ฐ˜ ๊ฐ์ง€ (ํด๋ฐฑ) // ์—”ํ‹ฐํ‹ฐ ๊ด€๊ณ„๊ฐ€ ์—†์œผ๋ฉด ๊ณตํ†ต ์ปฌ๋Ÿผ๋ช… ํŒจํ„ด์œผ๋กœ ์ฐพ๊ธฐ if (!linkColumn) { const priorityPatterns = ["_no", "_number", "_code", "_id"]; for (const pattern of priorityPatterns) { for (const masterKey of masterKeys) { if ( masterKey.endsWith(pattern) && detailColumns.includes(masterKey) && effectiveInitialData[masterKey] && masterKey !== "id" && masterKey !== "company_code" ) { console.log(`[initializeForm] ํ…Œ์ด๋ธ” ์„น์…˜ ${section.id}: ๊ณตํ†ต ์ปฌ๋Ÿผ ํŒจํ„ด ๊ฐ์ง€ - ${masterKey}`); linkColumn = { masterField: masterKey, detailField: masterKey }; break; } } if (linkColumn) break; } } // ๋ฐฉ๋ฒ• 3: ์ผ๋ฐ˜ ๊ณตํ†ต ์ปฌ๋Ÿผ (๋งˆ์ง€๋ง‰ ํด๋ฐฑ) if (!linkColumn) { for (const masterKey of masterKeys) { if ( detailColumns.includes(masterKey) && effectiveInitialData[masterKey] && masterKey !== "id" && masterKey !== "company_code" && !masterKey.startsWith("__") ) { console.log(`[initializeForm] ํ…Œ์ด๋ธ” ์„น์…˜ ${section.id}: ๊ณตํ†ต ์ปฌ๋Ÿผ ๊ฐ์ง€ - ${masterKey}`); linkColumn = { masterField: masterKey, detailField: masterKey }; break; } } } } } catch (error) { console.warn(`[initializeForm] ํ…Œ์ด๋ธ” ์„น์…˜ ${section.id}: ์ปฌ๋Ÿผ ์ •๋ณด ์กฐํšŒ ์‹คํŒจ`, error); } } if (!linkColumn?.masterField || !linkColumn?.detailField) { console.log(`[initializeForm] ํ…Œ์ด๋ธ” ์„น์…˜ ${section.id}: linkColumn ๋ฏธ์„ค์ • ๋ฐ ์ž๋™ ๊ฐ์ง€ ์‹คํŒจ, ์Šคํ‚ต`); continue; } // ๋งˆ์Šคํ„ฐ ํ…Œ์ด๋ธ”์˜ ์—ฐ๊ฒฐ ํ•„๋“œ ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ const masterValue = effectiveInitialData[linkColumn.masterField]; if (!masterValue) { console.log( `[initializeForm] ํ…Œ์ด๋ธ” ์„น์…˜ ${section.id}: masterField(${linkColumn.masterField}) ๊ฐ’ ์—†์Œ, ์Šคํ‚ต`, ); continue; } try { console.log(`[initializeForm] ํ…Œ์ด๋ธ” ์„น์…˜ ${section.id}: ๋””ํ…Œ์ผ ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์‹œ์ž‘`, { detailTable, linkColumn, masterValue, }); // ๋””ํ…Œ์ผ ํ…Œ์ด๋ธ”์—์„œ ๋ฐ์ดํ„ฐ ์กฐํšŒ // operator: "equals"๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ •ํ™•ํžˆ ์ผ์น˜ํ•˜๋Š” ๊ฐ’๋งŒ ๊ฒ€์ƒ‰ (์—”ํ‹ฐํ‹ฐ ํƒ€์ž… ์ปฌ๋Ÿผ์—์„œ ์ค‘์š”) const searchCondition: Record = { [linkColumn.detailField]: { value: masterValue, operator: "equals" }, }; console.log( `[initializeForm] ํ…Œ์ด๋ธ” ์„น์…˜ ${section.id}: API ์š”์ฒญ - URL: /table-management/tables/${detailTable}/data`, ); console.log( `[initializeForm] ํ…Œ์ด๋ธ” ์„น์…˜ ${section.id}: API ์š”์ฒญ - search:`, JSON.stringify(searchCondition), ); const response = await apiClient.post(`/table-management/tables/${detailTable}/data`, { search: searchCondition, // filters๊ฐ€ ์•„๋‹Œ search๋กœ ์ „๋‹ฌ page: 1, size: 1000, // pageSize๊ฐ€ ์•„๋‹Œ size๋กœ ์ „๋‹ฌ autoFilter: { enabled: true }, // ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ ํ•„ํ„ฐ ์ ์šฉ }); console.log( `[initializeForm] ํ…Œ์ด๋ธ” ์„น์…˜ ${section.id}: API ์‘๋‹ต - success: ${response.data?.success}, total: ${response.data?.data?.total}, dataLength: ${response.data?.data?.data?.length}`, ); if (response.data?.success) { // ๋‹ค์–‘ํ•œ ์‘๋‹ต ๊ตฌ์กฐ ์ฒ˜๋ฆฌ let items: any[] = []; const data = response.data.data; if (Array.isArray(data)) { items = data; } else if (data?.items && Array.isArray(data.items)) { items = data.items; } else if (data?.rows && Array.isArray(data.rows)) { items = data.rows; } else if (data?.data && Array.isArray(data.data)) { items = data.data; } console.log(`[initializeForm] ํ…Œ์ด๋ธ” ์„น์…˜ ${section.id}: ${items.length}๊ฑด ๋กœ๋“œ๋จ`, items); // ํ…Œ์ด๋ธ” ์„น์…˜ ๋ฐ์ดํ„ฐ๋ฅผ formData์— ์ €์žฅ (TableSectionRenderer์—์„œ ์‚ฌ์šฉ) const tableSectionKey = `__tableSection_${section.id}`; newFormData[tableSectionKey] = items; console.log(`[initializeForm] ํ…Œ์ด๋ธ” ์„น์…˜ ${section.id}: formData[${tableSectionKey}]์— ์ €์žฅ๋จ`); } } catch (error) { console.error(`[initializeForm] ํ…Œ์ด๋ธ” ์„น์…˜ ${section.id}: ๋””ํ…Œ์ผ ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์‹คํŒจ`, error); } } } setFormData(newFormData); setRepeatSections(newRepeatSections); setCollapsedSections(newCollapsed); setActivatedOptionalFieldGroups(newActivatedGroups); setOriginalData(effectiveInitialData || {}); // ์ˆ˜์ • ๋ชจ๋“œ์—์„œ ์„œ๋ธŒ ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ ๋กœ๋“œ (๊ฒธ์ง ๋“ฑ) const multiTable = config.saveConfig?.customApiSave?.multiTable; if (multiTable && effectiveInitialData) { const pkColumn = multiTable.mainTable?.primaryKeyColumn; const pkValue = effectiveInitialData[pkColumn]; // PK ๊ฐ’์ด ์žˆ์œผ๋ฉด ์ˆ˜์ • ๋ชจ๋“œ๋กœ ํŒ๋‹จ if (pkValue) { console.log("[initializeForm] ์ˆ˜์ • ๋ชจ๋“œ - ์„œ๋ธŒ ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์‹œ์ž‘"); for (const subTableConfig of multiTable.subTables || []) { // loadOnEdit ์˜ต์…˜์ด ํ™œ์„ฑํ™”๋œ ๊ฒฝ์šฐ์—๋งŒ ๋กœ๋“œ if (!subTableConfig.enabled || !subTableConfig.options?.loadOnEdit) { continue; } const { tableName, linkColumn, repeatSectionId, fieldMappings, options } = subTableConfig; if (!tableName || !linkColumn?.subColumn || !repeatSectionId) { continue; } try { // ์„œ๋ธŒ ํ…Œ์ด๋ธ”์—์„œ ๋ฐ์ดํ„ฐ ์กฐํšŒ const filters: Record = { [linkColumn.subColumn]: pkValue, }; // ์„œ๋ธŒ ํ•ญ๋ชฉ๋งŒ ๋กœ๋“œ (๋ฉ”์ธ ํ•ญ๋ชฉ ์ œ์™ธ) if (options?.loadOnlySubItems && options?.mainMarkerColumn) { filters[options.mainMarkerColumn] = options.subMarkerValue ?? false; } console.log(`[initializeForm] ์„œ๋ธŒ ํ…Œ์ด๋ธ” ${tableName} ์กฐํšŒ:`, filters); const response = await apiClient.get(`/table-management/tables/${tableName}/data`, { params: { filters: JSON.stringify(filters), page: 1, pageSize: 100, }, }); if (response.data?.success && response.data?.data?.items) { const subItems = response.data.data.items; console.log(`[initializeForm] ์„œ๋ธŒ ํ…Œ์ด๋ธ” ${tableName} ๋ฐ์ดํ„ฐ ${subItems.length}๊ฑด ๋กœ๋“œ๋จ`); // ์—ญ๋งคํ•‘: ์„œ๋ธŒ ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ โ†’ ๋ฐ˜๋ณต ์„น์…˜ ๋ฐ์ดํ„ฐ const repeatItems: RepeatSectionItem[] = subItems.map((item: any, index: number) => { const repeatItem: RepeatSectionItem = { _id: generateUniqueId("repeat"), _index: index, _originalData: item, // ์›๋ณธ ๋ฐ์ดํ„ฐ ๋ณด๊ด€ (์ˆ˜์ • ์‹œ ํ•„์š”) }; // ํ•„๋“œ ๋งคํ•‘ ์—ญ๋ณ€ํ™˜ (targetColumn โ†’ formField) for (const mapping of fieldMappings || []) { if (mapping.formField && mapping.targetColumn) { repeatItem[mapping.formField] = item[mapping.targetColumn]; } } return repeatItem; }); // ๋ฐ˜๋ณต ์„น์…˜์— ๋ฐ์ดํ„ฐ ์„ค์ • newRepeatSections[repeatSectionId] = repeatItems; setRepeatSections({ ...newRepeatSections }); console.log(`[initializeForm] ๋ฐ˜๋ณต ์„น์…˜ ${repeatSectionId}์— ${repeatItems.length}๊ฑด ์„ค์ •`); } } catch (error) { console.error(`[initializeForm] ์„œ๋ธŒ ํ…Œ์ด๋ธ” ${tableName} ๋กœ๋“œ ์‹คํŒจ:`, error); } } } } // ์ฑ„๋ฒˆ๊ทœ์น™ ์ž๋™ ์ƒ์„ฑ // console.log("[initializeForm] generateNumberingValues ํ˜ธ์ถœ"); await generateNumberingValues(newFormData); // console.log("[initializeForm] ์™„๋ฃŒ"); // eslint-disable-next-line react-hooks/exhaustive-deps }, [config]); // initialData๋Š” ์˜์กด์„ฑ์—์„œ ์ œ๊ฑฐ (capturedInitialData.current ์‚ฌ์šฉ) // ๋ฐ˜๋ณต ์„น์…˜ ์•„์ดํ…œ ์ƒ์„ฑ const createRepeatItem = (section: FormSectionConfig, index: number): RepeatSectionItem => { const item: RepeatSectionItem = { _id: generateUniqueId("repeat"), _index: index, }; for (const field of section.fields || []) { item[field.columnName] = field.defaultValue ?? ""; } return item; }; // ํ•„๋“œ ๊ฐ’ ๋ณ€๊ฒฝ ํ•ธ๋“ค๋Ÿฌ const handleFieldChange = useCallback( (columnName: string, value: any) => { // ์ฑ„๋ฒˆ๊ทœ์น™ ํ•„๋“œ์˜ ์ˆ˜๋™ ๋ชจ๋“œ ๊ฐ์ง€ const originalNumberingValue = numberingOriginalValues[columnName]; const ruleIdKey = `${columnName}_numberingRuleId`; // ํ•ด๋‹น ํ•„๋“œ์˜ ์ฑ„๋ฒˆ๊ทœ์น™ ์„ค์ • ์ฐพ๊ธฐ let fieldConfig: FormFieldConfig | undefined; for (const section of config.sections) { if (section.type === "table" || section.repeatable) continue; fieldConfig = section.fields?.find((f) => f.columnName === columnName); if (fieldConfig) break; // ์˜ต์…”๋„ ํ•„๋“œ ๊ทธ๋ฃน์—์„œ๋„ ์ฐพ๊ธฐ for (const group of section.optionalFieldGroups || []) { fieldConfig = group.fields?.find((f) => f.columnName === columnName); if (fieldConfig) break; } if (fieldConfig) break; } setFormData((prev) => { const newData = { ...prev, [columnName]: value }; // ์ฑ„๋ฒˆ๊ทœ์น™์ด ํ™œ์„ฑํ™”๋œ ํ•„๋“œ์ด๊ณ , "์‚ฌ์šฉ์ž ์ˆ˜์ • ๊ฐ€๋Šฅ"์ด ON์ธ ๊ฒฝ์šฐ if (fieldConfig?.numberingRule?.enabled && fieldConfig?.numberingRule?.editable && originalNumberingValue) { // ์‚ฌ์šฉ์ž๊ฐ€ ๊ฐ’์„ ์ˆ˜์ •ํ–ˆ์œผ๋ฉด (์›๋ณธ๊ณผ ๋‹ค๋ฅด๋ฉด) ruleId ์ œ๊ฑฐ โ†’ ์ˆ˜๋™ ๋ชจ๋“œ if (value !== originalNumberingValue) { delete newData[ruleIdKey]; console.log(`[์ฑ„๋ฒˆ ์ˆ˜๋™ ๋ชจ๋“œ] ${columnName}: ์‚ฌ์šฉ์ž๊ฐ€ ๊ฐ’ ์ˆ˜์ • โ†’ ruleId ์ œ๊ฑฐ`); } else { // ์›๋ณธ ๊ฐ’์œผ๋กœ ๋ณต๊ตฌํ•˜๋ฉด ruleId ๋ณต๊ตฌ โ†’ ์ž๋™ ๋ชจ๋“œ if (fieldConfig.numberingRule.ruleId) { newData[ruleIdKey] = fieldConfig.numberingRule.ruleId; console.log(`[์ฑ„๋ฒˆ ์ž๋™ ๋ชจ๋“œ] ${columnName}: ์›๋ณธ ๊ฐ’ ๋ณต๊ตฌ โ†’ ruleId ๋ณต๊ตฌ`); } } } // onChange๋Š” ๋ Œ๋”๋ง ์™ธ๋ถ€์—์„œ ํ˜ธ์ถœํ•ด์•ผ ํ•จ (setTimeout ์‚ฌ์šฉ) if (onChange) { setTimeout(() => onChange(newData), 0); } return newData; }); }, [onChange, numberingOriginalValues, config.sections], ); // ๋ฐ˜๋ณต ์„น์…˜ ํ•„๋“œ ๊ฐ’ ๋ณ€๊ฒฝ ํ•ธ๋“ค๋Ÿฌ const handleRepeatFieldChange = useCallback((sectionId: string, itemId: string, columnName: string, value: any) => { setRepeatSections((prev) => { const items = prev[sectionId] || []; const newItems = items.map((item) => (item._id === itemId ? { ...item, [columnName]: value } : item)); return { ...prev, [sectionId]: newItems }; }); }, []); // ๋ฐ˜๋ณต ์„น์…˜ ์•„์ดํ…œ ์ถ”๊ฐ€ const handleAddRepeatItem = useCallback( (sectionId: string) => { const section = config.sections.find((s) => s.id === sectionId); if (!section) return; const maxItems = section.repeatConfig?.maxItems || 10; setRepeatSections((prev) => { const items = prev[sectionId] || []; if (items.length >= maxItems) { toast.error(`์ตœ๋Œ€ ${maxItems}๊ฐœ๊นŒ์ง€๋งŒ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.`); return prev; } const newItem = createRepeatItem(section, items.length); return { ...prev, [sectionId]: [...items, newItem] }; }); }, [config], ); // ๋ฐ˜๋ณต ์„น์…˜ ์•„์ดํ…œ ์‚ญ์ œ const handleRemoveRepeatItem = useCallback( (sectionId: string, itemId: string) => { const section = config.sections.find((s) => s.id === sectionId); if (!section) return; const minItems = section.repeatConfig?.minItems || 0; setRepeatSections((prev) => { const items = prev[sectionId] || []; if (items.length <= minItems) { toast.error(`์ตœ์†Œ ${minItems}๊ฐœ๋Š” ์œ ์ง€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.`); return prev; } const newItems = items.filter((item) => item._id !== itemId).map((item, index) => ({ ...item, _index: index })); return { ...prev, [sectionId]: newItems }; }); setDeleteDialog({ open: false, sectionId: "", itemId: "" }); }, [config], ); // ์„น์…˜ ์ ‘ํž˜ ํ† ๊ธ€ const toggleSectionCollapse = useCallback((sectionId: string) => { setCollapsedSections((prev) => { const newSet = new Set(prev); if (newSet.has(sectionId)) { newSet.delete(sectionId); } else { newSet.add(sectionId); } return newSet; }); }, []); // ์˜ต์…”๋„ ํ•„๋“œ ๊ทธ๋ฃน ํ™œ์„ฑํ™” const activateOptionalFieldGroup = useCallback( (sectionId: string, groupId: string) => { const section = config.sections.find((s) => s.id === sectionId); const group = section?.optionalFieldGroups?.find((g) => g.id === groupId); if (!group) return; const key = `${sectionId}-${groupId}`; setActivatedOptionalFieldGroups((prev) => { const newSet = new Set(prev); newSet.add(key); return newSet; }); // ์—ฐ๋™ ํ•„๋“œ ๊ฐ’ ๋ณ€๊ฒฝ (์ถ”๊ฐ€ ์‹œ) if (group.triggerField && group.triggerValueOnAdd !== undefined) { handleFieldChange(group.triggerField, group.triggerValueOnAdd); } }, [config, handleFieldChange], ); // ์˜ต์…”๋„ ํ•„๋“œ ๊ทธ๋ฃน ๋น„ํ™œ์„ฑํ™” const deactivateOptionalFieldGroup = useCallback( (sectionId: string, groupId: string) => { const section = config.sections.find((s) => s.id === sectionId); const group = section?.optionalFieldGroups?.find((g) => g.id === groupId); if (!group) return; const key = `${sectionId}-${groupId}`; setActivatedOptionalFieldGroups((prev) => { const newSet = new Set(prev); newSet.delete(key); return newSet; }); // ์—ฐ๋™ ํ•„๋“œ ๊ฐ’ ๋ณ€๊ฒฝ (์ œ๊ฑฐ ์‹œ) if (group.triggerField && group.triggerValueOnRemove !== undefined) { handleFieldChange(group.triggerField, group.triggerValueOnRemove); } // ์˜ต์…”๋„ ํ•„๋“œ ๊ทธ๋ฃน ํ•„๋“œ ๊ฐ’ ์ดˆ๊ธฐํ™” (group.fields || []).forEach((field) => { handleFieldChange(field.columnName, field.defaultValue || ""); }); }, [config, handleFieldChange], ); // Select ์˜ต์…˜ ๋กœ๋“œ const loadSelectOptions = useCallback( async (fieldId: string, optionConfig: SelectOptionConfig): Promise<{ value: string; label: string }[]> => { // ์บ์‹œ ํ™•์ธ if (selectOptionsCache[fieldId]) { return selectOptionsCache[fieldId]; } let options: { value: string; label: string }[] = []; try { if (optionConfig.type === "static") { // ์ง์ ‘ ์ž…๋ ฅ: ์„ค์ •๋œ ์ •์  ์˜ต์…˜ ์‚ฌ์šฉ options = optionConfig.staticOptions || []; } else if (optionConfig.type === "table" && optionConfig.tableName) { // ํ…Œ์ด๋ธ” ์ฐธ์กฐ: POST ๋ฐฉ์‹์œผ๋กœ ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ ์กฐํšŒ (autoFilter ํฌํ•จ) const response = await apiClient.post(`/table-management/tables/${optionConfig.tableName}/data`, { page: 1, size: 1000, autoFilter: { enabled: true, filterColumn: "company_code" }, }); // ์‘๋‹ต ๋ฐ์ดํ„ฐ ํŒŒ์‹ฑ let dataArray: any[] = []; if (response.data?.success) { const responseData = response.data?.data; if (responseData?.data && Array.isArray(responseData.data)) { dataArray = responseData.data; } else if (Array.isArray(responseData)) { dataArray = responseData; } else if (responseData?.rows && Array.isArray(responseData.rows)) { dataArray = responseData.rows; } } options = dataArray.map((row: any) => ({ value: String(row[optionConfig.valueColumn || "id"]), label: String(row[optionConfig.labelColumn || "name"]), })); } else if (optionConfig.type === "code" && optionConfig.categoryKey) { // ๊ณตํ†ต์ฝ”๋“œ(์นดํ…Œ๊ณ ๋ฆฌ ์ปฌ๋Ÿผ): table_column_category_values ํ…Œ์ด๋ธ”์—์„œ ์กฐํšŒ // categoryKey ํ˜•์‹: "tableName.columnName" const [categoryTable, categoryColumn] = optionConfig.categoryKey.split("."); if (categoryTable && categoryColumn) { const response = await apiClient.get(`/table-categories/${categoryTable}/${categoryColumn}/values`); if (response.data?.success && response.data?.data) { // ์ฝ”๋“œ๊ฐ’์„ DB์— ์ €์žฅํ•˜๊ณ  ๋ผ๋ฒจ๊ฐ’์„ ํ™”๋ฉด์— ํ‘œ์‹œ options = response.data.data.map((item: any) => ({ value: item.valueCode || item.value_code, label: item.valueLabel || item.value_label, })); } } } // ์บ์‹œ ์ €์žฅ setSelectOptionsCache((prev) => ({ ...prev, [fieldId]: options })); } catch (error) { console.error(`Select ์˜ต์…˜ ๋กœ๋“œ ์‹คํŒจ (${fieldId}):`, error); } return options; }, [selectOptionsCache], ); // ์—ฐ๋™ ํ•„๋“œ ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ ๋กœ๋“œ const loadLinkedFieldData = useCallback( async (sourceTable: string): Promise[]> => { // ์บ์‹œ ํ™•์ธ - ์ด๋ฏธ ๋ฐฐ์—ด๋กœ ์บ์‹œ๋˜์–ด ์žˆ์œผ๋ฉด ๋ฐ˜ํ™˜ if (Array.isArray(linkedFieldDataCache[sourceTable]) && linkedFieldDataCache[sourceTable].length > 0) { return linkedFieldDataCache[sourceTable]; } let data: Record[] = []; try { console.log(`[์—ฐ๋™ํ•„๋“œ] ${sourceTable} ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์‹œ์ž‘`); // ํ˜„์žฌ ํšŒ์‚ฌ ๊ธฐ์ค€์œผ๋กœ ๋ฐ์ดํ„ฐ ์กฐํšŒ (POST ๋ฉ”์„œ๋“œ, autoFilter ์‚ฌ์šฉ) const response = await apiClient.post(`/table-management/tables/${sourceTable}/data`, { page: 1, size: 1000, autoFilter: { enabled: true, filterColumn: "company_code" }, // ํ˜„์žฌ ํšŒ์‚ฌ ๊ธฐ์ค€ ์ž๋™ ํ•„ํ„ฐ๋ง }); console.log(`[์—ฐ๋™ํ•„๋“œ] ${sourceTable} API ์‘๋‹ต:`, response.data); if (response.data?.success) { // data ๊ตฌ์กฐ ํ™•์ธ: { data: { data: [...], total, page, ... } } ๋˜๋Š” { data: [...] } const responseData = response.data?.data; if (Array.isArray(responseData)) { // ์ง์ ‘ ๋ฐฐ์—ด์ธ ๊ฒฝ์šฐ data = responseData; } else if (responseData?.data && Array.isArray(responseData.data)) { // { data: [...], total: ... } ํ˜•ํƒœ (tableManagementService ์‘๋‹ต) data = responseData.data; } else if (responseData?.rows && Array.isArray(responseData.rows)) { // { rows: [...], total: ... } ํ˜•ํƒœ (๋‹ค๋ฅธ API ์‘๋‹ต) data = responseData.rows; } console.log(`[์—ฐ๋™ํ•„๋“œ] ${sourceTable} ํŒŒ์‹ฑ๋œ ๋ฐ์ดํ„ฐ ${data.length}๊ฐœ:`, data.slice(0, 3)); } // ์บ์‹œ ์ €์žฅ (๋นˆ ๋ฐฐ์—ด์ด๋ผ๋„ ์ €์žฅํ•˜์—ฌ ์ค‘๋ณต ์š”์ฒญ ๋ฐฉ์ง€) setLinkedFieldDataCache((prev) => ({ ...prev, [sourceTable]: data })); } catch (error) { console.error(`์—ฐ๋™ ํ•„๋“œ ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์‹คํŒจ (${sourceTable}):`, error); // ์‹คํŒจํ•ด๋„ ๋นˆ ๋ฐฐ์—ด๋กœ ์บ์‹œํ•˜์—ฌ ๋ฌดํ•œ ์š”์ฒญ ๋ฐฉ์ง€ setLinkedFieldDataCache((prev) => ({ ...prev, [sourceTable]: [] })); } return data; }, [linkedFieldDataCache], ); // ํ•„์ˆ˜ ํ•„๋“œ ๊ฒ€์ฆ const validateRequiredFields = useCallback((): { valid: boolean; missingFields: string[] } => { const missingFields: string[] = []; for (const section of config.sections) { if (section.repeatable || section.type === "table") continue; // ๋ฐ˜๋ณต ์„น์…˜ ๋ฐ ํ…Œ์ด๋ธ” ์„น์…˜์€ ๋ณ„๋„ ๊ฒ€์ฆ for (const field of section.fields || []) { if (field.required && !field.hidden && !field.numberingRule?.hidden) { const value = formData[field.columnName]; if (value === undefined || value === null || value === "") { missingFields.push(field.label || field.columnName); } } } } return { valid: missingFields.length === 0, missingFields }; }, [config.sections, formData]); // ๋‹ค์ค‘ ํ…Œ์ด๋ธ” ์ €์žฅ (๋ฒ”์šฉ) const saveWithMultiTable = useCallback(async () => { const { customApiSave } = config.saveConfig; if (!customApiSave?.multiTable) return; const { multiTable } = customApiSave; // 1. ๋ฉ”์ธ ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ ๊ตฌ์„ฑ const mainData: Record = {}; config.sections.forEach((section) => { if (section.repeatable || section.type === "table") return; // ๋ฐ˜๋ณต ์„น์…˜ ๋ฐ ํ…Œ์ด๋ธ” ํƒ€์ž… ์ œ์™ธ (section.fields || []).forEach((field) => { const value = formData[field.columnName]; if (value !== undefined && value !== null && value !== "") { mainData[field.columnName] = value; } }); }); // 1-0. receiveFromParent ํ•„๋“œ ๊ฐ’๋„ mainData์— ์ถ”๊ฐ€ (์„œ๋ธŒ ํ…Œ์ด๋ธ” ์ €์žฅ์šฉ) // ์ด ํ•„๋“œ๋“ค์€ ๋ฉ”์ธ ํ…Œ์ด๋ธ”์—๋Š” ์ €์žฅ๋˜์ง€ ์•Š์ง€๋งŒ, ์„œ๋ธŒ ํ…Œ์ด๋ธ” ์ €์žฅ ์‹œ ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Œ config.sections.forEach((section) => { if (section.repeatable || section.type === "table") return; (section.fields || []).forEach((field) => { if (field.receiveFromParent && !mainData[field.columnName]) { const value = formData[field.columnName]; if (value !== undefined && value !== null && value !== "") { mainData[field.columnName] = value; } } }); }); // 1-1. ์ฑ„๋ฒˆ๊ทœ์น™ ์ฒ˜๋ฆฌ (์ €์žฅ ์‹œ์ ์— ์‹ค์ œ ์ˆœ๋ฒˆ ํ• ๋‹น) for (const section of config.sections) { if (section.repeatable || section.type === "table") continue; for (const field of section.fields || []) { // ์ฑ„๋ฒˆ๊ทœ์น™์ด ํ™œ์„ฑํ™”๋œ ํ•„๋“œ ์ฒ˜๋ฆฌ if (field.numberingRule?.enabled && field.numberingRule?.ruleId) { // ์‹ ๊ทœ ์ƒ์„ฑ์ด๊ฑฐ๋‚˜ ๊ฐ’์ด ์—†๋Š” ๊ฒฝ์šฐ์—๋งŒ ์ฑ„๋ฒˆ const isNewRecord = !initialData?.[multiTable.mainTable.primaryKeyColumn]; const hasNoValue = !mainData[field.columnName]; if (isNewRecord || hasNoValue) { try { // allocateNumberingCode๋กœ ์‹ค์ œ ์ˆœ๋ฒˆ ์ฆ๊ฐ€ const response = await allocateNumberingCode(field.numberingRule.ruleId); if (response.success && response.data?.generatedCode) { mainData[field.columnName] = response.data.generatedCode; } } catch (error) { console.error(`์ฑ„๋ฒˆ๊ทœ์น™ ํ• ๋‹น ์‹คํŒจ (${field.columnName}):`, error); } } } } } // 2. ์„œ๋ธŒ ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ ๊ตฌ์„ฑ const subTablesData: Array<{ tableName: string; linkColumn: { mainField: string; subColumn: string }; items: Record[]; options?: { saveMainAsFirst?: boolean; mainFieldMappings?: Array<{ formField: string; targetColumn: string }>; mainMarkerColumn?: string; mainMarkerValue?: any; subMarkerValue?: any; deleteExistingBefore?: boolean; }; }> = []; for (const subTableConfig of multiTable.subTables || []) { // ์„œ๋ธŒ ํ…Œ์ด๋ธ”์ด ํ™œ์„ฑํ™”๋˜์–ด ์žˆ๊ณ  ํ…Œ์ด๋ธ”๋ช…์ด ์žˆ์–ด์•ผ ํ•จ // repeatSectionId๋Š” ์„ ํƒ์‚ฌํ•ญ (saveMainAsFirst๋งŒ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ์—†์„ ์ˆ˜ ์žˆ์Œ) if (!subTableConfig.enabled || !subTableConfig.tableName) { continue; } const subItems: Record[] = []; // ๋ฐ˜๋ณต ์„น์…˜์ด ์žˆ๋Š” ๊ฒฝ์šฐ์—๋งŒ ๋ฐ˜๋ณต ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ if (subTableConfig.repeatSectionId) { const repeatData = repeatSections[subTableConfig.repeatSectionId] || []; // ๋ฐ˜๋ณต ์„น์…˜ ๋ฐ์ดํ„ฐ๋ฅผ ํ•„๋“œ ๋งคํ•‘์— ๋”ฐ๋ผ ๋ณ€ํ™˜ for (const item of repeatData) { const mappedItem: Record = {}; // ์—ฐ๊ฒฐ ์ปฌ๋Ÿผ ๊ฐ’ ์„ค์ • if (subTableConfig.linkColumn?.mainField && subTableConfig.linkColumn?.subColumn) { mappedItem[subTableConfig.linkColumn.subColumn] = mainData[subTableConfig.linkColumn.mainField]; } // ํ•„๋“œ ๋งคํ•‘์— ๋”ฐ๋ผ ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜ for (const mapping of subTableConfig.fieldMappings || []) { if (mapping.formField && mapping.targetColumn) { mappedItem[mapping.targetColumn] = item[mapping.formField]; } } // ๋ฉ”์ธ/์„œ๋ธŒ ๊ตฌ๋ถ„ ์ปฌ๋Ÿผ ์„ค์ • (์„œ๋ธŒ ๋ฐ์ดํ„ฐ๋Š” ์„œ๋ธŒ ๋งˆ์ปค ๊ฐ’) if (subTableConfig.options?.mainMarkerColumn) { mappedItem[subTableConfig.options.mainMarkerColumn] = subTableConfig.options?.subMarkerValue ?? false; } if (Object.keys(mappedItem).length > 0) { subItems.push(mappedItem); } } } // saveMainAsFirst๊ฐ€ ํ™œ์„ฑํ™”๋œ ๊ฒฝ์šฐ, ๋ฉ”์ธ ๋ฐ์ดํ„ฐ๋ฅผ ์„œ๋ธŒ ํ…Œ์ด๋ธ”์— ์ €์žฅํ•˜๊ธฐ ์œ„ํ•œ ๋งคํ•‘ ์ƒ์„ฑ let mainFieldMappings: Array<{ formField: string; targetColumn: string }> | undefined; if (subTableConfig.options?.saveMainAsFirst) { mainFieldMappings = []; // fieldMappings์— ์ •์˜๋œ targetColumn๋งŒ ๋งคํ•‘ (์„œ๋ธŒ ํ…Œ์ด๋ธ”์— ์กด์žฌํ•˜๋Š” ์ปฌ๋Ÿผ๋งŒ) for (const mapping of subTableConfig.fieldMappings || []) { if (mapping.targetColumn) { // formData์—์„œ ๋™์ผํ•œ ์ปฌ๋Ÿผ๋ช…์ด ์žˆ์œผ๋ฉด ๋งคํ•‘ (receiveFromParent ํ•„๋“œ ํฌํ•จ) const formValue = formData[mapping.targetColumn]; if (formValue !== undefined && formValue !== null && formValue !== "") { mainFieldMappings.push({ formField: mapping.targetColumn, targetColumn: mapping.targetColumn, }); } // ๋˜๋Š” ๋ฉ”์ธ ์„น์…˜์˜ ํ•„๋“œ ์ค‘ ๊ฐ™์€ ์ด๋ฆ„์ด ์žˆ์œผ๋ฉด ๋งคํ•‘ else { config.sections.forEach((section) => { if (section.repeatable || section.type === "table") return; const matchingField = (section.fields || []).find((f) => f.columnName === mapping.targetColumn); if (matchingField) { const fieldValue = formData[matchingField.columnName]; if (fieldValue !== undefined && fieldValue !== null && fieldValue !== "") { mainFieldMappings!.push({ formField: matchingField.columnName, targetColumn: mapping.targetColumn, }); } } }); } } } // ์ค‘๋ณต ์ œ๊ฑฐ mainFieldMappings = mainFieldMappings.filter( (m, idx, arr) => arr.findIndex((x) => x.targetColumn === m.targetColumn) === idx, ); } // ์„œ๋ธŒ ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ ์ถ”๊ฐ€ (๋ฐ˜๋ณต ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์–ด๋„ saveMainAsFirst๊ฐ€ ์žˆ์œผ๋ฉด ์ถ”๊ฐ€) if (subItems.length > 0 || subTableConfig.options?.saveMainAsFirst) { subTablesData.push({ tableName: subTableConfig.tableName, linkColumn: subTableConfig.linkColumn, items: subItems, options: { ...subTableConfig.options, mainFieldMappings, // ๋ฉ”์ธ ๋ฐ์ดํ„ฐ ๋งคํ•‘ ์ถ”๊ฐ€ }, }); } } // 3. ๋ฒ”์šฉ ๋‹ค์ค‘ ํ…Œ์ด๋ธ” ์ €์žฅ API ํ˜ธ์ถœ const response = await apiClient.post("/table-management/multi-table-save", { mainTable: multiTable.mainTable, mainData, subTables: subTablesData, isUpdate: !!initialData?.[multiTable.mainTable.primaryKeyColumn], }); if (!response.data?.success) { throw new Error(response.data?.message || "๋‹ค์ค‘ ํ…Œ์ด๋ธ” ์ €์žฅ ์‹คํŒจ"); } }, [config.sections, config.saveConfig, formData, repeatSections, initialData]); // ํ•„๋“œ ์š”์†Œ ๋ Œ๋”๋ง (์ž…๋ ฅ ์ปดํฌ๋„ŒํŠธ๋งŒ) // repeatContext: ๋ฐ˜๋ณต ์„น์…˜์ธ ๊ฒฝ์šฐ { sectionId, itemId }๋ฅผ ์ „๋‹ฌ const renderFieldElement = ( field: FormFieldConfig, value: any, onChangeHandler: (value: any) => void, fieldKey: string, isDisabled: boolean, repeatContext?: { sectionId: string; itemId: string }, ) => { return (() => { switch (field.fieldType) { case "textarea": return (