From 47ac9ecd8a2c0264be5423d5d20f2ea1649f9415 Mon Sep 17 00:00:00 2001 From: kjs Date: Wed, 7 Jan 2026 16:10:11 +0900 Subject: [PATCH] =?UTF-8?q?=EB=B2=94=EC=9A=A9=ED=8F=BC=EB=AA=A8=EB=8B=AC?= =?UTF-8?q?=20=EC=99=B8=EB=B6=80=EC=86=8C=EC=8A=A4=20=EC=A7=80=EC=9B=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/systemMng/tableMngList/page.tsx | 341 +++++++++++++----- .../TableSectionRenderer.tsx | 60 +++ .../UniversalFormModalComponent.tsx | 1 + .../modals/TableSectionSettingsModal.tsx | 203 +++++++++++ .../components/universal-form-modal/types.ts | 9 + 5 files changed, 525 insertions(+), 89 deletions(-) diff --git a/frontend/app/(main)/admin/systemMng/tableMngList/page.tsx b/frontend/app/(main)/admin/systemMng/tableMngList/page.tsx index 0b5ff573..4ba1e6c0 100644 --- a/frontend/app/(main)/admin/systemMng/tableMngList/page.tsx +++ b/frontend/app/(main)/admin/systemMng/tableMngList/page.tsx @@ -6,7 +6,10 @@ import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Checkbox } from "@/components/ui/checkbox"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -import { Search, Database, RefreshCw, Settings, Plus, Activity, Trash2, Copy } from "lucide-react"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; +import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"; +import { Search, Database, RefreshCw, Settings, Plus, Activity, Trash2, Copy, Check, ChevronsUpDown } from "lucide-react"; +import { cn } from "@/lib/utils"; import { LoadingSpinner } from "@/components/common/LoadingSpinner"; import { toast } from "sonner"; import { useMultiLang } from "@/hooks/useMultiLang"; @@ -90,6 +93,13 @@ export default function TableManagementPage() { // ๐ŸŽฏ Entity ์กฐ์ธ ๊ด€๋ จ ์ƒํƒœ const [referenceTableColumns, setReferenceTableColumns] = useState>({}); + // ๐Ÿ†• Entity ํƒ€์ž… Combobox ์—ด๋ฆผ/๋‹ซํž˜ ์ƒํƒœ (์ปฌ๋Ÿผ๋ณ„ ๊ด€๋ฆฌ) + const [entityComboboxOpen, setEntityComboboxOpen] = useState>({}); + // DDL ๊ธฐ๋Šฅ ๊ด€๋ จ ์ƒํƒœ const [createTableModalOpen, setCreateTableModalOpen] = useState(false); const [addColumnModalOpen, setAddColumnModalOpen] = useState(false); @@ -1388,113 +1398,266 @@ export default function TableManagementPage() { {/* ์ž…๋ ฅ ํƒ€์ž…์ด 'entity'์ธ ๊ฒฝ์šฐ ์ฐธ์กฐ ํ…Œ์ด๋ธ” ์„ ํƒ */} {column.inputType === "entity" && ( <> - {/* ์ฐธ์กฐ ํ…Œ์ด๋ธ” */} -
+ {/* ์ฐธ์กฐ ํ…Œ์ด๋ธ” - ๊ฒ€์ƒ‰ ๊ฐ€๋Šฅํ•œ Combobox */} +
- + + + + + + + + + ํ…Œ์ด๋ธ”์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. + + + {referenceTableOptions.map((option) => ( + { + handleDetailSettingsChange(column.columnName, "entity", option.value); + setEntityComboboxOpen((prev) => ({ + ...prev, + [column.columnName]: { ...prev[column.columnName], table: false }, + })); + }} + className="text-xs" + > + +
+ {option.label} + {option.value !== "none" && ( + {option.value} + )} +
+
+ ))} +
+
+
+
+
- {/* ์กฐ์ธ ์ปฌ๋Ÿผ */} + {/* ์กฐ์ธ ์ปฌ๋Ÿผ - ๊ฒ€์ƒ‰ ๊ฐ€๋Šฅํ•œ Combobox */} {column.referenceTable && column.referenceTable !== "none" && ( -
+
- + ๋กœ๋”ฉ์ค‘... + + ) : column.referenceColumn && column.referenceColumn !== "none" ? ( + column.referenceColumn + ) : ( + "์ปฌ๋Ÿผ ์„ ํƒ..." + )} + + + + + + + + + ์ปฌ๋Ÿผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. + + + { + handleDetailSettingsChange(column.columnName, "entity_reference_column", "none"); + setEntityComboboxOpen((prev) => ({ + ...prev, + [column.columnName]: { ...prev[column.columnName], joinColumn: false }, + })); + }} + className="text-xs" + > + + -- ์„ ํƒ ์•ˆํ•จ -- + + {referenceTableColumns[column.referenceTable]?.map((refCol) => ( + { + handleDetailSettingsChange(column.columnName, "entity_reference_column", refCol.columnName); + setEntityComboboxOpen((prev) => ({ + ...prev, + [column.columnName]: { ...prev[column.columnName], joinColumn: false }, + })); + }} + className="text-xs" + > + +
+ {refCol.columnName} + {refCol.columnLabel && ( + {refCol.columnLabel} + )} +
+
+ ))} +
+
+
+
+
)} - {/* ํ‘œ์‹œ ์ปฌ๋Ÿผ */} + {/* ํ‘œ์‹œ ์ปฌ๋Ÿผ - ๊ฒ€์ƒ‰ ๊ฐ€๋Šฅํ•œ Combobox */} {column.referenceTable && column.referenceTable !== "none" && column.referenceColumn && column.referenceColumn !== "none" && ( -
+
- + ๋กœ๋”ฉ์ค‘... + + ) : column.displayColumn && column.displayColumn !== "none" ? ( + column.displayColumn + ) : ( + "์ปฌ๋Ÿผ ์„ ํƒ..." + )} + + + + + + + + + ์ปฌ๋Ÿผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. + + + { + handleDetailSettingsChange(column.columnName, "entity_display_column", "none"); + setEntityComboboxOpen((prev) => ({ + ...prev, + [column.columnName]: { ...prev[column.columnName], displayColumn: false }, + })); + }} + className="text-xs" + > + + -- ์„ ํƒ ์•ˆํ•จ -- + + {referenceTableColumns[column.referenceTable]?.map((refCol) => ( + { + handleDetailSettingsChange(column.columnName, "entity_display_column", refCol.columnName); + setEntityComboboxOpen((prev) => ({ + ...prev, + [column.columnName]: { ...prev[column.columnName], displayColumn: false }, + })); + }} + className="text-xs" + > + +
+ {refCol.columnName} + {refCol.columnLabel && ( + {refCol.columnLabel} + )} +
+
+ ))} +
+
+
+
+
)} @@ -1505,8 +1668,8 @@ export default function TableManagementPage() { column.referenceColumn !== "none" && column.displayColumn && column.displayColumn !== "none" && ( -
- โœ“ +
+ ์„ค์ • ์™„๋ฃŒ
)} diff --git a/frontend/lib/registry/components/universal-form-modal/TableSectionRenderer.tsx b/frontend/lib/registry/components/universal-form-modal/TableSectionRenderer.tsx index 4f872bc1..ac43e1ed 100644 --- a/frontend/lib/registry/components/universal-form-modal/TableSectionRenderer.tsx +++ b/frontend/lib/registry/components/universal-form-modal/TableSectionRenderer.tsx @@ -32,6 +32,8 @@ interface TableSectionRendererProps { onTableDataChange: (data: any[]) => void; // ์กฐ๊ฑด๋ถ€ ํ…Œ์ด๋ธ”์šฉ ์ฝœ๋ฐฑ (์กฐ๊ฑด๋ณ„ ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ) onConditionalTableDataChange?: (conditionValue: string, data: any[]) => void; + // ์™ธ๋ถ€ ๋ฐ์ดํ„ฐ (๋ฐ์ดํ„ฐ ์ „๋‹ฌ ๋ชจ๋‹ฌ์—ด๊ธฐ ์•ก์…˜์œผ๋กœ ์ „๋‹ฌ๋ฐ›์€ ๋ฐ์ดํ„ฐ) + groupedData?: Record[]; className?: string; } @@ -337,6 +339,7 @@ export function TableSectionRenderer({ onFormDataChange, onTableDataChange, onConditionalTableDataChange, + groupedData, className, }: TableSectionRendererProps) { // ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ ์ƒํƒœ (์ผ๋ฐ˜ ๋ชจ๋“œ) @@ -373,6 +376,13 @@ export function TableSectionRenderer({ // ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์™„๋ฃŒ ํ”Œ๋ž˜๊ทธ (๋ฌดํ•œ ๋ฃจํ”„ ๋ฐฉ์ง€) const initialDataLoadedRef = React.useRef(false); + // ์™ธ๋ถ€ ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์™„๋ฃŒ ํ”Œ๋ž˜๊ทธ + const externalDataLoadedRef = React.useRef(false); + + // ์™ธ๋ถ€ ๋ฐ์ดํ„ฐ ์†Œ์Šค ์„ค์ • + const externalDataConfig = tableConfig.externalDataSource; + const isExternalDataMode = externalDataConfig?.enabled && externalDataConfig?.tableName; + // ์กฐ๊ฑด๋ถ€ ํ…Œ์ด๋ธ” ์„ค์ • const conditionalConfig = tableConfig.conditionalTable; const isConditionalMode = conditionalConfig?.enabled ?? false; @@ -388,6 +398,56 @@ export function TableSectionRenderer({ // ์†Œ์Šค ํ…Œ์ด๋ธ”์˜ ์ปฌ๋Ÿผ ๋ผ๋ฒจ (API์—์„œ ๋™์  ๋กœ๋“œ) const [sourceColumnLabels, setSourceColumnLabels] = useState>({}); + // ์™ธ๋ถ€ ๋ฐ์ดํ„ฐ(groupedData) ์ฒ˜๋ฆฌ: ๋ฐ์ดํ„ฐ ์ „๋‹ฌ ๋ชจ๋‹ฌ์—ด๊ธฐ ์•ก์…˜์œผ๋กœ ์ „๋‹ฌ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋ฅผ ์ดˆ๊ธฐ ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ๋กœ ์„ค์ • + useEffect(() => { + // ์™ธ๋ถ€ ๋ฐ์ดํ„ฐ ์†Œ์Šค๊ฐ€ ํ™œ์„ฑํ™”๋˜์ง€ ์•Š์•˜๊ฑฐ๋‚˜, groupedData๊ฐ€ ์—†์œผ๋ฉด ์Šคํ‚ต + if (!isExternalDataMode) return; + if (!groupedData || groupedData.length === 0) return; + // ์ด๋ฏธ ๋กœ๋“œ๋œ ๊ฒฝ์šฐ ์Šคํ‚ต + if (externalDataLoadedRef.current) return; + + console.log("[TableSectionRenderer] ์™ธ๋ถ€ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ์‹œ์ž‘:", { + externalTableName: externalDataConfig?.tableName, + groupedDataCount: groupedData.length, + columns: tableConfig.columns?.length, + }); + + // groupedData๋ฅผ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ๋งคํ•‘์— ๋”ฐ๋ผ ๋ณ€ํ™˜ + const mappedData = groupedData.map((externalRow, index) => { + const newRow: Record = { + _id: `external_${Date.now()}_${index}`, + _sourceData: externalRow, // ์›๋ณธ ๋ฐ์ดํ„ฐ ๋ณด๊ด€ + }; + + // ๊ฐ ์ปฌ๋Ÿผ์— ๋Œ€ํ•ด externalField ๋˜๋Š” field๋กœ ๊ฐ’์„ ๋งคํ•‘ + tableConfig.columns?.forEach((col) => { + // externalField๊ฐ€ ์„ค์ •๋˜์–ด ์žˆ์œผ๋ฉด ์‚ฌ์šฉ, ์•„๋‹ˆ๋ฉด field์™€ ๋™์ผํ•œ ์ด๋ฆ„์œผ๋กœ ๋งคํ•‘ + const externalFieldName = col.externalField || col.field; + const value = externalRow[externalFieldName]; + + // ๊ฐ’์ด ์žˆ์œผ๋ฉด ์„ค์ • + if (value !== undefined) { + newRow[col.field] = value; + } else if (col.defaultValue !== undefined) { + // ๊ธฐ๋ณธ๊ฐ’์ด ์„ค์ •๋˜์–ด ์žˆ์œผ๋ฉด ๊ธฐ๋ณธ๊ฐ’ ์‚ฌ์šฉ + newRow[col.field] = col.defaultValue; + } + }); + + return newRow; + }); + + console.log("[TableSectionRenderer] ์™ธ๋ถ€ ๋ฐ์ดํ„ฐ ๋งคํ•‘ ์™„๋ฃŒ:", { + mappedCount: mappedData.length, + sampleRow: mappedData[0], + }); + + // ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ ์„ค์ • + setTableData(mappedData); + onTableDataChange(mappedData); + externalDataLoadedRef.current = true; + }, [isExternalDataMode, groupedData, tableConfig.columns, externalDataConfig?.tableName, onTableDataChange]); + // ์†Œ์Šค ํ…Œ์ด๋ธ”์˜ ์นดํ…Œ๊ณ ๋ฆฌ ํƒ€์ž… ์ปฌ๋Ÿผ ๋ชฉ๋ก ๋กœ๋“œ useEffect(() => { const loadCategoryColumns = async () => { diff --git a/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx b/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx index cebd8aa6..0b0f73a6 100644 --- a/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx +++ b/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx @@ -2295,6 +2295,7 @@ export function UniversalFormModalComponent({ // ํ…Œ์ด๋ธ” ์„น์…˜ ๋ฐ์ดํ„ฐ๋ฅผ formData์— ์ €์žฅ handleFieldChange(`_tableSection_${section.id}`, data); }} + groupedData={_groupedData} /> diff --git a/frontend/lib/registry/components/universal-form-modal/modals/TableSectionSettingsModal.tsx b/frontend/lib/registry/components/universal-form-modal/modals/TableSectionSettingsModal.tsx index a7bec622..a145b49a 100644 --- a/frontend/lib/registry/components/universal-form-modal/modals/TableSectionSettingsModal.tsx +++ b/frontend/lib/registry/components/universal-form-modal/modals/TableSectionSettingsModal.tsx @@ -710,6 +710,9 @@ interface ColumnSettingItemProps { displayColumns: string[]; // ๊ฒ€์ƒ‰ ์„ค์ •์—์„œ ์„ ํƒํ•œ ํ‘œ์‹œ ์ปฌ๋Ÿผ ๋ชฉ๋ก sourceTableColumns: { column_name: string; data_type: string; is_nullable: string; comment?: string }[]; // ์†Œ์Šค ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ sourceTableName: string; // ์†Œ์Šค ํ…Œ์ด๋ธ”๋ช… + externalTableColumns: { column_name: string; data_type: string; is_nullable: string; comment?: string }[]; // ์™ธ๋ถ€ ๋ฐ์ดํ„ฐ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ + externalTableName?: string; // ์™ธ๋ถ€ ๋ฐ์ดํ„ฐ ํ…Œ์ด๋ธ”๋ช… + externalDataEnabled?: boolean; // ์™ธ๋ถ€ ๋ฐ์ดํ„ฐ ์†Œ์Šค ํ™œ์„ฑํ™” ์—ฌ๋ถ€ tables: { table_name: string; comment?: string }[]; // ์ „์ฒด ํ…Œ์ด๋ธ” ๋ชฉ๋ก tableColumns: Record; // ํ…Œ์ด๋ธ”๋ณ„ ์ปฌ๋Ÿผ sections: { id: string; title: string }[]; // ์„น์…˜ ๋ชฉ๋ก @@ -731,6 +734,9 @@ function ColumnSettingItem({ displayColumns, sourceTableColumns, sourceTableName, + externalTableColumns, + externalTableName, + externalDataEnabled, tables, tableColumns, sections, @@ -745,6 +751,7 @@ function ColumnSettingItem({ }: ColumnSettingItemProps) { const [fieldSearchOpen, setFieldSearchOpen] = useState(false); const [sourceFieldSearchOpen, setSourceFieldSearchOpen] = useState(false); + const [externalFieldSearchOpen, setExternalFieldSearchOpen] = useState(false); const [parentFieldSearchOpen, setParentFieldSearchOpen] = useState(false); const [lookupTableOpenMap, setLookupTableOpenMap] = useState>({}); @@ -1014,6 +1021,88 @@ function ColumnSettingItem({
+ {/* ์™ธ๋ถ€ ํ•„๋“œ - Combobox (์™ธ๋ถ€ ๋ฐ์ดํ„ฐ์—์„œ ๊ฐ€์ ธ์˜ฌ ์ปฌ๋Ÿผ) */} + {externalDataEnabled && externalTableName && ( +
+ + + + + + + + + + + ์™ธ๋ถ€ ํ•„๋“œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. + + + {/* ํ•„๋“œ๋ช…๊ณผ ๋™์ผ ์˜ต์…˜ */} + { + onUpdate({ externalField: undefined }); + setExternalFieldSearchOpen(false); + }} + className="text-xs" + > + + (ํ•„๋“œ๋ช…๊ณผ ๋™์ผ) + + {/* ์™ธ๋ถ€ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ๋ชฉ๋ก */} + {externalTableColumns.map((extCol) => ( + { + onUpdate({ externalField: extCol.column_name }); + setExternalFieldSearchOpen(false); + }} + className="text-xs" + > + +
+ {extCol.column_name} + {extCol.comment && ( + + {extCol.comment} + + )} +
+
+ ))} +
+
+
+
+
+

+ ์™ธ๋ถ€ ๋ฐ์ดํ„ฐ({externalTableName})์—์„œ ์ด ์ปฌ๋Ÿผ์— ๋งคํ•‘ํ•  ํ•„๋“œ +

+
+ )} + {/* ๋ผ๋ฒจ */}
@@ -2450,6 +2539,7 @@ export function TableSectionSettingsModal({ // ํ…Œ์ด๋ธ” ๊ฒ€์ƒ‰ Combobox ์ƒํƒœ const [tableSearchOpen, setTableSearchOpen] = useState(false); const [saveTableSearchOpen, setSaveTableSearchOpen] = useState(false); + const [externalTableSearchOpen, setExternalTableSearchOpen] = useState(false); // ํ™œ์„ฑ ํƒญ const [activeTab, setActiveTab] = useState("source"); @@ -2623,6 +2713,24 @@ export function TableSectionSettingsModal({ }); }; + const updateExternalDataSource = (updates: Partial>) => { + updateTableConfig({ + externalDataSource: { ...tableConfig.externalDataSource, enabled: false, tableName: "", ...updates }, + }); + }; + + // ์™ธ๋ถ€ ๋ฐ์ดํ„ฐ ์†Œ์Šค ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ๋ชฉ๋ก + const externalTableColumns = useMemo(() => { + return tableColumns[tableConfig.externalDataSource?.tableName || ""] || []; + }, [tableColumns, tableConfig.externalDataSource?.tableName]); + + // ์™ธ๋ถ€ ๋ฐ์ดํ„ฐ ์†Œ์Šค ํ…Œ์ด๋ธ” ๋ณ€๊ฒฝ ์‹œ ์ปฌ๋Ÿผ ๋กœ๋“œ + useEffect(() => { + if (tableConfig.externalDataSource?.enabled && tableConfig.externalDataSource?.tableName) { + onLoadTableColumns(tableConfig.externalDataSource.tableName); + } + }, [tableConfig.externalDataSource?.enabled, tableConfig.externalDataSource?.tableName, onLoadTableColumns]); + // ์ €์žฅ ํ•จ์ˆ˜ const handleSave = () => { onSave({ @@ -2986,6 +3094,98 @@ export function TableSectionSettingsModal({
+ + {/* ์™ธ๋ถ€ ๋ฐ์ดํ„ฐ ์†Œ์Šค ์„ค์ • */} +
+
+
+

์™ธ๋ถ€ ๋ฐ์ดํ„ฐ ์†Œ์Šค

+

+ "๋ฐ์ดํ„ฐ ์ „๋‹ฌ ๋ชจ๋‹ฌ์—ด๊ธฐ" ์•ก์…˜์œผ๋กœ ์ „๋‹ฌ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋ฅผ ํ…Œ์ด๋ธ”์— ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค. +

+
+ { + if (checked) { + updateExternalDataSource({ enabled: true, tableName: "" }); + } else { + updateTableConfig({ externalDataSource: undefined }); + } + }} + /> +
+ + {tableConfig.externalDataSource?.enabled && ( +
+
+ + + + + + + + + + + ํ…Œ์ด๋ธ”์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. + + + {tables.map((table) => ( + { + updateExternalDataSource({ enabled: true, tableName: table.table_name }); + onLoadTableColumns(table.table_name); + setExternalTableSearchOpen(false); + }} + className="text-sm" + > + +
+ {table.table_name} + {table.comment && ( + {table.comment} + )} +
+
+ ))} +
+
+
+
+
+ ์ด์ „ ํ™”๋ฉด์—์„œ ์ „๋‹ฌ๋ฐ›์„ ๋ฐ์ดํ„ฐ์˜ ์›๋ณธ ํ…Œ์ด๋ธ”์„ ์„ ํƒํ•˜์„ธ์š”. (์˜ˆ: ์ˆ˜์ฃผ์ƒ์„ธ ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌ๋ฐ›๋Š” ๊ฒฝ์šฐ sales_order_detail) +
+ + {tableConfig.externalDataSource?.tableName && externalTableColumns.length > 0 && ( +
+

+ ์„ ํƒํ•œ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ: {externalTableColumns.length}๊ฐœ +

+

+ "์ปฌ๋Ÿผ ์„ค์ •" ํƒญ์—์„œ ๊ฐ ์ปฌ๋Ÿผ์˜ "์™ธ๋ถ€ ํ•„๋“œ"๋ฅผ ์„ค์ •ํ•˜์—ฌ ์ „๋‹ฌ๋ฐ›์€ ๋ฐ์ดํ„ฐ์˜ ์ปฌ๋Ÿผ์„ ๋งคํ•‘ํ•˜์„ธ์š”. +

+
+ )} +
+ )} +
{/* ์ปฌ๋Ÿผ ์„ค์ • ํƒญ */} @@ -3041,6 +3241,9 @@ export function TableSectionSettingsModal({ displayColumns={tableConfig.source.displayColumns || []} sourceTableColumns={sourceTableColumns} sourceTableName={tableConfig.source.tableName} + externalTableColumns={externalTableColumns} + externalTableName={tableConfig.externalDataSource?.tableName} + externalDataEnabled={tableConfig.externalDataSource?.enabled} tables={tables} tableColumns={tableColumns} sections={otherSections} diff --git a/frontend/lib/registry/components/universal-form-modal/types.ts b/frontend/lib/registry/components/universal-form-modal/types.ts index 9d54270b..303705b6 100644 --- a/frontend/lib/registry/components/universal-form-modal/types.ts +++ b/frontend/lib/registry/components/universal-form-modal/types.ts @@ -230,6 +230,12 @@ export interface TableSectionConfig { columnLabels?: Record; // ์ปฌ๋Ÿผ ๋ผ๋ฒจ (์ปฌ๋Ÿผ๋ช… -> ํ‘œ์‹œ ๋ผ๋ฒจ) }; + // 1-1. ์™ธ๋ถ€ ๋ฐ์ดํ„ฐ ์†Œ์Šค ์„ค์ • (๋ฐ์ดํ„ฐ ์ „๋‹ฌ ๋ชจ๋‹ฌ์—ด๊ธฐ ์•ก์…˜์œผ๋กœ ์ „๋‹ฌ๋ฐ›์€ ๋ฐ์ดํ„ฐ) + externalDataSource?: { + enabled: boolean; // ์™ธ๋ถ€ ๋ฐ์ดํ„ฐ ์†Œ์Šค ์‚ฌ์šฉ ์—ฌ๋ถ€ + tableName: string; // ์ „๋‹ฌ๋ฐ›์„ ๋ฐ์ดํ„ฐ์˜ ์†Œ์Šค ํ…Œ์ด๋ธ”๋ช… (์˜ˆ: sales_order_detail) + }; + // 2. ํ•„ํ„ฐ ์„ค์ • filters?: { // ์‚ฌ์ „ ํ•„ํ„ฐ (ํ•ญ์ƒ ์ ์šฉ, ์‚ฌ์šฉ์ž์—๊ฒŒ ๋…ธ์ถœ๋˜์ง€ ์•Š์Œ) @@ -374,6 +380,9 @@ export interface TableColumnConfig { // ์†Œ์Šค ํ•„๋“œ ๋งคํ•‘ (๊ฒ€์ƒ‰ ๋ชจ๋‹ฌ์—์„œ ๊ฐ€์ ธ์˜ฌ ์ปฌ๋Ÿผ๋ช…) sourceField?: string; // ์†Œ์Šค ํ…Œ์ด๋ธ”์˜ ์ปฌ๋Ÿผ๋ช… (๋ฏธ์„ค์ • ์‹œ field์™€ ๋™์ผ) + // ์™ธ๋ถ€ ๋ฐ์ดํ„ฐ ํ•„๋“œ ๋งคํ•‘ (๋ฐ์ดํ„ฐ ์ „๋‹ฌ ๋ชจ๋‹ฌ์—ด๊ธฐ๋กœ ์ „๋‹ฌ๋ฐ›์€ ๋ฐ์ดํ„ฐ์˜ ์ปฌ๋Ÿผ๋ช…) + externalField?: string; // ์™ธ๋ถ€ ๋ฐ์ดํ„ฐ์˜ ์ปฌ๋Ÿผ๋ช… (๋ฏธ์„ค์ • ์‹œ field์™€ ๋™์ผ) + // ํŽธ์ง‘ ์„ค์ • editable?: boolean; // ํŽธ์ง‘ ๊ฐ€๋Šฅ ์—ฌ๋ถ€ (๊ธฐ๋ณธ: true) calculated?: boolean; // ๊ณ„์‚ฐ ํ•„๋“œ ์—ฌ๋ถ€ (์ž๋™ ์ฝ๊ธฐ์ „์šฉ)