From 9463d8d0b64a8ae98160d7cabf0f2710be141bd6 Mon Sep 17 00:00:00 2001 From: SeongHyun Kim Date: Thu, 11 Dec 2025 13:25:13 +0900 Subject: [PATCH 01/14] =?UTF-8?q?=EC=88=98=EC=A3=BC=EA=B4=80=EB=A6=AC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=AA=A8=EB=8B=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/screen/EditModal.tsx | 17 +++++------------ .../lib/registry/DynamicComponentRenderer.tsx | 5 +++-- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/frontend/components/screen/EditModal.tsx b/frontend/components/screen/EditModal.tsx index 2a3050fc..294bca7f 100644 --- a/frontend/components/screen/EditModal.tsx +++ b/frontend/components/screen/EditModal.tsx @@ -176,7 +176,7 @@ export const EditModal: React.FC = ({ className }) => { loadGroupData(); } } - }, [modalState.isOpen, modalState.screenId]); + }, [modalState.isOpen, modalState.screenId, modalState.groupByColumns, modalState.tableName]); // ๐Ÿ†• ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ ์กฐํšŒ ํ•จ์ˆ˜ const loadGroupData = async () => { @@ -225,7 +225,7 @@ export const EditModal: React.FC = ({ className }) => { const dataArray = Array.isArray(response) ? response : response?.data || []; if (dataArray.length > 0) { - console.log("โœ… ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ ์กฐํšŒ ์„ฑ๊ณต:", dataArray); + console.log("โœ… ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ ์กฐํšŒ ์„ฑ๊ณต:", dataArray.length, "๊ฑด"); setGroupData(dataArray); setOriginalGroupData(JSON.parse(JSON.stringify(dataArray))); // Deep copy toast.info(`${dataArray.length}๊ฐœ์˜ ๊ด€๋ จ ํ’ˆ๋ชฉ์„ ๋ถˆ๋Ÿฌ์™”์Šต๋‹ˆ๋‹ค.`); @@ -749,15 +749,8 @@ export const EditModal: React.FC = ({ className }) => { }, }; - // ๐Ÿ” ๋””๋ฒ„๊น…: ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง ์‹œ์ ์˜ groupData ํ™•์ธ - if (component.id === screenData.components[0]?.id) { - console.log("๐Ÿ” [EditModal] InteractiveScreenViewerDynamic props:", { - componentId: component.id, - groupDataLength: groupData.length, - groupData: groupData, - formData: groupData.length > 0 ? groupData[0] : formData, - }); - } + + const groupedDataProp = groupData.length > 0 ? groupData : undefined; return ( = ({ className }) => { onSave={handleSave} isInModal={true} // ๐Ÿ†• ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ๋ฅผ ModalRepeaterTable์— ์ „๋‹ฌ - groupedData={groupData.length > 0 ? groupData : undefined} + groupedData={groupedDataProp} // ๐Ÿ†• ์ˆ˜์ • ๋ชจ๋‹ฌ์—์„œ ์ฝ๊ธฐ ์ „์šฉ ํ•„๋“œ ์ง€์ • (์ˆ˜์ฃผ๋ฒˆํ˜ธ, ๊ฑฐ๋ž˜์ฒ˜) disabledFields={["order_no", "partner_id"]} /> diff --git a/frontend/lib/registry/DynamicComponentRenderer.tsx b/frontend/lib/registry/DynamicComponentRenderer.tsx index b039ac38..4d12309b 100644 --- a/frontend/lib/registry/DynamicComponentRenderer.tsx +++ b/frontend/lib/registry/DynamicComponentRenderer.tsx @@ -317,7 +317,7 @@ export const DynamicComponentRenderer: React.FC = // modal-repeater-table์€ ๋ฐฐ์—ด ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค๋ฃจ๋ฏ€๋กœ ๋นˆ ๋ฐฐ์—ด๋กœ ์ดˆ๊ธฐํ™” let currentValue; - if (componentType === "modal-repeater-table") { + if (componentType === "modal-repeater-table" || componentType === "repeat-screen-modal") { // EditModal์—์„œ ์ „๋‹ฌ๋œ groupedData๊ฐ€ ์žˆ์œผ๋ฉด ์šฐ์„  ์‚ฌ์šฉ currentValue = props.groupedData || formData?.[fieldName] || []; } else { @@ -449,7 +449,8 @@ export const DynamicComponentRenderer: React.FC = // ๐Ÿ†• ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ ์ „๋‹ฌ (EditModal โ†’ ConditionalContainer โ†’ ModalRepeaterTable) // Note: ์ด props๋“ค์€ DOM ์š”์†Œ์— ์ „๋‹ฌ๋˜๋ฉด ์•ˆ ๋จ // ๊ฐ ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ช…์‹œ์ ์œผ๋กœ destructureํ•˜์—ฌ ์‚ฌ์šฉํ•ด์•ผ ํ•จ - _groupedData: props.groupedData, + groupedData: props.groupedData, // โœ… ์–ธ๋”์Šค์ฝ”์–ด ์ œ๊ฑฐํ•˜์—ฌ ์ง์ ‘ ์ „๋‹ฌ + _groupedData: props.groupedData, // ํ•˜์œ„ ํ˜ธํ™˜์„ฑ ์œ ์ง€ // ๐Ÿ†• UniversalFormModal์šฉ initialData ์ „๋‹ฌ // originalData๋ฅผ ์‚ฌ์šฉ (์ตœ์ดˆ ์ „๋‹ฌ๋œ ๊ฐ’, formData๋Š” ๊ณ„์† ๋ณ€๊ฒฝ๋˜๋ฏ€๋กœ ์‚ฌ์šฉํ•˜๋ฉด ์•ˆ๋จ) _initialData: originalData || formData, From 190a677067bbe3922e1727ae476e267235c31a06 Mon Sep 17 00:00:00 2001 From: SeongHyun Kim Date: Thu, 11 Dec 2025 14:05:34 +0900 Subject: [PATCH 02/14] =?UTF-8?q?feat(modal-repeater-table):=20=EC=BB=AC?= =?UTF-8?q?=EB=9F=BC=20=EB=84=88=EB=B9=84=20=EB=A6=AC=EC=82=AC=EC=9D=B4?= =?UTF-8?q?=EC=A6=88=20=EA=B8=B0=EB=8A=A5=20=EB=B0=8F=20=EC=97=91=EC=85=80?= =?UTF-8?q?=20=EC=8A=A4=ED=83=80=EC=9D=BC=20UI=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ์ปฌ๋Ÿผ ํ—ค๋” ๋“œ๋ž˜๊ทธ๋กœ ๋„ˆ๋น„ ์กฐ์ • ๊ธฐ๋Šฅ ์ถ”๊ฐ€ (์ตœ์†Œ 60px) - ํ—ค๋” ๋”๋ธ”ํด๋ฆญ์œผ๋กœ ๊ธฐ๋ณธ ๋„ˆ๋น„ ๋ณต์› ๊ธฐ๋Šฅ ์ถ”๊ฐ€ - ์—‘์…€ ์Šคํƒ€์ผ ํ…Œ๋‘๋ฆฌ ๋ฐ ์ƒ‰์ƒ ์ ์šฉ (border-b border-r) - ํ…Œ์ด๋ธ” ์ตœ๋Œ€ ๋†’์ด 240px โ†’ 400px ํ™•์žฅ - ์ž…๋ ฅ ํ•„๋“œ ๋†’์ด ๋ฐ ํฌ์ปค์Šค ์Šคํƒ€์ผ ๊ฐœ์„  --- .../modal-repeater-table/RepeaterTable.tsx | 227 ++++++++++++------ 1 file changed, 152 insertions(+), 75 deletions(-) diff --git a/frontend/lib/registry/components/modal-repeater-table/RepeaterTable.tsx b/frontend/lib/registry/components/modal-repeater-table/RepeaterTable.tsx index 410fd9a6..56e9a321 100644 --- a/frontend/lib/registry/components/modal-repeater-table/RepeaterTable.tsx +++ b/frontend/lib/registry/components/modal-repeater-table/RepeaterTable.tsx @@ -36,6 +36,71 @@ export function RepeaterTable({ // ๋™์  ๋ฐ์ดํ„ฐ ์†Œ์Šค Popover ์—ด๋ฆผ ์ƒํƒœ const [openPopover, setOpenPopover] = useState(null); + + // ์ปฌ๋Ÿผ ๋„ˆ๋น„ ์ƒํƒœ ๊ด€๋ฆฌ + const [columnWidths, setColumnWidths] = useState>(() => { + const widths: Record = {}; + columns.forEach((col) => { + widths[col.field] = col.width ? parseInt(col.width) : 120; + }); + return widths; + }); + + // ๊ธฐ๋ณธ ๋„ˆ๋น„ ์ €์žฅ (๋ฆฌ์…‹์šฉ) + const defaultWidths = React.useMemo(() => { + const widths: Record = {}; + columns.forEach((col) => { + widths[col.field] = col.width ? parseInt(col.width) : 120; + }); + return widths; + }, [columns]); + + // ๋ฆฌ์‚ฌ์ด์ฆˆ ์ƒํƒœ + const [resizing, setResizing] = useState<{ field: string; startX: number; startWidth: number } | null>(null); + + // ๋ฆฌ์‚ฌ์ด์ฆˆ ํ•ธ๋“ค๋Ÿฌ + const handleMouseDown = (e: React.MouseEvent, field: string) => { + e.preventDefault(); + setResizing({ + field, + startX: e.clientX, + startWidth: columnWidths[field] || 120, + }); + }; + + // ๋”๋ธ”ํด๋ฆญ์œผ๋กœ ๊ธฐ๋ณธ ๋„ˆ๋น„๋กœ ๋ฆฌ์…‹ + const handleDoubleClick = (field: string) => { + setColumnWidths((prev) => ({ + ...prev, + [field]: defaultWidths[field] || 120, + })); + }; + + useEffect(() => { + if (!resizing) return; + + const handleMouseMove = (e: MouseEvent) => { + if (!resizing) return; + const diff = e.clientX - resizing.startX; + const newWidth = Math.max(60, resizing.startWidth + diff); + setColumnWidths((prev) => ({ + ...prev, + [resizing.field]: newWidth, + })); + }; + + const handleMouseUp = () => { + setResizing(null); + }; + + document.addEventListener("mousemove", handleMouseMove); + document.addEventListener("mouseup", handleMouseUp); + + return () => { + document.removeEventListener("mousemove", handleMouseMove); + document.removeEventListener("mouseup", handleMouseUp); + }; + }, [resizing, columns, data]); // ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ ๊ฐ์ง€ (ํ•„์š”์‹œ ํ™œ์„ฑํ™”) // useEffect(() => { @@ -79,7 +144,7 @@ export function RepeaterTable({ onChange={(e) => handleCellEdit(rowIndex, column.field, parseFloat(e.target.value) || 0) } - className="h-7 text-xs" + className="h-8 text-xs border-gray-200 focus:border-blue-500 focus:ring-1 focus:ring-blue-500 rounded-none" /> ); @@ -107,7 +172,7 @@ export function RepeaterTable({ type="date" value={formatDateValue(value)} onChange={(e) => handleCellEdit(rowIndex, column.field, e.target.value)} - className="h-7 text-xs" + className="h-8 text-xs border-gray-200 focus:border-blue-500 focus:ring-1 focus:ring-blue-500 rounded-none" /> ); @@ -119,7 +184,7 @@ export function RepeaterTable({ handleCellEdit(rowIndex, column.field, newValue) } > - + @@ -138,19 +203,19 @@ export function RepeaterTable({ type="text" value={value || ""} onChange={(e) => handleCellEdit(rowIndex, column.field, e.target.value)} - className="h-7 text-xs" + className="h-8 text-xs border-gray-200 focus:border-blue-500 focus:ring-1 focus:ring-blue-500 rounded-none" /> ); } }; return ( -
-
- - +
+
+
+ - {columns.map((col) => { @@ -163,101 +228,113 @@ export function RepeaterTable({ return ( + "inline-flex items-center gap-1 hover:text-blue-600 transition-colors", + "focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 rounded px-1 -mx-1" + )} + > + {col.label} + + + + +
+ ๋ฐ์ดํ„ฐ ์†Œ์Šค ์„ ํƒ +
+ {col.dynamicDataSource!.options.map((option) => ( + + ))} +
+ + ) : ( + <> + {col.label} + {col.required && *} + + )} + + {/* ๋ฆฌ์‚ฌ์ด์ฆˆ ํ•ธ๋“ค */} +
handleMouseDown(e, col.field)} + title="๋“œ๋ž˜๊ทธํ•˜์—ฌ ๋„ˆ๋น„ ์กฐ์ •" + /> +
+ ); })} -
- + {data.length === 0 ? ( ) : ( data.map((row, rowIndex) => ( - - + {columns.map((col) => ( - ))} -
+ # handleDoubleClick(col.field)} + title="๋”๋ธ”ํด๋ฆญํ•˜์—ฌ ๊ธฐ๋ณธ ๋„ˆ๋น„๋กœ ๋˜๋Œ๋ฆฌ๊ธฐ" > - {hasDynamicSource ? ( - setOpenPopover(open ? col.field : null)} - > - - - - -
- ๋ฐ์ดํ„ฐ ์†Œ์Šค ์„ ํƒ -
- {col.dynamicDataSource!.options.map((option) => ( - - ))} -
-
- ) : ( - <> - {col.label} - {col.required && *} - - )} -
+ ์‚ญ์ œ
์ถ”๊ฐ€๋œ ํ•ญ๋ชฉ์ด ์—†์Šต๋‹ˆ๋‹ค
+
{rowIndex + 1} + {renderCell(row, col, rowIndex)} + From 6a676dcf5c64961d0fdc77904b9e1d0b96c7ce66 Mon Sep 17 00:00:00 2001 From: SeongHyun Kim Date: Thu, 11 Dec 2025 15:29:37 +0900 Subject: [PATCH 03/14] =?UTF-8?q?refactor(universal-form-modal):=20ConfigP?= =?UTF-8?q?anel=20=EB=AA=A8=EB=8B=AC=20=EB=B6=84=EB=A6=AC=20=EB=B0=8F=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=ED=8C=A8=EB=84=90=20=EC=98=A4=EB=B2=84?= =?UTF-8?q?=ED=94=8C=EB=A1=9C=EC=9A=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - UniversalFormModalConfigPanel์„ 3๊ฐœ ๋ชจ๋‹ฌ๋กœ ๋ถ„๋ฆฌ (2300์ค„ โ†’ 300์ค„) - FieldDetailSettingsModal: ํ•„๋“œ ์ƒ์„ธ ์„ค์ • - SaveSettingsModal: ์ €์žฅ ์„ค์ • - SectionLayoutModal: ์„น์…˜ ๋ ˆ์ด์•„์›ƒ ์„ค์ • - FloatingPanel, DetailSettingsPanel ๊ฐ€๋กœ ์Šคํฌ๋กค ์˜ค๋ฒ„ํ”Œ๋กœ์šฐ ์ˆ˜์ • - SelectOptionConfig์— saveColumn ํ•„๋“œ ์ถ”๊ฐ€ (์ €์žฅ ๊ฐ’ ๋ณ„๋„ ์ง€์ •) --- frontend/components/screen/FloatingPanel.tsx | 2 +- .../screen/panels/DetailSettingsPanel.tsx | 12 +- .../UniversalFormModalConfigPanel.tsx | 2357 +++-------------- .../modals/FieldDetailSettingsModal.tsx | 842 ++++++ .../modals/SaveSettingsModal.tsx | 796 ++++++ .../modals/SectionLayoutModal.tsx | 516 ++++ .../components/universal-form-modal/types.ts | 5 +- .../lib/utils/getComponentConfigPanel.tsx | 6 +- 8 files changed, 2467 insertions(+), 2069 deletions(-) create mode 100644 frontend/lib/registry/components/universal-form-modal/modals/FieldDetailSettingsModal.tsx create mode 100644 frontend/lib/registry/components/universal-form-modal/modals/SaveSettingsModal.tsx create mode 100644 frontend/lib/registry/components/universal-form-modal/modals/SectionLayoutModal.tsx diff --git a/frontend/components/screen/FloatingPanel.tsx b/frontend/components/screen/FloatingPanel.tsx index cddb053b..d6efd8b1 100644 --- a/frontend/components/screen/FloatingPanel.tsx +++ b/frontend/components/screen/FloatingPanel.tsx @@ -267,7 +267,7 @@ export const FloatingPanel: React.FC = ({ {/* ์ปจํ…์ธ  */}
= ({ }; return ( -
+

{definition.name} ์„ค์ •

@@ -998,7 +998,7 @@ export const DetailSettingsPanel: React.FC = ({
{/* ์„ค์ • ํŒจ๋„ ์˜์—ญ */} -
{renderComponentConfigPanel()}
+
{renderComponentConfigPanel()}
); } @@ -1156,8 +1156,8 @@ export const DetailSettingsPanel: React.FC = ({
{/* ์ปดํฌ๋„ŒํŠธ ์„ค์ • ํŒจ๋„ */} -
-
+
+
{/* DynamicComponentConfigPanel */} = ({
{/* ์ƒ์„ธ ์„ค์ • ์˜์—ญ */} -
-
+
+
{console.log("๐Ÿ” [DetailSettingsPanel] widget ํƒ€์ž…:", selectedComponent?.type, "autoFill:", widget.autoFill)} {/* ๐Ÿ†• ์ž๋™ ์ž…๋ ฅ ์„น์…˜ */}
diff --git a/frontend/lib/registry/components/universal-form-modal/UniversalFormModalConfigPanel.tsx b/frontend/lib/registry/components/universal-form-modal/UniversalFormModalConfigPanel.tsx index 48542342..656f3f1a 100644 --- a/frontend/lib/registry/components/universal-form-modal/UniversalFormModalConfigPanel.tsx +++ b/frontend/lib/registry/components/universal-form-modal/UniversalFormModalConfigPanel.tsx @@ -4,13 +4,9 @@ import React, { useState, useEffect, useCallback } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; -import { Switch } from "@/components/ui/switch"; -import { Textarea } from "@/components/ui/textarea"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"; -import { Card, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; -import { Checkbox } from "@/components/ui/checkbox"; -import { ScrollArea } from "@/components/ui/scroll-area"; +import { Badge } from "@/components/ui/badge"; import { Separator } from "@/components/ui/separator"; import { Plus, @@ -21,37 +17,27 @@ import { Settings, Database, Layout, - Hash, - Check, - ChevronsUpDown, } from "lucide-react"; -import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"; -import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { cn } from "@/lib/utils"; -import { toast } from "sonner"; import { apiClient } from "@/lib/api/client"; import { getNumberingRules } from "@/lib/api/numberingRule"; - import { UniversalFormModalConfig, UniversalFormModalConfigPanelProps, FormSectionConfig, FormFieldConfig, - LinkedFieldMapping, - FIELD_TYPE_OPTIONS, MODAL_SIZE_OPTIONS, - SELECT_OPTION_TYPE_OPTIONS, - LINKED_FIELD_DISPLAY_FORMAT_OPTIONS, } from "./types"; import { - defaultFieldConfig, defaultSectionConfig, - defaultNumberingRuleConfig, - defaultSelectOptionsConfig, generateSectionId, - generateFieldId, } from "./config"; +// ๋ชจ๋‹ฌ import +import { FieldDetailSettingsModal } from "./modals/FieldDetailSettingsModal"; +import { SaveSettingsModal } from "./modals/SaveSettingsModal"; +import { SectionLayoutModal } from "./modals/SectionLayoutModal"; + // ๋„์›€๋ง ํ…์ŠคํŠธ ์ปดํฌ๋„ŒํŠธ const HelpText = ({ children }: { children: React.ReactNode }) => (

{children}

@@ -67,16 +53,15 @@ export function UniversalFormModalConfigPanel({ config, onChange }: UniversalFor // ์ฑ„๋ฒˆ๊ทœ์น™ ๋ชฉ๋ก const [numberingRules, setNumberingRules] = useState<{ id: string; name: string }[]>([]); - // ์„ ํƒ๋œ ์„น์…˜/ํ•„๋“œ - const [selectedSectionId, setSelectedSectionId] = useState(null); - const [selectedFieldId, setSelectedFieldId] = useState(null); - - // ํ…Œ์ด๋ธ” ์„ ํƒ Combobox ์ƒํƒœ - const [tableSelectOpen, setTableSelectOpen] = useState(false); + // ๋ชจ๋‹ฌ ์ƒํƒœ + const [saveSettingsModalOpen, setSaveSettingsModalOpen] = useState(false); + const [sectionLayoutModalOpen, setSectionLayoutModalOpen] = useState(false); + const [fieldDetailModalOpen, setFieldDetailModalOpen] = useState(false); + const [selectedSection, setSelectedSection] = useState(null); + const [selectedField, setSelectedField] = useState(null); // ํ…Œ์ด๋ธ” ๋ชฉ๋ก ๋กœ๋“œ useEffect(() => { - console.log("[UniversalFormModal ConfigPanel] ์ดˆ๊ธฐํ™” - ํ…Œ์ด๋ธ” ๋ฐ ์ฑ„๋ฒˆ๊ทœ์น™ ๋กœ๋“œ"); loadTables(); loadNumberingRules(); }, []); @@ -86,47 +71,8 @@ export function UniversalFormModalConfigPanel({ config, onChange }: UniversalFor if (config.saveConfig.tableName) { loadTableColumns(config.saveConfig.tableName); } - // eslint-disable-next-line react-hooks/exhaustive-deps }, [config.saveConfig.tableName]); - // ๋‹ค์ค‘ ์ปฌ๋Ÿผ ์ €์žฅ์˜ ์†Œ์Šค ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ๋กœ๋“œ - useEffect(() => { - const allSourceTables = new Set(); - config.sections.forEach((section) => { - // ํ•„๋“œ ๋ ˆ๋ฒจ์˜ linkedFieldGroup ํ™•์ธ - section.fields.forEach((field) => { - if (field.linkedFieldGroup?.sourceTable) { - allSourceTables.add(field.linkedFieldGroup.sourceTable); - } - }); - }); - allSourceTables.forEach((tableName) => { - if (!tableColumns[tableName]) { - loadTableColumns(tableName); - } - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [config.sections]); - - // ๋‹ค์ค‘ ํ…Œ์ด๋ธ” ์ €์žฅ ์„ค์ •์˜ ๋ฉ”์ธ/์„œ๋ธŒ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ๋กœ๋“œ - useEffect(() => { - const customApiSave = config.saveConfig.customApiSave; - if (customApiSave?.enabled && customApiSave?.multiTable) { - // ๋ฉ”์ธ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ๋กœ๋“œ - const mainTableName = customApiSave.multiTable.mainTable?.tableName; - if (mainTableName && !tableColumns[mainTableName]) { - loadTableColumns(mainTableName); - } - // ์„œ๋ธŒ ํ…Œ์ด๋ธ”๋“ค ์ปฌ๋Ÿผ ๋กœ๋“œ - customApiSave.multiTable.subTables?.forEach((subTable) => { - if (subTable.tableName && !tableColumns[subTable.tableName]) { - loadTableColumns(subTable.tableName); - } - }); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [config.saveConfig.customApiSave]); - const loadTables = async () => { try { const response = await apiClient.get("/table-management/tables"); @@ -145,18 +91,13 @@ export function UniversalFormModalConfigPanel({ config, onChange }: UniversalFor }; const loadTableColumns = async (tableName: string) => { - console.log(`[UniversalFormModal] ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ๋กœ๋“œ ์‹œ๋„: ${tableName}`); if (!tableName || (tableColumns[tableName] && tableColumns[tableName].length > 0)) return; try { const response = await apiClient.get(`/table-management/tables/${tableName}/columns`); - console.log("[UniversalFormModal] ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ์‘๋‹ต:", response.data); - - // API ์‘๋‹ต ๊ตฌ์กฐ: { success: true, data: { columns: [...] } } - const data = response.data?.data?.columns || response.data?.data; + const data = response.data?.data; if (response.data?.success && Array.isArray(data)) { - console.log(`[UniversalFormModal] ํŒŒ์‹ฑ๋œ ์ปฌ๋Ÿผ ${data.length}๊ฐœ:`, data); setTableColumns((prev) => ({ ...prev, [tableName]: data.map( @@ -165,36 +106,27 @@ export function UniversalFormModalConfigPanel({ config, onChange }: UniversalFor column_name?: string; dataType?: string; data_type?: string; - columnLabel?: string; - column_label?: string; - name?: string; + columnComment?: string; + column_comment?: string; }) => ({ - name: c.columnName || c.column_name || c.name || "", - type: c.dataType || c.data_type || "", - label: c.columnLabel || c.column_label || c.columnName || c.column_name || c.name || "", + name: c.columnName || c.column_name || "", + type: c.dataType || c.data_type || "text", + label: c.columnComment || c.column_comment || c.columnName || c.column_name || "", }), ), })); - } else { - console.warn("[UniversalFormModal] ์ปฌ๋Ÿผ ๋ฐ์ดํ„ฐ ์—†์Œ ๋˜๋Š” ํ˜•์‹ ์˜ค๋ฅ˜:", response.data); - setTableColumns((prev) => ({ ...prev, [tableName]: [] })); } } catch (error) { console.error(`ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ๋กœ๋“œ ์‹คํŒจ (${tableName}):`, error); - setTableColumns((prev) => ({ ...prev, [tableName]: [] })); } }; const loadNumberingRules = async () => { try { - console.log("[UniversalFormModal] ์ฑ„๋ฒˆ๊ทœ์น™ ๋กœ๋“œ ์‹œ๋„"); const response = await getNumberingRules(); - console.log("[UniversalFormModal] ์ฑ„๋ฒˆ๊ทœ์น™ ์‘๋‹ต:", response); + const data = response?.data; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const data: any = response.data; - - if (response.success && Array.isArray(data) && data.length > 0) { + if (response?.success && Array.isArray(data)) { const rules = data.map( (r: { id?: string | number; @@ -208,13 +140,10 @@ export function UniversalFormModalConfigPanel({ config, onChange }: UniversalFor name: r.name || r.ruleName || r.rule_name || "", }), ); - console.log("[UniversalFormModal] ํŒŒ์‹ฑ๋œ ์ฑ„๋ฒˆ๊ทœ์น™:", rules); setNumberingRules(rules); - } else { - console.warn("[UniversalFormModal] ์ฑ„๋ฒˆ๊ทœ์น™ ๋ฐ์ดํ„ฐ ์—†์Œ:", data); } } catch (error) { - console.error("[UniversalFormModal] ์ฑ„๋ฒˆ๊ทœ์น™ ๋ชฉ๋ก ๋กœ๋“œ ์‹คํŒจ:", error); + console.error("์ฑ„๋ฒˆ๊ทœ์น™ ๋ชฉ๋ก ๋กœ๋“œ ์‹คํŒจ:", error); } }; @@ -229,16 +158,6 @@ export function UniversalFormModalConfigPanel({ config, onChange }: UniversalFor [config, onChange], ); - const updateSaveConfig = useCallback( - (updates: Partial) => { - onChange({ - ...config, - saveConfig: { ...config.saveConfig, ...updates }, - }); - }, - [config, onChange], - ); - // ์„น์…˜ ๊ด€๋ฆฌ const addSection = useCallback(() => { const newSection: FormSectionConfig = { @@ -250,7 +169,6 @@ export function UniversalFormModalConfigPanel({ config, onChange }: UniversalFor ...config, sections: [...config.sections, newSection], }); - setSelectedSectionId(newSection.id); }, [config, onChange]); const updateSection = useCallback( @@ -269,12 +187,8 @@ export function UniversalFormModalConfigPanel({ config, onChange }: UniversalFor ...config, sections: config.sections.filter((s) => s.id !== sectionId), }); - if (selectedSectionId === sectionId) { - setSelectedSectionId(null); - setSelectedFieldId(null); - } }, - [config, onChange, selectedSectionId], + [config, onChange], ); const moveSectionUp = useCallback( @@ -297,99 +211,71 @@ export function UniversalFormModalConfigPanel({ config, onChange }: UniversalFor [config, onChange], ); - // ํ•„๋“œ ๊ด€๋ฆฌ - const addField = useCallback( - (sectionId: string) => { - const newField: FormFieldConfig = { - ...defaultFieldConfig, - id: generateFieldId(), - label: "์ƒˆ ํ•„๋“œ", - numberingRule: { ...defaultNumberingRuleConfig }, - selectOptions: { ...defaultSelectOptionsConfig }, - }; - onChange({ - ...config, - sections: config.sections.map((s) => - s.id === sectionId ? { ...s, fields: [...s.fields, newField] } : s, - ), - }); - setSelectedSectionId(sectionId); - setSelectedFieldId(newField.id); - }, - [config, onChange], - ); + // ํ•„๋“œ ํƒ€์ž…๋ณ„ ์ƒ‰์ƒ + const getFieldTypeColor = (fieldType: FormFieldConfig["fieldType"]): string => { + switch (fieldType) { + case "text": + case "email": + case "password": + case "tel": + return "text-blue-600 bg-blue-50 border-blue-200"; + case "number": + return "text-cyan-600 bg-cyan-50 border-cyan-200"; + case "date": + case "datetime": + return "text-purple-600 bg-purple-50 border-purple-200"; + case "select": + return "text-green-600 bg-green-50 border-green-200"; + case "checkbox": + return "text-pink-600 bg-pink-50 border-pink-200"; + case "textarea": + return "text-orange-600 bg-orange-50 border-orange-200"; + default: + return "text-gray-600 bg-gray-50 border-gray-200"; + } + }; - const updateField = useCallback( - (sectionId: string, fieldId: string, updates: Partial) => { - onChange({ - ...config, - sections: config.sections.map((s) => - s.id === sectionId - ? { - ...s, - fields: s.fields.map((f) => (f.id === fieldId ? { ...f, ...updates } : f)), - } - : s, - ), - }); - }, - [config, onChange], - ); + // ์„น์…˜ ๋ ˆ์ด์•„์›ƒ ๋ชจ๋‹ฌ ์—ด๊ธฐ + const handleOpenSectionLayout = (section: FormSectionConfig) => { + setSelectedSection(section); + setSectionLayoutModalOpen(true); + }; - const removeField = useCallback( - (sectionId: string, fieldId: string) => { - onChange({ - ...config, - sections: config.sections.map((s) => - s.id === sectionId ? { ...s, fields: s.fields.filter((f) => f.id !== fieldId) } : s, - ), - }); - if (selectedFieldId === fieldId) { - setSelectedFieldId(null); - } - }, - [config, onChange, selectedFieldId], - ); - - // ์„ ํƒ๋œ ์„น์…˜/ํ•„๋“œ ๊ฐ€์ ธ์˜ค๊ธฐ - const selectedSection = config.sections.find((s) => s.id === selectedSectionId); - const selectedField = selectedSection?.fields.find((f) => f.id === selectedFieldId); - - // ํ˜„์žฌ ํ…Œ์ด๋ธ”์˜ ์ปฌ๋Ÿผ ๋ชฉ๋ก - const currentColumns = tableColumns[config.saveConfig.tableName] || []; + // ํ•„๋“œ ์ƒ์„ธ ์„ค์ • ๋ชจ๋‹ฌ ์—ด๊ธฐ + const handleOpenFieldDetail = (section: FormSectionConfig, field: FormFieldConfig) => { + setSelectedSection(section); + setSelectedField(field); + setFieldDetailModalOpen(true); + }; return ( - -
+
+
+
{/* ๋ชจ๋‹ฌ ๊ธฐ๋ณธ ์„ค์ • */} - - - -
- - ๋ชจ๋‹ฌ ๊ธฐ๋ณธ ์„ค์ • + + + +
+ + ๋ชจ๋‹ฌ ๊ธฐ๋ณธ ์„ค์ •
- -
- + +
+ updateModalConfig({ title: e.target.value })} - placeholder="๋ชจ๋‹ฌ ์ œ๋ชฉ ์ž…๋ ฅ" - className="h-7 text-xs mt-1" + className="h-9 text-sm w-full max-w-full" /> + ๋ชจ๋‹ฌ ์ƒ๋‹จ์— ํ‘œ์‹œ๋  ์ œ๋ชฉ์ž…๋‹ˆ๋‹ค
-
- - updateModalConfig({ size: value })}> + @@ -400,1906 +286,263 @@ export function UniversalFormModalConfigPanel({ config, onChange }: UniversalFor ))} + ๋ชจ๋‹ฌ ์ฐฝ์˜ ํฌ๊ธฐ๋ฅผ ์„ ํƒํ•˜์„ธ์š”
-
-
- ์ €์žฅ ๋ฒ„ํŠผ ํ‘œ์‹œ - updateModalConfig({ showSaveButton: checked })} +
+
+ + updateModalConfig({ saveButtonText: e.target.value })} + className="h-9 text-sm w-full max-w-full" + /> +
+
+ + updateModalConfig({ cancelButtonText: e.target.value })} + className="h-9 text-sm w-full max-w-full" />
- ButtonPrimary ์ปดํฌ๋„ŒํŠธ๋กœ ์ €์žฅ ๋ฒ„ํŠผ์„ ๋ณ„๋„ ๊ตฌ์„ฑํ•  ๊ฒฝ์šฐ ๋„์„ธ์š” - - {config.modal.showSaveButton !== false && ( -
- - updateModalConfig({ saveButtonText: e.target.value })} - className="h-7 text-xs mt-1" - /> -
- )}
{/* ์ €์žฅ ์„ค์ • */} - - - -
- - ์ €์žฅ ์„ค์ • + + + +
+ + ์ €์žฅ ์„ค์ •
- - {/* ์ €์žฅ ํ…Œ์ด๋ธ” - Combobox */} -
- - {config.saveConfig.customApiSave?.enabled ? ( -
- ์ „์šฉ API ์ €์žฅ ๋ชจ๋“œ์—์„œ๋Š” API๊ฐ€ ํ…Œ์ด๋ธ” ์ €์žฅ์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. - {config.saveConfig.customApiSave?.apiType === "user-with-dept" && ( - ๋Œ€์ƒ ํ…Œ์ด๋ธ”: user_info + user_dept - )} -
- ) : ( - <> - - - - - - - - - ํ…Œ์ด๋ธ”์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค - - {tables.map((t) => ( - { - updateSaveConfig({ tableName: t.name }); - setTableSelectOpen(false); - }} - className="text-xs" - > - - {t.name} - {t.label !== t.name && ( - ({t.label}) - )} - - ))} - - - - - - {config.saveConfig.tableName && ( -

- ์ปฌ๋Ÿผ {currentColumns.length}๊ฐœ ๋กœ๋“œ๋จ + +

+
+ +

+ {config.saveConfig.tableName || "(๋ฏธ์„ค์ •)"}

- )} - - )} -
- - {/* ๋‹ค์ค‘ ํ–‰ ์ €์žฅ ์„ค์ • - ์ „์šฉ API ๋ชจ๋“œ์—์„œ๋Š” ์ˆจ๊น€ */} - {!config.saveConfig.customApiSave?.enabled && ( -
-
- ๋‹ค์ค‘ ํ–‰ ์ €์žฅ - - updateSaveConfig({ - multiRowSave: { ...config.saveConfig.multiRowSave, enabled: checked }, - }) - } - /> -
- ๊ฒธ์ง์ฒ˜๋Ÿผ ํ•˜๋‚˜์˜ ํผ์—์„œ ์—ฌ๋Ÿฌ ํ–‰์„ ์ €์žฅํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. - - {config.saveConfig.multiRowSave?.enabled && ( -
- {/* ๊ณตํ†ต ํ•„๋“œ ์„ ํƒ */} -
- -
- {config.sections - .filter((s) => !s.repeatable) - .flatMap((s) => s.fields) - .map((field) => ( - - ))} -
- ๋ฉ”์ธ ํ–‰๊ณผ ๊ฒธ์ง ํ–‰ ๋ชจ๋‘์— ์ €์žฅ๋  ํ•„๋“œ -
- - {/* ๋ฉ”์ธ ์„น์…˜ ํ•„๋“œ ์„ ํƒ */} -
- -
- {config.sections - .filter((s) => !s.repeatable) - .flatMap((s) => s.fields) - .filter((field) => !config.saveConfig.multiRowSave?.commonFields?.includes(field.columnName)) - .map((field) => ( - - ))} -
- ๋ฉ”์ธ ํ–‰์—๋งŒ ์ €์žฅ๋  ํ•„๋“œ (๊ณตํ†ต ํ•„๋“œ ์ œ์™ธ) -
- - {/* ๋ฐ˜๋ณต ์„น์…˜ ์„ ํƒ */} -
- - - ๊ฒธ์ง ๋“ฑ ๋ฐ˜๋ณต ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋Š” ์„น์…˜ -
-
- )} -
- )} - - {/* ๋‹ค์ค‘ ํ…Œ์ด๋ธ” ์ €์žฅ ์„ค์ • (๋ฒ”์šฉ) */} -
-
- ๋‹ค์ค‘ ํ…Œ์ด๋ธ” ์ €์žฅ - - updateSaveConfig({ - customApiSave: { - ...config.saveConfig.customApiSave, - enabled: checked, - apiType: "multi-table", - multiTable: checked ? { - enabled: true, - mainTable: { tableName: config.saveConfig.tableName || "", primaryKeyColumn: "" }, - subTables: [], - } : undefined, - }, - }) - } - /> -
- - ๋ฉ”์ธ ํ…Œ์ด๋ธ” + ์„œ๋ธŒ ํ…Œ์ด๋ธ”(๋ฐ˜๋ณต ์„น์…˜)์— ํŠธ๋žœ์žญ์…˜์œผ๋กœ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. -
์˜ˆ: ์‚ฌ์›+๋ถ€์„œ, ์ฃผ๋ฌธ+์ฃผ๋ฌธ์ƒ์„ธ, ํ”„๋กœ์ ํŠธ+๋ฉค๋ฒ„ ๋“ฑ -
- - {config.saveConfig.customApiSave?.enabled && ( -
- {/* API ํƒ€์ž… ์„ ํƒ */} -
- - -
- - {/* ๋‹ค์ค‘ ํ…Œ์ด๋ธ” ์ €์žฅ ์„ค์ • */} - {config.saveConfig.customApiSave?.apiType === "multi-table" && ( -
- {/* ๋ฉ”์ธ ํ…Œ์ด๋ธ” ์„ค์ • */} -
- - ๋น„๋ฐ˜๋ณต ์„น์…˜์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ์ €์žฅ๋  ๋ฉ”์ธ ํ…Œ์ด๋ธ”์ž…๋‹ˆ๋‹ค. - -
- - - - - - - - - - ํ…Œ์ด๋ธ”์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค - - {tables.map((table) => ( - { - updateSaveConfig({ - customApiSave: { - ...config.saveConfig.customApiSave, - multiTable: { - ...config.saveConfig.customApiSave?.multiTable, - enabled: true, - mainTable: { - ...config.saveConfig.customApiSave?.multiTable?.mainTable, - tableName: table.name, - }, - subTables: config.saveConfig.customApiSave?.multiTable?.subTables || [], - }, - }, - }); - // ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ๋กœ๋“œ - if (!tableColumns[table.name]) { - loadTableColumns(table.name); - } - }} - className="text-[10px]" - > - -
- {table.label || table.name} - {table.label && {table.name}} -
-
- ))} -
-
-
-
-
-
- -
- - - ์„œ๋ธŒ ํ…Œ์ด๋ธ”๊ณผ ์—ฐ๊ฒฐํ•  ๋•Œ ์‚ฌ์šฉํ•  PK ์ปฌ๋Ÿผ -
-
- - {/* ์„œ๋ธŒ ํ…Œ์ด๋ธ” ์„ค์ • */} -
-
- - -
- ๋ฐ˜๋ณต ์„น์…˜์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ์ €์žฅ๋  ์„œ๋ธŒ ํ…Œ์ด๋ธ”์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. - - {(config.saveConfig.customApiSave?.multiTable?.subTables || []).map((subTable, subIndex) => ( -
-
- ์„œ๋ธŒ ํ…Œ์ด๋ธ” #{subIndex + 1} - -
- - {/* ์„œ๋ธŒ ํ…Œ์ด๋ธ”๋ช… */} -
- - - - - - - - - - ํ…Œ์ด๋ธ”์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค - - {tables.map((table) => ( - { - const newSubTables = [...(config.saveConfig.customApiSave?.multiTable?.subTables || [])]; - newSubTables[subIndex] = { ...newSubTables[subIndex], tableName: table.name }; - updateSaveConfig({ - customApiSave: { - ...config.saveConfig.customApiSave, - multiTable: { - ...config.saveConfig.customApiSave?.multiTable, - enabled: true, - mainTable: config.saveConfig.customApiSave?.multiTable?.mainTable || { tableName: "", primaryKeyColumn: "" }, - subTables: newSubTables, - }, - }, - }); - // ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ๋กœ๋“œ - if (!tableColumns[table.name]) { - loadTableColumns(table.name); - } - }} - className="text-[9px]" - > - -
- {table.label || table.name} - {table.label && {table.name}} -
-
- ))} -
-
-
-
-
-
- - {/* ๋ฐ˜๋ณต ์„น์…˜ ์„ ํƒ */} -
- - ์„œ๋ธŒ ํ…Œ์ด๋ธ”์— ์ €์žฅํ•  ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋Š” ๋ฐ˜๋ณต ์„น์…˜ - -
- - {/* ์—ฐ๊ฒฐ ์ปฌ๋Ÿผ ์„ค์ • */} -
- - ๋ฉ”์ธ ํ…Œ์ด๋ธ”์˜ PK์™€ ์„œ๋ธŒ ํ…Œ์ด๋ธ”์˜ FK๋ฅผ ์—ฐ๊ฒฐ -
- {/* ๋ฉ”์ธ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ์„ ํƒ (PK ์ปฌ๋Ÿผ ๊ธฐ์ค€) */} - -
โ†“
- {/* ์„œ๋ธŒ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ์„ ํƒ (FK ์ปฌ๋Ÿผ) */} - -
- ๋ฉ”์ธ ํ…Œ์ด๋ธ”๊ณผ ์„œ๋ธŒ ํ…Œ์ด๋ธ”์„ ์—ฐ๊ฒฐํ•  ์ปฌ๋Ÿผ -
- - {/* ํ•„๋“œ ๋งคํ•‘ */} - {subTable.repeatSectionId && subTable.tableName && ( -
-
- - -
- - {(subTable.fieldMappings || []).map((mapping, mapIndex) => { - const repeatSection = config.sections.find((s) => s.id === subTable.repeatSectionId); - const sectionFields = repeatSection?.fields || []; - - return ( -
-
- ๋งคํ•‘ #{mapIndex + 1} - -
- -
โ†“
- -
- ); - })} -
- )} - - {/* ์ถ”๊ฐ€ ์˜ต์…˜ */} -
- -
- { - const newSubTables = [...(config.saveConfig.customApiSave?.multiTable?.subTables || [])]; - newSubTables[subIndex] = { - ...newSubTables[subIndex], - options: { ...newSubTables[subIndex].options, saveMainAsFirst: !!checked }, - }; - updateSaveConfig({ - customApiSave: { - ...config.saveConfig.customApiSave, - multiTable: { - ...config.saveConfig.customApiSave?.multiTable, - enabled: true, - mainTable: config.saveConfig.customApiSave?.multiTable?.mainTable || { tableName: "", primaryKeyColumn: "" }, - subTables: newSubTables, - }, - }, - }); - }} - className="shrink-0" - /> - -
- - {subTable.options?.saveMainAsFirst && ( -
- - - ๋ฉ”์ธ/์„œ๋ธŒ ๊ตฌ๋ถ„์šฉ ์ปฌ๋Ÿผ (์˜ˆ: is_primary) -
- )} - -
- { - const newSubTables = [...(config.saveConfig.customApiSave?.multiTable?.subTables || [])]; - newSubTables[subIndex] = { - ...newSubTables[subIndex], - options: { ...newSubTables[subIndex].options, deleteExistingBefore: !!checked }, - }; - updateSaveConfig({ - customApiSave: { - ...config.saveConfig.customApiSave, - multiTable: { - ...config.saveConfig.customApiSave?.multiTable, - enabled: true, - mainTable: config.saveConfig.customApiSave?.multiTable?.mainTable || { tableName: "", primaryKeyColumn: "" }, - subTables: newSubTables, - }, - }, - }); - }} - className="shrink-0" - /> - -
-
-
- ))} - - {(config.saveConfig.customApiSave?.multiTable?.subTables || []).length === 0 && ( -

- ์„œ๋ธŒ ํ…Œ์ด๋ธ”์„ ์ถ”๊ฐ€ํ•˜์„ธ์š” -

- )} -
-
- )} - - {/* ์ปค์Šคํ…€ API ์„ค์ • */} - {config.saveConfig.customApiSave?.apiType === "custom" && ( -
-
- - - updateSaveConfig({ - customApiSave: { ...config.saveConfig.customApiSave, customEndpoint: e.target.value }, - }) - } - placeholder="/api/custom/endpoint" - className="h-6 text-[10px] mt-1" - /> -
-
- - -
-
- )} -
- )} -
- - {/* ์ €์žฅ ํ›„ ๋™์ž‘ */} -
- -
- ๋ชจ๋‹ฌ ๋‹ซ๊ธฐ - - updateSaveConfig({ - afterSave: { ...config.saveConfig.afterSave, closeModal: checked }, - }) - } - /> -
-
- ๋ถ€๋ชจ ํ™”๋ฉด ์ƒˆ๋กœ๊ณ ์นจ - - updateSaveConfig({ - afterSave: { ...config.saveConfig.afterSave, refreshParent: checked }, - }) - } - /> -
-
- ํ† ์ŠคํŠธ ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ - - updateSaveConfig({ - afterSave: { ...config.saveConfig.afterSave, showToast: checked }, - }) - } - /> -
+ {config.saveConfig.customApiSave?.enabled && config.saveConfig.customApiSave?.multiTable?.enabled && ( + + ๋‹ค์ค‘ ํ…Œ์ด๋ธ” ๋ชจ๋“œ + + )} +
+
+ + ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•  ํ…Œ์ด๋ธ”๊ณผ ๋ฐฉ์‹์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. +
+ "์ €์žฅ ์„ค์ • ์—ด๊ธฐ"๋ฅผ ํด๋ฆญํ•˜์—ฌ ์ƒ์„ธ ์„ค์ •์„ ๋ณ€๊ฒฝํ•˜์„ธ์š”. +
- {/* ์„น์…˜ ๊ด€๋ฆฌ */} - - - -
- - ์„น์…˜ ๊ด€๋ฆฌ ({config.sections.length}) + {/* ์„น์…˜ ๊ตฌ์„ฑ */} + + + +
+ + ์„น์…˜ ๊ตฌ์„ฑ + + {config.sections.length}๊ฐœ +
- - + + ํผ์„ ์—ฌ๋Ÿฌ ์„น์…˜์œผ๋กœ ๋‚˜๋ˆ„์–ด ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +
+ ์˜ˆ: ๊ธฐ๋ณธ ์ •๋ณด, ๋ฐฐ์†ก ์ •๋ณด, ๊ฒฐ์ œ ์ •๋ณด +
- {config.sections.map((section, sectionIndex) => ( - { - setSelectedSectionId(section.id); - setSelectedFieldId(null); - }} - > - -
-
- - {section.title} - {section.repeatable && ( - ๋ฐ˜๋ณต - )} -
-
+ {config.sections.length === 0 ? ( +
+

์„น์…˜์ด ์—†์Šต๋‹ˆ๋‹ค

+

"์„น์…˜ ์ถ”๊ฐ€" ๋ฒ„ํŠผ์œผ๋กœ ํผ ์„น์…˜์„ ๋งŒ๋“œ์„ธ์š”

+
+ ) : ( +
+ {config.sections.map((section, index) => ( +
+ {/* ํ—ค๋”: ์ œ๋ชฉ + ์‚ญ์ œ */} +
+
+
+ {section.title} + {section.repeatable && ( + + ๋ฐ˜๋ณต + + )} +
+ + {section.fields.length}๊ฐœ ํ•„๋“œ + +
+ - -
+ + {/* ์ˆœ์„œ ์กฐ์ • ๋ฒ„ํŠผ */} +
+ +
+ + +
+
+ + {/* ํ•„๋“œ ๋ชฉ๋ก */} + {section.fields.length > 0 && ( +
+ {section.fields.slice(0, 4).map((field) => ( + + {field.label} + + ))} + {section.fields.length > 4 && ( + + +{section.fields.length - 4} + + )} +
+ )} + + {/* ๋ ˆ์ด์•„์›ƒ ์„ค์ • ๋ฒ„ํŠผ */} +
- ํ•„๋“œ {section.fields.length}๊ฐœ - - - ))} + ))} +
+ )} - - {/* ์„ ํƒ๋œ ์„น์…˜ ์„ค์ • */} - {selectedSection && ( - - - -
- - ์„น์…˜: {selectedSection.title} -
-
- -
- - updateSection(selectedSection.id, { title: e.target.value })} - className="h-7 text-xs mt-1" - /> -
- -
- -