From 85519e302fd99de8fab4308ccf104a77c79e8d01 Mon Sep 17 00:00:00 2001 From: kjs Date: Mon, 5 Jan 2026 13:54:41 +0900 Subject: [PATCH 1/6] =?UTF-8?q?=ED=96=89=EC=84=A0=ED=83=9D=EC=8B=9C?= =?UTF-8?q?=EC=97=90=EB=A7=8C=20=EB=B2=84=ED=8A=BC=20=ED=99=9C=EC=84=B1?= =?UTF-8?q?=ED=99=94=20=EA=B8=B0=EB=8A=A5=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../button-primary/ButtonPrimaryComponent.tsx | 22 +++++-- .../table-list/TableListComponent.tsx | 64 +++++++++---------- 2 files changed, 49 insertions(+), 37 deletions(-) diff --git a/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx b/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx index f311c035..a71f6e03 100644 --- a/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx +++ b/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx @@ -299,6 +299,20 @@ export const ButtonPrimaryComponent: React.FC = ({ // ๐Ÿ†• modalDataStore์—์„œ ์„ ํƒ๋œ ๋ฐ์ดํ„ฐ ํ™•์ธ (๋ถ„ํ•  ํŒจ๋„ ๋“ฑ์—์„œ ์ €์žฅ๋จ) const [modalStoreData, setModalStoreData] = useState>({}); + // ๐Ÿ†• splitPanelContext?.selectedLeftData๋ฅผ ๋กœ์ปฌ ์ƒํƒœ๋กœ ์ถ”์  (๋ฆฌ๋ Œ๋”๋ง ๋ณด์žฅ) + const [trackedSelectedLeftData, setTrackedSelectedLeftData] = useState | null>(null); + + // splitPanelContext?.selectedLeftData ๋ณ€๊ฒฝ ๊ฐ์ง€ ๋ฐ ๋กœ์ปฌ ์ƒํƒœ ๋™๊ธฐํ™” + useEffect(() => { + const newData = splitPanelContext?.selectedLeftData ?? null; + setTrackedSelectedLeftData(newData); + console.log("๐Ÿ”„ [ButtonPrimary] selectedLeftData ๋ณ€๊ฒฝ ๊ฐ์ง€:", { + label: component.label, + hasData: !!newData, + dataKeys: newData ? Object.keys(newData) : [], + }); + }, [splitPanelContext?.selectedLeftData, component.label]); + // modalDataStore ์ƒํƒœ ๊ตฌ๋… (์‹ค์‹œ๊ฐ„ ์—…๋ฐ์ดํŠธ) useEffect(() => { const actionConfig = component.componentConfig?.action; @@ -357,8 +371,8 @@ export const ButtonPrimaryComponent: React.FC = ({ // 2. ๋ถ„ํ•  ํŒจ๋„ ์ขŒ์ธก ์„ ํƒ ๋ฐ์ดํ„ฐ ํ™•์ธ if (rowSelectionSource === "auto" || rowSelectionSource === "splitPanelLeft") { - // SplitPanelContext์—์„œ ํ™•์ธ - if (splitPanelContext?.selectedLeftData && Object.keys(splitPanelContext.selectedLeftData).length > 0) { + // SplitPanelContext์—์„œ ํ™•์ธ (trackedSelectedLeftData ์‚ฌ์šฉ์œผ๋กœ ๋ฆฌ๋ Œ๋”๋ง ๋ณด์žฅ) + if (trackedSelectedLeftData && Object.keys(trackedSelectedLeftData).length > 0) { if (!hasSelection) { hasSelection = true; selectionCount = 1; @@ -397,7 +411,7 @@ export const ButtonPrimaryComponent: React.FC = ({ selectionCount, selectionSource, hasSplitPanelContext: !!splitPanelContext, - selectedLeftData: splitPanelContext?.selectedLeftData, + trackedSelectedLeftData: trackedSelectedLeftData, selectedRowsData: selectedRowsData?.length, selectedRows: selectedRows?.length, flowSelectedData: flowSelectedData?.length, @@ -429,7 +443,7 @@ export const ButtonPrimaryComponent: React.FC = ({ component.label, selectedRows, selectedRowsData, - splitPanelContext?.selectedLeftData, + trackedSelectedLeftData, flowSelectedData, splitPanelContext, modalStoreData, diff --git a/frontend/lib/registry/components/table-list/TableListComponent.tsx b/frontend/lib/registry/components/table-list/TableListComponent.tsx index 7ac521af..7a787ed3 100644 --- a/frontend/lib/registry/components/table-list/TableListComponent.tsx +++ b/frontend/lib/registry/components/table-list/TableListComponent.tsx @@ -2043,7 +2043,7 @@ export const TableListComponent: React.FC = ({ return row.id || row.uuid || `row-${index}`; }; - const handleRowSelection = (rowKey: string, checked: boolean) => { + const handleRowSelection = (rowKey: string, checked: boolean, rowData?: any) => { const newSelectedRows = new Set(selectedRows); if (checked) { newSelectedRows.add(rowKey); @@ -2086,6 +2086,31 @@ export const TableListComponent: React.FC = ({ }); } + // ๐Ÿ†• ๋ถ„ํ•  ํŒจ๋„ ์ปจํ…์ŠคํŠธ์— ์„ ํƒ๋œ ๋ฐ์ดํ„ฐ ์ €์žฅ/ํ•ด์ œ (์ฒดํฌ๋ฐ•์Šค ์„ ํƒ ์‹œ์—๋„ ์ž‘๋™) + const effectiveSplitPosition = splitPanelPosition || currentSplitPosition; + if (splitPanelContext && effectiveSplitPosition === "left" && !splitPanelContext.disableAutoDataTransfer) { + if (checked && selectedRowsData.length > 0) { + // ์„ ํƒ๋œ ๊ฒฝ์šฐ: ์ฒซ ๋ฒˆ์งธ ์„ ํƒ๋œ ๋ฐ์ดํ„ฐ ์ €์žฅ (๋˜๋Š” ์ „๋‹ฌ๋œ rowData) + const dataToStore = rowData || selectedRowsData[selectedRowsData.length - 1]; + splitPanelContext.setSelectedLeftData(dataToStore); + console.log("๐Ÿ”— [TableList] handleRowSelection - ๋ถ„ํ•  ํŒจ๋„ ์ขŒ์ธก ๋ฐ์ดํ„ฐ ์ €์žฅ:", { + rowKey, + dataToStore, + }); + } else if (!checked && selectedRowsData.length === 0) { + // ๋ชจ๋“  ์„ ํƒ์ด ํ•ด์ œ๋œ ๊ฒฝ์šฐ: ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐํ™” + splitPanelContext.setSelectedLeftData(null); + console.log("๐Ÿ”— [TableList] handleRowSelection - ๋ถ„ํ•  ํŒจ๋„ ์ขŒ์ธก ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐํ™”"); + } else if (selectedRowsData.length > 0) { + // ์ผ๋ถ€ ์„ ํƒ ํ•ด์ œ๋œ ๊ฒฝ์šฐ: ๋‚จ์€ ์ฒซ ๋ฒˆ์งธ ๋ฐ์ดํ„ฐ๋กœ ์—…๋ฐ์ดํŠธ + splitPanelContext.setSelectedLeftData(selectedRowsData[0]); + console.log("๐Ÿ”— [TableList] handleRowSelection - ๋ถ„ํ•  ํŒจ๋„ ์ขŒ์ธก ๋ฐ์ดํ„ฐ ์—…๋ฐ์ดํŠธ:", { + remainingCount: selectedRowsData.length, + firstData: selectedRowsData[0], + }); + } + } + const allRowsSelected = filteredData.every((row, index) => newSelectedRows.has(getRowKey(row, index))); setIsAllSelected(allRowsSelected && filteredData.length > 0); }; @@ -2155,35 +2180,8 @@ export const TableListComponent: React.FC = ({ const rowKey = getRowKey(row, index); const isCurrentlySelected = selectedRows.has(rowKey); - handleRowSelection(rowKey, !isCurrentlySelected); - - // ๐Ÿ†• ๋ถ„ํ•  ํŒจ๋„ ์ปจํ…์ŠคํŠธ์— ์„ ํƒ๋œ ๋ฐ์ดํ„ฐ ์ €์žฅ (์ขŒ์ธก ํ™”๋ฉด์ธ ๊ฒฝ์šฐ) - // disableAutoDataTransfer๊ฐ€ true์ด๋ฉด ์ž๋™ ์ „๋‹ฌ ๋น„ํ™œ์„ฑํ™” (๋ฒ„ํŠผ ํด๋ฆญ์œผ๋กœ๋งŒ ์ „๋‹ฌ) - // currentSplitPosition์„ ์‚ฌ์šฉํ•˜์—ฌ ์ •ํ™•ํ•œ ์œ„์น˜ ํ™•์ธ (splitPanelPosition์ด ์—†์„ ์ˆ˜ ์žˆ์Œ) - const effectiveSplitPosition = splitPanelPosition || currentSplitPosition; - - console.log("๐Ÿ”— [TableList] ํ–‰ ํด๋ฆญ - ๋ถ„ํ•  ํŒจ๋„ ์œ„์น˜ ํ™•์ธ:", { - splitPanelPosition, - currentSplitPosition, - effectiveSplitPosition, - hasSplitPanelContext: !!splitPanelContext, - disableAutoDataTransfer: splitPanelContext?.disableAutoDataTransfer, - }); - - if (splitPanelContext && effectiveSplitPosition === "left" && !splitPanelContext.disableAutoDataTransfer) { - if (!isCurrentlySelected) { - // ์„ ํƒ๋œ ๊ฒฝ์šฐ: ๋ฐ์ดํ„ฐ ์ €์žฅ - splitPanelContext.setSelectedLeftData(row); - console.log("๐Ÿ”— [TableList] ๋ถ„ํ•  ํŒจ๋„ ์ขŒ์ธก ๋ฐ์ดํ„ฐ ์ €์žฅ:", { - row, - parentDataMapping: splitPanelContext.parentDataMapping, - }); - } else { - // ์„ ํƒ ํ•ด์ œ๋œ ๊ฒฝ์šฐ: ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐํ™” - splitPanelContext.setSelectedLeftData(null); - console.log("๐Ÿ”— [TableList] ๋ถ„ํ•  ํŒจ๋„ ์ขŒ์ธก ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐํ™”"); - } - } + // handleRowSelection์—์„œ ๋ถ„ํ•  ํŒจ๋„ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ๋„ ํ•จ๊ป˜ ์ˆ˜ํ–‰๋จ + handleRowSelection(rowKey, !isCurrentlySelected, row); console.log("ํ–‰ ํด๋ฆญ:", { row, index, isSelected: !isCurrentlySelected }); }; @@ -3918,7 +3916,7 @@ export const TableListComponent: React.FC = ({ if (enterRow) { const rowKey = getRowKey(enterRow, rowIndex); const isCurrentlySelected = selectedRows.has(rowKey); - handleRowSelection(rowKey, !isCurrentlySelected); + handleRowSelection(rowKey, !isCurrentlySelected, enterRow); } break; case " ": // Space @@ -3928,7 +3926,7 @@ export const TableListComponent: React.FC = ({ if (spaceRow) { const currentRowKey = getRowKey(spaceRow, rowIndex); const isChecked = selectedRows.has(currentRowKey); - handleRowSelection(currentRowKey, !isChecked); + handleRowSelection(currentRowKey, !isChecked, spaceRow); } break; case "F2": @@ -4142,7 +4140,7 @@ export const TableListComponent: React.FC = ({ return ( handleRowSelection(rowKey, checked as boolean)} + onCheckedChange={(checked) => handleRowSelection(rowKey, checked as boolean, row)} aria-label={`ํ–‰ ${index + 1} ์„ ํƒ`} /> ); From 2a7066b6fd6feddbcf4d86a88aea1c6f3332c11a Mon Sep 17 00:00:00 2001 From: hjjeong Date: Mon, 5 Jan 2026 17:08:03 +0900 Subject: [PATCH 2/6] =?UTF-8?q?=ED=85=8C=EC=9D=B4=EB=B8=94=EC=97=90=20?= =?UTF-8?q?=EC=A1=B4=EC=9E=AC=ED=95=98=EB=8A=94=20=EC=BB=AC=EB=9F=BC?= =?UTF-8?q?=EB=A7=8C=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend-node/src/services/tableManagementService.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/backend-node/src/services/tableManagementService.ts b/backend-node/src/services/tableManagementService.ts index def9a978..8ac5989b 100644 --- a/backend-node/src/services/tableManagementService.ts +++ b/backend-node/src/services/tableManagementService.ts @@ -2409,11 +2409,19 @@ export class TableManagementService { } // SET ์ ˆ ์ƒ์„ฑ (์ˆ˜์ •ํ•  ๋ฐ์ดํ„ฐ) - ๋จผ์ € ์ƒ์„ฑ + // ๐Ÿ”ง ํ…Œ์ด๋ธ”์— ์กด์žฌํ•˜๋Š” ์ปฌ๋Ÿผ๋งŒ UPDATE (๊ฐ€์ƒ ์ปฌ๋Ÿผ ์ œ์™ธ) const setConditions: string[] = []; const setValues: any[] = []; let paramIndex = 1; + const skippedColumns: string[] = []; Object.keys(updatedData).forEach((column) => { + // ํ…Œ์ด๋ธ”์— ์กด์žฌํ•˜์ง€ ์•Š๋Š” ์ปฌ๋Ÿผ์€ ์Šคํ‚ต + if (!columnTypeMap.has(column)) { + skippedColumns.push(column); + return; + } + const dataType = columnTypeMap.get(column) || "text"; setConditions.push( `"${column}" = $${paramIndex}::${this.getPostgreSQLType(dataType)}` @@ -2424,6 +2432,10 @@ export class TableManagementService { paramIndex++; }); + if (skippedColumns.length > 0) { + logger.info(`โš ๏ธ ํ…Œ์ด๋ธ”์— ์กด์žฌํ•˜์ง€ ์•Š๋Š” ์ปฌ๋Ÿผ ์Šคํ‚ต: ${skippedColumns.join(", ")}`); + } + // WHERE ์กฐ๊ฑด ์ƒ์„ฑ (PRIMARY KEY ์šฐ์„ , ์—†์œผ๋ฉด ๋ชจ๋“  ์›๋ณธ ๋ฐ์ดํ„ฐ ์‚ฌ์šฉ) let whereConditions: string[] = []; let whereValues: any[] = []; From 714698c20f6e7109c096563ae1275cc4dc1f6568 Mon Sep 17 00:00:00 2001 From: hjjeong Date: Mon, 5 Jan 2026 17:08:47 +0900 Subject: [PATCH 3/6] =?UTF-8?q?=EA=B5=AC=EB=A7=A4=EA=B4=80=EB=A6=AC=5F?= =?UTF-8?q?=EB=B0=9C=EC=A3=BC=EA=B4=80=EB=A6=AC=20=EC=A0=80=EC=9E=A5=20?= =?UTF-8?q?=EC=8B=9C=20=EB=A7=88=EC=8A=A4=ED=84=B0=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=ED=92=88=EB=AA=A9=EC=97=90=20=EC=A0=84=EB=8B=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/lib/utils/buttonActions.ts | 34 +++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/frontend/lib/utils/buttonActions.ts b/frontend/lib/utils/buttonActions.ts index 5587fc1a..681e9a3f 100644 --- a/frontend/lib/utils/buttonActions.ts +++ b/frontend/lib/utils/buttonActions.ts @@ -995,6 +995,40 @@ export class ButtonActionExecutor { console.log("๐Ÿ“‹ [handleSave] ๋ฒ”์šฉ ํผ ๋ชจ๋‹ฌ ๊ณตํ†ต ํ•„๋“œ:", commonFields); } + // ๐Ÿ†• ๋ฃจํŠธ ๋ ˆ๋ฒจ formData์—์„œ RepeaterFieldGroup์— ์ „๋‹ฌํ•  ๊ณตํ†ต ํ•„๋“œ ์ถ”์ถœ + // ์ฃผ๋ฌธ๋ฒˆํ˜ธ, ๋ฐœ์ฃผ๋ฒˆํ˜ธ ๋“ฑ ๋งˆ์Šคํ„ฐ-๋””ํ…Œ์ผ ๊ด€๊ณ„์—์„œ ํ•„์š”ํ•œ ํ•„๋“œ๋งŒ ๋ช…์‹œ์ ์œผ๋กœ ์ง€์ • + const masterDetailFields = [ + // ๋ฒˆํ˜ธ ํ•„๋“œ + "order_no", // ๋ฐœ์ฃผ๋ฒˆํ˜ธ + "sales_order_no", // ์ˆ˜์ฃผ๋ฒˆํ˜ธ + "shipment_no", // ์ถœํ•˜๋ฒˆํ˜ธ + "receipt_no", // ์ž…๊ณ ๋ฒˆํ˜ธ + "work_order_no", // ์ž‘์—…์ง€์‹œ๋ฒˆํ˜ธ + // ๊ฑฐ๋ž˜์ฒ˜ ํ•„๋“œ + "supplier_code", // ๊ณต๊ธ‰์ฒ˜ ์ฝ”๋“œ + "supplier_name", // ๊ณต๊ธ‰์ฒ˜ ์ด๋ฆ„ + "customer_code", // ๊ณ ๊ฐ ์ฝ”๋“œ + "customer_name", // ๊ณ ๊ฐ ์ด๋ฆ„ + // ๋‚ ์งœ ํ•„๋“œ + "order_date", // ๋ฐœ์ฃผ์ผ + "sales_date", // ์ˆ˜์ฃผ์ผ + "shipment_date", // ์ถœํ•˜์ผ + "receipt_date", // ์ž…๊ณ ์ผ + "due_date", // ๋‚ฉ๊ธฐ์ผ + // ๋‹ด๋‹น์ž/๋ฉ”๋ชจ ํ•„๋“œ + "manager", // ๋‹ด๋‹น์ž + "memo", // ๋ฉ”๋ชจ + "remark", // ๋น„๊ณ  + ]; + + for (const fieldName of masterDetailFields) { + const value = context.formData[fieldName]; + if (value !== undefined && value !== "" && value !== null && !(fieldName in commonFields)) { + commonFields[fieldName] = value; + } + } + console.log("๐Ÿ“‹ [handleSave] ์ตœ์ข… ๊ณตํ†ต ํ•„๋“œ (๋งˆ์Šคํ„ฐ-๋””ํ…Œ์ผ ํ•„๋“œ ํฌํ•จ):", commonFields); + for (const item of parsedData) { // ๋ฉ”ํƒ€ ํ•„๋“œ ์ œ๊ฑฐ (eslint ๊ฒฝ๊ณ  ๋ฌด์‹œ - ์˜๋„์ ์œผ๋กœ ๋ถ„๋ฆฌ) From 64105bf525644f6c76f8f87b8ba3ea1d7acd0788 Mon Sep 17 00:00:00 2001 From: hjjeong Date: Mon, 5 Jan 2026 18:21:29 +0900 Subject: [PATCH 4/6] =?UTF-8?q?=EB=B0=9C=EC=A3=BC=EA=B4=80=EB=A6=AC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=EC=8B=9C=20=EA=B3=B5=EA=B8=89=EC=B2=98=20?= =?UTF-8?q?=ED=91=9C=EC=8B=9C=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AutocompleteSearchInputComponent.tsx | 145 +++++++++++++++++- 1 file changed, 141 insertions(+), 4 deletions(-) diff --git a/frontend/lib/registry/components/autocomplete-search-input/AutocompleteSearchInputComponent.tsx b/frontend/lib/registry/components/autocomplete-search-input/AutocompleteSearchInputComponent.tsx index 7a115ea3..cbd2744c 100644 --- a/frontend/lib/registry/components/autocomplete-search-input/AutocompleteSearchInputComponent.tsx +++ b/frontend/lib/registry/components/autocomplete-search-input/AutocompleteSearchInputComponent.tsx @@ -44,7 +44,42 @@ export function AutocompleteSearchInputComponent({ const displayField = config?.displayField || propDisplayField || ""; const displayFields = config?.displayFields || (displayField ? [displayField] : []); // ๋‹ค์ค‘ ํ‘œ์‹œ ํ•„๋“œ const displaySeparator = config?.displaySeparator || " โ†’ "; // ๊ตฌ๋ถ„์ž - const valueField = config?.valueField || propValueField || ""; + + // valueField ๊ฒฐ์ •: fieldMappings ๊ธฐ๋ฐ˜์œผ๋กœ ์ถ”๋ก  (config.valueField๊ฐ€ fieldMappings์— ์—†์œผ๋ฉด ๋ฌด์‹œ) + const getValueField = () => { + // fieldMappings๊ฐ€ ์žˆ์œผ๋ฉด ๊ทธ ์•ˆ์—์„œ ์ถ”๋ก  (๊ฐ€์žฅ ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ๋Š” ์†Œ์Šค) + if (config?.fieldMappings && config.fieldMappings.length > 0) { + // config.valueField๊ฐ€ fieldMappings์˜ sourceField์— ์žˆ์œผ๋ฉด ์‚ฌ์šฉ + if (config?.valueField) { + const hasValueFieldInMappings = config.fieldMappings.some( + (m: any) => m.sourceField === config.valueField + ); + if (hasValueFieldInMappings) { + return config.valueField; + } + // fieldMappings์— ์—†์œผ๋ฉด ๋ฌด์‹œํ•˜๊ณ  ์ถ”๋ก  + } + + // _code ๋˜๋Š” _id๋กœ ๋๋‚˜๋Š” ํ•„๋“œ ์šฐ์„  (๋ณดํ†ต PK๋‚˜ ์ฝ”๋“œ ํ•„๋“œ) + const codeMapping = config.fieldMappings.find( + (m: any) => m.sourceField?.endsWith("_code") || m.sourceField?.endsWith("_id") + ); + if (codeMapping) { + return codeMapping.sourceField; + } + + // ์—†์œผ๋ฉด ์ฒซ ๋ฒˆ์งธ ๋งคํ•‘ ์‚ฌ์šฉ + return config.fieldMappings[0].sourceField || ""; + } + + // fieldMappings๊ฐ€ ์—†์œผ๋ฉด ๊ธฐ์กด ๋ฐฉ์‹ + if (config?.valueField) return config.valueField; + if (propValueField) return propValueField; + + return ""; + }; + const valueField = getValueField(); + const searchFields = config?.searchFields || propSearchFields || displayFields; // ๊ฒ€์ƒ‰ ํ•„๋“œ๋„ ๋‹ค์ค‘ ํ‘œ์‹œ ํ•„๋“œ ์‚ฌ์šฉ const placeholder = config?.placeholder || propPlaceholder || "๊ฒ€์ƒ‰..."; @@ -76,11 +111,39 @@ export function AutocompleteSearchInputComponent({ // ์„ ํƒ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ref๋กœ๋„ ์œ ์ง€ (๋ฆฌ๋ Œ๋”๋ง ์‹œ ์ดˆ๊ธฐํ™” ๋ฐฉ์ง€) const selectedDataRef = useRef(null); const inputValueRef = useRef(""); + const initialValueLoadedRef = useRef(null); // ์ดˆ๊ธฐ๊ฐ’ ๋กœ๋“œ ์ถ”์  // formData์—์„œ ํ˜„์žฌ ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ (isInteractive ๋ชจ๋“œ) - const currentValue = isInteractive && formData && component?.columnName - ? formData[component.columnName] - : value; + // ์šฐ์„ ์ˆœ์œ„: 1) component.columnName, 2) fieldMappings์—์„œ valueField์— ๋งคํ•‘๋œ targetField + const getCurrentValue = () => { + if (!isInteractive || !formData) { + return value; + } + + // 1. component.columnName์œผ๋กœ ์ง์ ‘ ๋ฐ”์ธ๋”ฉ๋œ ๊ฒฝ์šฐ + if (component?.columnName && formData[component.columnName] !== undefined) { + return formData[component.columnName]; + } + + // 2. fieldMappings์—์„œ valueField์™€ ๋งคํ•‘๋œ targetField์—์„œ ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ + if (config?.fieldMappings && Array.isArray(config.fieldMappings)) { + const valueFieldMapping = config.fieldMappings.find( + (mapping: any) => mapping.sourceField === valueField + ); + + if (valueFieldMapping) { + const targetField = valueFieldMapping.targetField || valueFieldMapping.targetColumn; + + if (targetField && formData[targetField] !== undefined) { + return formData[targetField]; + } + } + } + + return value; + }; + + const currentValue = getCurrentValue(); // selectedData ๋ณ€๊ฒฝ ์‹œ ref๋„ ์—…๋ฐ์ดํŠธ useEffect(() => { @@ -98,6 +161,79 @@ export function AutocompleteSearchInputComponent({ } }, []); + // ์ดˆ๊ธฐ๊ฐ’์ด ์žˆ์„ ๋•Œ ํ•ด๋‹น ๊ฐ’์˜ ํ‘œ์‹œ ํ…์ŠคํŠธ๋ฅผ ์กฐํšŒํ•˜์—ฌ ์„ค์ • + useEffect(() => { + const loadInitialDisplayValue = async () => { + // ์ด๋ฏธ ๋กœ๋“œ๋œ ๊ฐ’์ด๊ฑฐ๋‚˜, ๊ฐ’์ด ์—†๊ฑฐ๋‚˜, ์ด๋ฏธ ์„ ํƒ๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์œผ๋ฉด ์Šคํ‚ต + if (!currentValue || selectedData || selectedDataRef.current) { + return; + } + + // ์ด๋ฏธ ๊ฐ™์€ ๊ฐ’์„ ๋กœ๋“œํ•œ ์ ์ด ์žˆ์œผ๋ฉด ์Šคํ‚ต + if (initialValueLoadedRef.current === currentValue) { + return; + } + + // ํ…Œ์ด๋ธ”๋ช…๊ณผ ํ•„๋“œ ์ •๋ณด๊ฐ€ ์—†์œผ๋ฉด ์Šคํ‚ต + if (!tableName || !valueField) { + return; + } + + console.log("๐Ÿ”„ AutocompleteSearchInput ์ดˆ๊ธฐ๊ฐ’ ๋กœ๋“œ:", { + currentValue, + tableName, + valueField, + displayFields, + }); + + try { + // API๋ฅผ ํ†ตํ•ด ํ•ด๋‹น ๊ฐ’์˜ ํ‘œ์‹œ ํ…์ŠคํŠธ ์กฐํšŒ + const { apiClient } = await import("@/lib/api/client"); + const filterConditionWithValue = { + ...filterCondition, + [valueField]: currentValue, + }; + + const params = new URLSearchParams({ + searchText: "", + searchFields: searchFields.join(","), + filterCondition: JSON.stringify(filterConditionWithValue), + page: "1", + limit: "10", + }); + + const response = await apiClient.get<{ success: boolean; data: EntitySearchResult[] }>( + `/entity-search/${tableName}?${params.toString()}` + ); + + if (response.data.success && response.data.data && response.data.data.length > 0) { + const matchedItem = response.data.data.find((item: EntitySearchResult) => + String(item[valueField]) === String(currentValue) + ); + + if (matchedItem) { + const displayText = getDisplayValue(matchedItem); + console.log("โœ… ์ดˆ๊ธฐ๊ฐ’ ํ‘œ์‹œ ํ…์ŠคํŠธ ๋กœ๋“œ ์„ฑ๊ณต:", { + currentValue, + displayText, + matchedItem, + }); + + setSelectedData(matchedItem); + setInputValue(displayText); + selectedDataRef.current = matchedItem; + inputValueRef.current = displayText; + initialValueLoadedRef.current = currentValue; + } + } + } catch (error) { + console.error("โŒ ์ดˆ๊ธฐ๊ฐ’ ํ‘œ์‹œ ํ…์ŠคํŠธ ๋กœ๋“œ ์‹คํŒจ:", error); + } + }; + + loadInitialDisplayValue(); + }, [currentValue, tableName, valueField, displayFields, filterCondition, searchFields, selectedData]); + // value๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด ํ‘œ์‹œ๊ฐ’ ์—…๋ฐ์ดํŠธ - ๋‹จ, selectedData๊ฐ€ ์žˆ์œผ๋ฉด ์œ ์ง€ useEffect(() => { // selectedData๊ฐ€ ์žˆ์œผ๋ฉด ํ‘œ์‹œ๊ฐ’ ์œ ์ง€ (์‚ฌ์šฉ์ž๊ฐ€ ๋ฐฉ๊ธˆ ์„ ํƒํ•œ ๊ฒฝ์šฐ) @@ -107,6 +243,7 @@ export function AutocompleteSearchInputComponent({ if (!currentValue) { setInputValue(""); + initialValueLoadedRef.current = null; // ๊ฐ’์ด ์—†์–ด์ง€๋ฉด ์ดˆ๊ธฐํ™” } }, [currentValue, selectedData]); From 4f77c382077ca2a499f76d1badf8417dcacb79ad Mon Sep 17 00:00:00 2001 From: kjs Date: Tue, 6 Jan 2026 10:27:54 +0900 Subject: [PATCH 5/6] =?UTF-8?q?=EA=B6=8C=ED=95=9C=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=9E=84=EC=8B=9C=20=EB=B9=84=ED=99=9C=EC=84=B1=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/routes/cascadingAutoFillRoutes.ts | 1 + .../src/routes/cascadingConditionRoutes.ts | 1 + .../src/routes/cascadingHierarchyRoutes.ts | 1 + .../routes/cascadingMutualExclusionRoutes.ts | 1 + backend-node/src/services/adminService.ts | 18 ++++++++++++++++++ docs/๋…ธ๋“œํ”Œ๋กœ์šฐ_๊ฐœ์„ ์‚ฌํ•ญ.md | 1 + docs/๋ฉ”์ผ๋ฐœ์†ก_๊ธฐ๋Šฅ_์‚ฌ์šฉ_๊ฐ€์ด๋“œ.md | 1 + docs/์ฆ‰์‹œ์ €์žฅ_๋ฒ„ํŠผ_์•ก์…˜_๊ตฌํ˜„_๊ณ„ํš์„œ.md | 1 + .../admin/screenMng/screenMngList/page.tsx | 1 + frontend/contexts/ActiveTabContext.tsx | 1 + frontend/hooks/useAutoFill.ts | 1 + ...ด_์ž„๋ฒ ๋”ฉ_๋ฐ_๋ฐ์ดํ„ฐ_์ „๋‹ฌ_์‹œ์Šคํ…œ_๊ตฌํ˜„_๊ณ„ํš์„œ.md | 1 + ํ™”๋ฉด_์ž„๋ฒ ๋”ฉ_์‹œ์Šคํ…œ_Phase1-4_๊ตฌํ˜„_์™„๋ฃŒ.md | 1 + ํ™”๋ฉด_์ž„๋ฒ ๋”ฉ_์‹œ์Šคํ…œ_์ถฉ๋Œ_๋ถ„์„_๋ณด๊ณ ์„œ.md | 1 + 14 files changed, 31 insertions(+) diff --git a/backend-node/src/routes/cascadingAutoFillRoutes.ts b/backend-node/src/routes/cascadingAutoFillRoutes.ts index a5107448..c1d69e9f 100644 --- a/backend-node/src/routes/cascadingAutoFillRoutes.ts +++ b/backend-node/src/routes/cascadingAutoFillRoutes.ts @@ -55,3 +55,4 @@ export default router; + diff --git a/backend-node/src/routes/cascadingConditionRoutes.ts b/backend-node/src/routes/cascadingConditionRoutes.ts index 22cd2d2b..bbc9384d 100644 --- a/backend-node/src/routes/cascadingConditionRoutes.ts +++ b/backend-node/src/routes/cascadingConditionRoutes.ts @@ -51,3 +51,4 @@ export default router; + diff --git a/backend-node/src/routes/cascadingHierarchyRoutes.ts b/backend-node/src/routes/cascadingHierarchyRoutes.ts index 79a1c6e8..35ced071 100644 --- a/backend-node/src/routes/cascadingHierarchyRoutes.ts +++ b/backend-node/src/routes/cascadingHierarchyRoutes.ts @@ -67,3 +67,4 @@ export default router; + diff --git a/backend-node/src/routes/cascadingMutualExclusionRoutes.ts b/backend-node/src/routes/cascadingMutualExclusionRoutes.ts index 352a05b5..29ac8ee4 100644 --- a/backend-node/src/routes/cascadingMutualExclusionRoutes.ts +++ b/backend-node/src/routes/cascadingMutualExclusionRoutes.ts @@ -55,3 +55,4 @@ export default router; + diff --git a/backend-node/src/services/adminService.ts b/backend-node/src/services/adminService.ts index 1b9280db..95d8befa 100644 --- a/backend-node/src/services/adminService.ts +++ b/backend-node/src/services/adminService.ts @@ -65,6 +65,13 @@ export class AdminService { } ); + // [์ž„์‹œ ๋น„ํ™œ์„ฑํ™”] ๋ฉ”๋‰ด ๊ถŒํ•œ ๊ทธ๋ฃน ์ฒดํฌ - ๋ชจ๋“  ์‚ฌ์šฉ์ž์—๊ฒŒ ์ „์ฒด ๋ฉ”๋‰ด ํ‘œ์‹œ + // TODO: ๊ถŒํ•œ ์ฒดํฌ ๋‹ค์‹œ ํ™œ์„ฑํ™” ํ•„์š” + logger.info( + `โš ๏ธ [์ž„์‹œ ๋น„ํ™œ์„ฑํ™”] ๊ถŒํ•œ ๊ทธ๋ฃน ์ฒดํฌ ์Šคํ‚ต - ์‚ฌ์šฉ์ž ${userId}(${userType})์—๊ฒŒ ์ „์ฒด ๋ฉ”๋‰ด ํ‘œ์‹œ` + ); + + /* [์›๋ณธ ์ฝ”๋“œ - ๊ถŒํ•œ ๊ทธ๋ฃน ์ฒดํฌ] if (userType === "COMPANY_ADMIN") { // ํšŒ์‚ฌ ๊ด€๋ฆฌ์ž: ๊ถŒํ•œ ๊ทธ๋ฃน ๊ธฐ๋ฐ˜ ํ•„ํ„ฐ๋ง ์ ์šฉ if (userRoleGroups.length > 0) { @@ -141,6 +148,7 @@ export class AdminService { return []; } } + */ } else if ( menuType !== undefined && userType === "SUPER_ADMIN" && @@ -412,6 +420,15 @@ export class AdminService { let queryParams: any[] = [userLang]; let paramIndex = 2; + // [์ž„์‹œ ๋น„ํ™œ์„ฑํ™”] ๋ฉ”๋‰ด ๊ถŒํ•œ ๊ทธ๋ฃน ์ฒดํฌ - ๋ชจ๋“  ์‚ฌ์šฉ์ž์—๊ฒŒ ์ „์ฒด ๋ฉ”๋‰ด ํ‘œ์‹œ + // TODO: ๊ถŒํ•œ ์ฒดํฌ ๋‹ค์‹œ ํ™œ์„ฑํ™” ํ•„์š” + logger.info( + `โš ๏ธ [์ž„์‹œ ๋น„ํ™œ์„ฑํ™”] getUserMenuList ๊ถŒํ•œ ๊ทธ๋ฃน ์ฒดํฌ ์Šคํ‚ต - ์‚ฌ์šฉ์ž ${userId}(${userType})์—๊ฒŒ ์ „์ฒด ๋ฉ”๋‰ด ํ‘œ์‹œ` + ); + authFilter = ""; + unionFilter = ""; + + /* [์›๋ณธ ์ฝ”๋“œ - getUserMenuList ๊ถŒํ•œ ๊ทธ๋ฃน ์ฒดํฌ] if (userType === "SUPER_ADMIN") { // SUPER_ADMIN: ๊ถŒํ•œ ๊ทธ๋ฃน ์ฒดํฌ ์—†์ด ํ•ด๋‹น ํšŒ์‚ฌ์˜ ๋ชจ๋“  ๋ฉ”๋‰ด ํ‘œ์‹œ logger.info(`โœ… ์ขŒ์ธก ์‚ฌ์ด๋“œ๋ฐ” (SUPER_ADMIN): ํšŒ์‚ฌ ${userCompanyCode}์˜ ๋ชจ๋“  ๋ฉ”๋‰ด ํ‘œ์‹œ`); @@ -471,6 +488,7 @@ export class AdminService { return []; } } + */ // 2. ํšŒ์‚ฌ๋ณ„ ํ•„ํ„ฐ๋ง ์กฐ๊ฑด ์ƒ์„ฑ let companyFilter = ""; diff --git a/docs/๋…ธ๋“œํ”Œ๋กœ์šฐ_๊ฐœ์„ ์‚ฌํ•ญ.md b/docs/๋…ธ๋“œํ”Œ๋กœ์šฐ_๊ฐœ์„ ์‚ฌํ•ญ.md index c9349b94..32757807 100644 --- a/docs/๋…ธ๋“œํ”Œ๋กœ์šฐ_๊ฐœ์„ ์‚ฌํ•ญ.md +++ b/docs/๋…ธ๋“œํ”Œ๋กœ์šฐ_๊ฐœ์„ ์‚ฌํ•ญ.md @@ -587,3 +587,4 @@ const result = await executeNodeFlow(flowId, { + diff --git a/docs/๋ฉ”์ผ๋ฐœ์†ก_๊ธฐ๋Šฅ_์‚ฌ์šฉ_๊ฐ€์ด๋“œ.md b/docs/๋ฉ”์ผ๋ฐœ์†ก_๊ธฐ๋Šฅ_์‚ฌ์šฉ_๊ฐ€์ด๋“œ.md index 42900211..8bfe484e 100644 --- a/docs/๋ฉ”์ผ๋ฐœ์†ก_๊ธฐ๋Šฅ_์‚ฌ์šฉ_๊ฐ€์ด๋“œ.md +++ b/docs/๋ฉ”์ผ๋ฐœ์†ก_๊ธฐ๋Šฅ_์‚ฌ์šฉ_๊ฐ€์ด๋“œ.md @@ -360,3 +360,4 @@ + diff --git a/docs/์ฆ‰์‹œ์ €์žฅ_๋ฒ„ํŠผ_์•ก์…˜_๊ตฌํ˜„_๊ณ„ํš์„œ.md b/docs/์ฆ‰์‹œ์ €์žฅ_๋ฒ„ํŠผ_์•ก์…˜_๊ตฌํ˜„_๊ณ„ํš์„œ.md index c392eece..8d8fb497 100644 --- a/docs/์ฆ‰์‹œ์ €์žฅ_๋ฒ„ํŠผ_์•ก์…˜_๊ตฌํ˜„_๊ณ„ํš์„œ.md +++ b/docs/์ฆ‰์‹œ์ €์žฅ_๋ฒ„ํŠผ_์•ก์…˜_๊ตฌํ˜„_๊ณ„ํš์„œ.md @@ -346,3 +346,4 @@ const getComponentValue = (componentId: string) => { + diff --git a/frontend/app/(main)/admin/screenMng/screenMngList/page.tsx b/frontend/app/(main)/admin/screenMng/screenMngList/page.tsx index 3145d9d3..0327e122 100644 --- a/frontend/app/(main)/admin/screenMng/screenMngList/page.tsx +++ b/frontend/app/(main)/admin/screenMng/screenMngList/page.tsx @@ -127,3 +127,4 @@ export default function ScreenManagementPage() { ); } + diff --git a/frontend/contexts/ActiveTabContext.tsx b/frontend/contexts/ActiveTabContext.tsx index 228dc990..35081225 100644 --- a/frontend/contexts/ActiveTabContext.tsx +++ b/frontend/contexts/ActiveTabContext.tsx @@ -140,3 +140,4 @@ export const useActiveTabOptional = () => { + diff --git a/frontend/hooks/useAutoFill.ts b/frontend/hooks/useAutoFill.ts index caa1e826..7d78322b 100644 --- a/frontend/hooks/useAutoFill.ts +++ b/frontend/hooks/useAutoFill.ts @@ -197,3 +197,4 @@ export function applyAutoFillToFormData( + diff --git a/ํ™”๋ฉด_์ž„๋ฒ ๋”ฉ_๋ฐ_๋ฐ์ดํ„ฐ_์ „๋‹ฌ_์‹œ์Šคํ…œ_๊ตฌํ˜„_๊ณ„ํš์„œ.md b/ํ™”๋ฉด_์ž„๋ฒ ๋”ฉ_๋ฐ_๋ฐ์ดํ„ฐ_์ „๋‹ฌ_์‹œ์Šคํ…œ_๊ตฌํ˜„_๊ณ„ํš์„œ.md index f61ab2fb..1108475c 100644 --- a/ํ™”๋ฉด_์ž„๋ฒ ๋”ฉ_๋ฐ_๋ฐ์ดํ„ฐ_์ „๋‹ฌ_์‹œ์Šคํ…œ_๊ตฌํ˜„_๊ณ„ํš์„œ.md +++ b/ํ™”๋ฉด_์ž„๋ฒ ๋”ฉ_๋ฐ_๋ฐ์ดํ„ฐ_์ „๋‹ฌ_์‹œ์Šคํ…œ_๊ตฌํ˜„_๊ณ„ํš์„œ.md @@ -1689,3 +1689,4 @@ const ์ถœ๊ณ ๋“ฑ๋ก_์„ค์ •: ScreenSplitPanel = { + diff --git a/ํ™”๋ฉด_์ž„๋ฒ ๋”ฉ_์‹œ์Šคํ…œ_Phase1-4_๊ตฌํ˜„_์™„๋ฃŒ.md b/ํ™”๋ฉด_์ž„๋ฒ ๋”ฉ_์‹œ์Šคํ…œ_Phase1-4_๊ตฌํ˜„_์™„๋ฃŒ.md index 0596216f..c20a94bc 100644 --- a/ํ™”๋ฉด_์ž„๋ฒ ๋”ฉ_์‹œ์Šคํ…œ_Phase1-4_๊ตฌํ˜„_์™„๋ฃŒ.md +++ b/ํ™”๋ฉด_์ž„๋ฒ ๋”ฉ_์‹œ์Šคํ…œ_Phase1-4_๊ตฌํ˜„_์™„๋ฃŒ.md @@ -536,3 +536,4 @@ const { data: config } = await getScreenSplitPanel(screenId); + diff --git a/ํ™”๋ฉด_์ž„๋ฒ ๋”ฉ_์‹œ์Šคํ…œ_์ถฉ๋Œ_๋ถ„์„_๋ณด๊ณ ์„œ.md b/ํ™”๋ฉด_์ž„๋ฒ ๋”ฉ_์‹œ์Šคํ…œ_์ถฉ๋Œ_๋ถ„์„_๋ณด๊ณ ์„œ.md index 4f0bfabb..77ad05b2 100644 --- a/ํ™”๋ฉด_์ž„๋ฒ ๋”ฉ_์‹œ์Šคํ…œ_์ถฉ๋Œ_๋ถ„์„_๋ณด๊ณ ์„œ.md +++ b/ํ™”๋ฉด_์ž„๋ฒ ๋”ฉ_์‹œ์Šคํ…œ_์ถฉ๋Œ_๋ถ„์„_๋ณด๊ณ ์„œ.md @@ -523,3 +523,4 @@ function ScreenViewPage() { + From 6bfc1a97a32e481f2242b48083fe0e050e6e727c Mon Sep 17 00:00:00 2001 From: kjs Date: Tue, 6 Jan 2026 11:43:26 +0900 Subject: [PATCH 6/6] =?UTF-8?q?=EB=B2=94=EC=9A=A9=20=ED=8F=BC=20=EB=AA=A8?= =?UTF-8?q?=EB=8B=AC=20=EC=82=AC=EC=A0=84=ED=95=84=ED=84=B0=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/controllers/entitySearchController.ts | 86 ++- .../entity-search-input/useEntitySearch.ts | 6 + .../ItemSelectionModal.tsx | 76 +- .../components/modal-repeater-table/types.ts | 3 + .../TableSectionRenderer.tsx | 43 +- .../UniversalFormModalConfigPanel.tsx | 686 +++++++++--------- .../modals/TableSectionSettingsModal.tsx | 186 +++-- 7 files changed, 689 insertions(+), 397 deletions(-) diff --git a/backend-node/src/controllers/entitySearchController.ts b/backend-node/src/controllers/entitySearchController.ts index 4d911c57..5f198c3f 100644 --- a/backend-node/src/controllers/entitySearchController.ts +++ b/backend-node/src/controllers/entitySearchController.ts @@ -107,14 +107,88 @@ export async function searchEntity(req: AuthenticatedRequest, res: Response) { } // ์ถ”๊ฐ€ ํ•„ํ„ฐ ์กฐ๊ฑด (์กด์žฌํ•˜๋Š” ์ปฌ๋Ÿผ๋งŒ) + // ์ง€์› ์—ฐ์‚ฐ์ž: =, !=, >, <, >=, <=, in, notIn, like + // ํŠน์ˆ˜ ํ‚ค ํ˜•์‹: column__operator (์˜ˆ: division__in, name__like) const additionalFilter = JSON.parse(filterCondition as string); for (const [key, value] of Object.entries(additionalFilter)) { - if (existingColumns.has(key)) { - whereConditions.push(`${key} = $${paramIndex}`); - params.push(value); - paramIndex++; - } else { - logger.warn("์—”ํ‹ฐํ‹ฐ ๊ฒ€์ƒ‰: ํ•„ํ„ฐ ์กฐ๊ฑด์— ์กด์žฌํ•˜์ง€ ์•Š๋Š” ์ปฌ๋Ÿผ ์ œ์™ธ", { tableName, key }); + // ํŠน์ˆ˜ ํ‚ค ํ˜•์‹ ํŒŒ์‹ฑ: column__operator + let columnName = key; + let operator = "="; + + if (key.includes("__")) { + const parts = key.split("__"); + columnName = parts[0]; + operator = parts[1] || "="; + } + + if (!existingColumns.has(columnName)) { + logger.warn("์—”ํ‹ฐํ‹ฐ ๊ฒ€์ƒ‰: ํ•„ํ„ฐ ์กฐ๊ฑด์— ์กด์žฌํ•˜์ง€ ์•Š๋Š” ์ปฌ๋Ÿผ ์ œ์™ธ", { tableName, key, columnName }); + continue; + } + + // ์—ฐ์‚ฐ์ž๋ณ„ WHERE ์กฐ๊ฑด ์ƒ์„ฑ + switch (operator) { + case "=": + whereConditions.push(`"${columnName}" = $${paramIndex}`); + params.push(value); + paramIndex++; + break; + case "!=": + whereConditions.push(`"${columnName}" != $${paramIndex}`); + params.push(value); + paramIndex++; + break; + case ">": + whereConditions.push(`"${columnName}" > $${paramIndex}`); + params.push(value); + paramIndex++; + break; + case "<": + whereConditions.push(`"${columnName}" < $${paramIndex}`); + params.push(value); + paramIndex++; + break; + case ">=": + whereConditions.push(`"${columnName}" >= $${paramIndex}`); + params.push(value); + paramIndex++; + break; + case "<=": + whereConditions.push(`"${columnName}" <= $${paramIndex}`); + params.push(value); + paramIndex++; + break; + case "in": + // IN ์—ฐ์‚ฐ์ž: ๊ฐ’์ด ๋ฐฐ์—ด์ด๊ฑฐ๋‚˜ ์‰ผํ‘œ๋กœ ๊ตฌ๋ถ„๋œ ๋ฌธ์ž์—ด + const inValues = Array.isArray(value) ? value : String(value).split(",").map(v => v.trim()); + if (inValues.length > 0) { + const placeholders = inValues.map((_, i) => `$${paramIndex + i}`).join(", "); + whereConditions.push(`"${columnName}" IN (${placeholders})`); + params.push(...inValues); + paramIndex += inValues.length; + } + break; + case "notIn": + // NOT IN ์—ฐ์‚ฐ์ž + const notInValues = Array.isArray(value) ? value : String(value).split(",").map(v => v.trim()); + if (notInValues.length > 0) { + const placeholders = notInValues.map((_, i) => `$${paramIndex + i}`).join(", "); + whereConditions.push(`"${columnName}" NOT IN (${placeholders})`); + params.push(...notInValues); + paramIndex += notInValues.length; + } + break; + case "like": + whereConditions.push(`"${columnName}"::text ILIKE $${paramIndex}`); + params.push(`%${value}%`); + paramIndex++; + break; + default: + // ์•Œ ์ˆ˜ ์—†๋Š” ์—ฐ์‚ฐ์ž๋Š” ๋“ฑํ˜ธ๋กœ ์ฒ˜๋ฆฌ + whereConditions.push(`"${columnName}" = $${paramIndex}`); + params.push(value); + paramIndex++; + break; } } diff --git a/frontend/lib/registry/components/entity-search-input/useEntitySearch.ts b/frontend/lib/registry/components/entity-search-input/useEntitySearch.ts index 1fac26d6..2ae71595 100644 --- a/frontend/lib/registry/components/entity-search-input/useEntitySearch.ts +++ b/frontend/lib/registry/components/entity-search-input/useEntitySearch.ts @@ -53,6 +53,12 @@ export function useEntitySearch({ limit: pagination.limit.toString(), }); + console.log("[useEntitySearch] ๊ฒ€์ƒ‰ ์‹คํ–‰:", { + tableName, + filterCondition: filterConditionRef.current, + searchText: text, + }); + const response = await apiClient.get( `/entity-search/${tableName}?${params.toString()}` ); diff --git a/frontend/lib/registry/components/modal-repeater-table/ItemSelectionModal.tsx b/frontend/lib/registry/components/modal-repeater-table/ItemSelectionModal.tsx index ad73c317..1eca9fab 100644 --- a/frontend/lib/registry/components/modal-repeater-table/ItemSelectionModal.tsx +++ b/frontend/lib/registry/components/modal-repeater-table/ItemSelectionModal.tsx @@ -32,6 +32,7 @@ export function ItemSelectionModal({ onSelect, columnLabels = {}, modalFilters = [], + categoryColumns = [], }: ItemSelectionModalProps) { const [localSearchText, setLocalSearchText] = useState(""); const [selectedItems, setSelectedItems] = useState([]); @@ -41,6 +42,9 @@ export function ItemSelectionModal({ // ์นดํ…Œ๊ณ ๋ฆฌ ์˜ต์…˜ ์ƒํƒœ (categoryRef๋ณ„๋กœ ๋กœ๋“œ๋œ ์˜ต์…˜) const [categoryOptions, setCategoryOptions] = useState>({}); + + // ์นดํ…Œ๊ณ ๋ฆฌ ์ฝ”๋“œ โ†’ ๋ผ๋ฒจ ๋งคํ•‘ (ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ ํ‘œ์‹œ์šฉ) + const [categoryLabelMap, setCategoryLabelMap] = useState>({}); // ๋ชจ๋‹ฌ ํ•„ํ„ฐ ๊ฐ’๊ณผ ๊ธฐ๋ณธ filterCondition์„ ํ•ฉ์นœ ์ตœ์ข… ํ•„ํ„ฐ ์กฐ๊ฑด const combinedFilterCondition = useMemo(() => { @@ -152,6 +156,54 @@ export function ItemSelectionModal({ } }, [modalFilterValues]); + // ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด ์นดํ…Œ๊ณ ๋ฆฌ ๊ฐ’๋“ค์˜ ๋ผ๋ฒจ ์กฐํšŒ + useEffect(() => { + const loadCategoryLabels = async () => { + if (!open || categoryColumns.length === 0 || results.length === 0) { + return; + } + + // ํ˜„์žฌ ๊ฒฐ๊ณผ์—์„œ ์นดํ…Œ๊ณ ๋ฆฌ ์ปฌ๋Ÿผ์˜ ๋ชจ๋“  ๊ณ ์œ ํ•œ ๊ฐ’ ์ˆ˜์ง‘ + // ์‰ผํ‘œ๋กœ ๊ตฌ๋ถ„๋œ ๋‹ค์ค‘ ๊ฐ’๋„ ๊ฐœ๋ณ„์ ์œผ๋กœ ์ˆ˜์ง‘ + const allCodes = new Set(); + for (const row of results) { + for (const col of categoryColumns) { + const val = row[col]; + if (val && typeof val === "string") { + // ์‰ผํ‘œ๋กœ ๊ตฌ๋ถ„๋œ ๋‹ค์ค‘ ๊ฐ’ ์ฒ˜๋ฆฌ + const codes = val.split(",").map((c) => c.trim()).filter(Boolean); + for (const code of codes) { + if (!categoryLabelMap[code]) { + allCodes.add(code); + } + } + } + } + } + + if (allCodes.size === 0) { + return; + } + + try { + const response = await apiClient.post("/table-categories/labels-by-codes", { + valueCodes: Array.from(allCodes), + }); + + if (response.data?.success && response.data.data) { + setCategoryLabelMap((prev) => ({ + ...prev, + ...response.data.data, + })); + } + } catch (error) { + console.error("์นดํ…Œ๊ณ ๋ฆฌ ๋ผ๋ฒจ ์กฐํšŒ ์‹คํŒจ:", error); + } + }; + + loadCategoryLabels(); + }, [open, results, categoryColumns]); + // ๋ชจ๋‹ฌ ํ•„ํ„ฐ ๊ฐ’ ๋ณ€๊ฒฝ ํ•ธ๋“ค๋Ÿฌ const handleModalFilterChange = (column: string, value: any) => { setModalFilterValues((prev) => ({ @@ -450,11 +502,25 @@ export function ItemSelectionModal({ )} - {validColumns.map((col) => ( - - {item[col] || "-"} - - ))} + {validColumns.map((col) => { + const rawValue = item[col]; + // ์นดํ…Œ๊ณ ๋ฆฌ ์ปฌ๋Ÿผ์ด๋ฉด ๋ผ๋ฒจ๋กœ ๋ณ€ํ™˜ + const isCategory = categoryColumns.includes(col); + let displayValue = rawValue; + + if (isCategory && rawValue && typeof rawValue === "string") { + // ์‰ผํ‘œ๋กœ ๊ตฌ๋ถ„๋œ ๋‹ค์ค‘ ๊ฐ’ ์ฒ˜๋ฆฌ + const codes = rawValue.split(",").map((c) => c.trim()).filter(Boolean); + const labels = codes.map((code) => categoryLabelMap[code] || code); + displayValue = labels.join(", "); + } + + return ( + + {displayValue || "-"} + + ); + })} ); }) diff --git a/frontend/lib/registry/components/modal-repeater-table/types.ts b/frontend/lib/registry/components/modal-repeater-table/types.ts index ad373200..ba23c60e 100644 --- a/frontend/lib/registry/components/modal-repeater-table/types.ts +++ b/frontend/lib/registry/components/modal-repeater-table/types.ts @@ -202,4 +202,7 @@ export interface ItemSelectionModalProps { // ๋ชจ๋‹ฌ ๋‚ด๋ถ€ ํ•„ํ„ฐ (์‚ฌ์šฉ์ž ์„ ํƒ ๊ฐ€๋Šฅ) modalFilters?: ModalFilterConfig[]; + + // ์นดํ…Œ๊ณ ๋ฆฌ ํƒ€์ž… ์ปฌ๋Ÿผ ๋ชฉ๋ก (ํ•ด๋‹น ์ปฌ๋Ÿผ์€ ์ฝ”๋“œ โ†’ ๋ผ๋ฒจ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ํ‘œ์‹œ) + categoryColumns?: string[]; } diff --git a/frontend/lib/registry/components/universal-form-modal/TableSectionRenderer.tsx b/frontend/lib/registry/components/universal-form-modal/TableSectionRenderer.tsx index 405e2abf..fb1b2ea3 100644 --- a/frontend/lib/registry/components/universal-form-modal/TableSectionRenderer.tsx +++ b/frontend/lib/registry/components/universal-form-modal/TableSectionRenderer.tsx @@ -381,6 +381,34 @@ export function TableSectionRenderer({ const [dynamicOptions, setDynamicOptions] = useState<{ id: string; value: string; label: string }[]>([]); const [dynamicOptionsLoading, setDynamicOptionsLoading] = useState(false); const dynamicOptionsLoadedRef = React.useRef(false); + + // ์†Œ์Šค ํ…Œ์ด๋ธ”์˜ ์นดํ…Œ๊ณ ๋ฆฌ ํƒ€์ž… ์ปฌ๋Ÿผ ๋ชฉ๋ก + const [sourceCategoryColumns, setSourceCategoryColumns] = useState([]); + + // ์†Œ์Šค ํ…Œ์ด๋ธ”์˜ ์นดํ…Œ๊ณ ๋ฆฌ ํƒ€์ž… ์ปฌ๋Ÿผ ๋ชฉ๋ก ๋กœ๋“œ + useEffect(() => { + const loadCategoryColumns = async () => { + if (!tableConfig.source.tableName) return; + + try { + const response = await apiClient.get( + `/table-categories/${tableConfig.source.tableName}/columns` + ); + + if (response.data?.success && Array.isArray(response.data.data)) { + const categoryColNames = response.data.data.map( + (col: { columnName?: string; column_name?: string }) => + col.columnName || col.column_name || "" + ).filter(Boolean); + setSourceCategoryColumns(categoryColNames); + } + } catch (error) { + console.error("์นดํ…Œ๊ณ ๋ฆฌ ์ปฌ๋Ÿผ ๋ชฉ๋ก ์กฐํšŒ ์‹คํŒจ:", error); + } + }; + + loadCategoryColumns(); + }, [tableConfig.source.tableName]); // ์กฐ๊ฑด๋ถ€ ํ…Œ์ด๋ธ”: ๋™์  ์˜ต์…˜ ๋กœ๋“œ (optionSource ์„ค์ •์ด ์žˆ๋Š” ๊ฒฝ์šฐ) useEffect(() => { @@ -1281,16 +1309,25 @@ export function TableSectionRenderer({ const addRowButtonText = uiConfig?.addRowButtonText || "์ง์ ‘ ์ž…๋ ฅ"; // ๊ธฐ๋ณธ ํ•„ํ„ฐ ์กฐ๊ฑด ์ƒ์„ฑ (์‚ฌ์ „ ํ•„ํ„ฐ๋งŒ - ๋ชจ๋‹ฌ ํ•„ํ„ฐ๋Š” ItemSelectionModal์—์„œ ์ฒ˜๋ฆฌ) + // ์—ฐ์‚ฐ์ž๋ณ„๋กœ ํŠน์ˆ˜ ํ‚ค ํ˜•์‹ ์‚ฌ์šฉ: column__operator (์˜ˆ: division__in) const baseFilterCondition: Record = useMemo(() => { const condition: Record = {}; if (filters?.preFilters) { for (const filter of filters.preFilters) { - // ๊ฐ„๋‹จํ•œ "=" ์—ฐ์‚ฐ์ž๋งŒ ์ฒ˜๋ฆฌ (ํ™•์žฅ ๊ฐ€๋Šฅ) - if (filter.operator === "=") { + if (!filter.column || filter.value === undefined || filter.value === "") continue; + + const operator = filter.operator || "="; + + if (operator === "=") { + // ๊ธฐ๋ณธ ๋“ฑํ˜ธ ์—ฐ์‚ฐ์ž๋Š” ๊ทธ๋Œ€๋กœ ์ „๋‹ฌ condition[filter.column] = filter.value; + } else { + // ๋‹ค๋ฅธ ์—ฐ์‚ฐ์ž๋Š” ํŠน์ˆ˜ ํ‚ค ํ˜•์‹ ์‚ฌ์šฉ: column__operator + condition[`${filter.column}__${operator}`] = filter.value; } } } + console.log("[TableSectionRenderer] baseFilterCondition:", condition, "preFilters:", filters?.preFilters); return condition; }, [filters?.preFilters]); @@ -1892,6 +1929,7 @@ export function TableSectionRenderer({ onSelect={handleConditionalAddItems} columnLabels={columnLabels} modalFilters={modalFiltersForModal} + categoryColumns={sourceCategoryColumns} /> ); @@ -2000,6 +2038,7 @@ export function TableSectionRenderer({ onSelect={handleAddItems} columnLabels={columnLabels} modalFilters={modalFiltersForModal} + categoryColumns={sourceCategoryColumns} /> ); diff --git a/frontend/lib/registry/components/universal-form-modal/UniversalFormModalConfigPanel.tsx b/frontend/lib/registry/components/universal-form-modal/UniversalFormModalConfigPanel.tsx index 27af68f1..7186ca7e 100644 --- a/frontend/lib/registry/components/universal-form-modal/UniversalFormModalConfigPanel.tsx +++ b/frontend/lib/registry/components/universal-form-modal/UniversalFormModalConfigPanel.tsx @@ -9,17 +9,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@ import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"; import { Badge } from "@/components/ui/badge"; import { Separator } from "@/components/ui/separator"; -import { - Plus, - Trash2, - GripVertical, - ChevronUp, - ChevronDown, - Settings, - Database, - Layout, - Table, -} from "lucide-react"; +import { Plus, Trash2, GripVertical, ChevronUp, ChevronDown, Settings, Database, Layout, Table } from "lucide-react"; import { cn } from "@/lib/utils"; import { apiClient } from "@/lib/api/client"; import { getNumberingRules } from "@/lib/api/numberingRule"; @@ -31,11 +21,7 @@ import { MODAL_SIZE_OPTIONS, SECTION_TYPE_OPTIONS, } from "./types"; -import { - defaultSectionConfig, - defaultTableSectionConfig, - generateSectionId, -} from "./config"; +import { defaultSectionConfig, defaultTableSectionConfig, generateSectionId } from "./config"; // ๋ชจ๋‹ฌ import import { FieldDetailSettingsModal } from "./modals/FieldDetailSettingsModal"; @@ -45,22 +31,26 @@ import { TableSectionSettingsModal } from "./modals/TableSectionSettingsModal"; // ๋„์›€๋ง ํ…์ŠคํŠธ ์ปดํฌ๋„ŒํŠธ const HelpText = ({ children }: { children: React.ReactNode }) => ( -

{children}

+

{children}

); // ๋ถ€๋ชจ ํ™”๋ฉด์—์„œ ์ „๋‹ฌ ๊ฐ€๋Šฅํ•œ ํ•„๋“œ ํƒ€์ž… interface AvailableParentField { - name: string; // ํ•„๋“œ๋ช… (columnName) - label: string; // ํ‘œ์‹œ ๋ผ๋ฒจ + name: string; // ํ•„๋“œ๋ช… (columnName) + label: string; // ํ‘œ์‹œ ๋ผ๋ฒจ sourceComponent?: string; // ์ถœ์ฒ˜ ์ปดํฌ๋„ŒํŠธ (์˜ˆ: "TableList", "SplitPanelLayout2") - sourceTable?: string; // ์ถœ์ฒ˜ ํ…Œ์ด๋ธ”๋ช… + sourceTable?: string; // ์ถœ์ฒ˜ ํ…Œ์ด๋ธ”๋ช… } -export function UniversalFormModalConfigPanel({ config, onChange, allComponents = [] }: UniversalFormModalConfigPanelProps) { +export function UniversalFormModalConfigPanel({ + config, + onChange, + allComponents = [], +}: UniversalFormModalConfigPanelProps) { // ํ…Œ์ด๋ธ” ๋ชฉ๋ก const [tables, setTables] = useState<{ name: string; label: string }[]>([]); const [tableColumns, setTableColumns] = useState<{ - [tableName: string]: { name: string; type: string; label: string }[]; + [tableName: string]: { name: string; type: string; label: string; inputType?: string }[]; }>({}); // ๋ถ€๋ชจ ํ™”๋ฉด์—์„œ ์ „๋‹ฌ ๊ฐ€๋Šฅํ•œ ํ•„๋“œ ๋ชฉ๋ก @@ -140,7 +130,7 @@ export function UniversalFormModalConfigPanel({ config, onChange, allComponents } }); } - + // ์ขŒ์ธก ํŒจ๋„ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ๋„ ์ถ”์ถœ const leftTableName = compConfig.leftPanel?.tableName; if (leftTableName) { @@ -152,7 +142,7 @@ export function UniversalFormModalConfigPanel({ config, onChange, allComponents const colName = col.columnName || col.column_name; const colLabel = col.displayName || col.columnComment || col.column_comment || colName; // ์ค‘๋ณต ๋ฐฉ์ง€ - if (!fields.some(f => f.name === colName && f.sourceTable === leftTableName)) { + if (!fields.some((f) => f.name === colName && f.sourceTable === leftTableName)) { fields.push({ name: colName, label: colLabel, @@ -179,7 +169,7 @@ export function UniversalFormModalConfigPanel({ config, onChange, allComponents columns.forEach((col: any) => { const colName = col.columnName || col.column_name; const colLabel = col.displayName || col.columnComment || col.column_comment || colName; - if (!fields.some(f => f.name === colName && f.sourceTable === tableName)) { + if (!fields.some((f) => f.name === colName && f.sourceTable === tableName)) { fields.push({ name: colName, label: colLabel, @@ -198,11 +188,11 @@ export function UniversalFormModalConfigPanel({ config, onChange, allComponents // 4. ๋ฒ„ํŠผ ์ปดํฌ๋„ŒํŠธ - openModalWithData์˜ fieldMappings/dataMapping์—์„œ ์†Œ์Šค ์ปฌ๋Ÿผ ์ถ”์ถœ if (compType === "button-primary" || compType === "button" || compType === "button-secondary") { const action = compConfig.action || {}; - + // fieldMappings์—์„œ ์†Œ์Šค ์ปฌ๋Ÿผ ์ถ”์ถœ const fieldMappings = action.fieldMappings || []; fieldMappings.forEach((mapping: any) => { - if (mapping.sourceColumn && !fields.some(f => f.name === mapping.sourceColumn)) { + if (mapping.sourceColumn && !fields.some((f) => f.name === mapping.sourceColumn)) { fields.push({ name: mapping.sourceColumn, label: mapping.sourceColumn, @@ -211,11 +201,11 @@ export function UniversalFormModalConfigPanel({ config, onChange, allComponents }); } }); - + // dataMapping์—์„œ ์†Œ์Šค ์ปฌ๋Ÿผ ์ถ”์ถœ const dataMapping = action.dataMapping || []; dataMapping.forEach((mapping: any) => { - if (mapping.sourceColumn && !fields.some(f => f.name === mapping.sourceColumn)) { + if (mapping.sourceColumn && !fields.some((f) => f.name === mapping.sourceColumn)) { fields.push({ name: mapping.sourceColumn, label: mapping.sourceColumn, @@ -237,7 +227,7 @@ export function UniversalFormModalConfigPanel({ config, onChange, allComponents columns.forEach((col: any) => { const colName = col.columnName || col.column_name; const colLabel = col.displayName || col.columnComment || col.column_comment || colName; - if (!fields.some(f => f.name === colName)) { + if (!fields.some((f) => f.name === colName)) { fields.push({ name: colName, label: colLabel, @@ -253,8 +243,8 @@ export function UniversalFormModalConfigPanel({ config, onChange, allComponents } // ์ค‘๋ณต ์ œ๊ฑฐ (๊ฐ™์€ name์ด๋ฉด ์ฒซ ๋ฒˆ์งธ๋งŒ ์œ ์ง€) - const uniqueFields = fields.filter((field, index, self) => - index === self.findIndex(f => f.name === field.name) + const uniqueFields = fields.filter( + (field, index, self) => index === self.findIndex((f) => f.name === field.name), ); setAvailableParentFields(uniqueFields); @@ -276,11 +266,19 @@ export function UniversalFormModalConfigPanel({ config, onChange, allComponents const data = response.data?.data; if (response.data?.success && Array.isArray(data)) { setTables( - data.map((t: { tableName?: string; table_name?: string; displayName?: string; tableLabel?: string; table_label?: string }) => ({ - name: t.tableName || t.table_name || "", - // displayName ์šฐ์„ , ์—†์œผ๋ฉด tableLabel, ๊ทธ๊ฒƒ๋„ ์—†์œผ๋ฉด ํ…Œ์ด๋ธ”๋ช… - label: t.displayName || t.tableLabel || t.table_label || "", - })), + data.map( + (t: { + tableName?: string; + table_name?: string; + displayName?: string; + tableLabel?: string; + table_label?: string; + }) => ({ + name: t.tableName || t.table_name || "", + // displayName ์šฐ์„ , ์—†์œผ๋ฉด tableLabel, ๊ทธ๊ฒƒ๋„ ์—†์œผ๋ฉด ํ…Œ์ด๋ธ”๋ช… + label: t.displayName || t.tableLabel || t.table_label || "", + }), + ), ); } } catch (error) { @@ -308,10 +306,13 @@ export function UniversalFormModalConfigPanel({ config, onChange, allComponents displayName?: string; columnComment?: string; column_comment?: string; + inputType?: string; + input_type?: string; }) => ({ name: c.columnName || c.column_name || "", type: c.dataType || c.data_type || "text", label: c.displayName || c.columnComment || c.column_comment || c.columnName || c.column_name || "", + inputType: c.inputType || c.input_type || "text", }), ), })); @@ -359,21 +360,24 @@ export function UniversalFormModalConfigPanel({ config, onChange, allComponents ); // ์„น์…˜ ๊ด€๋ฆฌ - const addSection = useCallback((type: "fields" | "table" = "fields") => { - const newSection: FormSectionConfig = { - ...defaultSectionConfig, - id: generateSectionId(), - title: type === "table" ? `ํ…Œ์ด๋ธ” ์„น์…˜ ${config.sections.length + 1}` : `์„น์…˜ ${config.sections.length + 1}`, - type, - fields: type === "fields" ? [] : undefined, - tableConfig: type === "table" ? { ...defaultTableSectionConfig } : undefined, - }; - onChange({ - ...config, - sections: [...config.sections, newSection], - }); - }, [config, onChange]); - + const addSection = useCallback( + (type: "fields" | "table" = "fields") => { + const newSection: FormSectionConfig = { + ...defaultSectionConfig, + id: generateSectionId(), + title: type === "table" ? `ํ…Œ์ด๋ธ” ์„น์…˜ ${config.sections.length + 1}` : `์„น์…˜ ${config.sections.length + 1}`, + type, + fields: type === "fields" ? [] : undefined, + tableConfig: type === "table" ? { ...defaultTableSectionConfig } : undefined, + }; + onChange({ + ...config, + sections: [...config.sections, newSection], + }); + }, + [config, onChange], + ); + // ์„น์…˜ ํƒ€์ž… ๋ณ€๊ฒฝ const changeSectionType = useCallback( (sectionId: string, newType: "fields" | "table") => { @@ -381,7 +385,7 @@ export function UniversalFormModalConfigPanel({ config, onChange, allComponents ...config, sections: config.sections.map((s) => { if (s.id !== sectionId) return s; - + if (newType === "table") { return { ...s, @@ -400,9 +404,9 @@ export function UniversalFormModalConfigPanel({ config, onChange, allComponents }), }); }, - [config, onChange] + [config, onChange], ); - + // ํ…Œ์ด๋ธ” ์„น์…˜ ์„ค์ • ๋ชจ๋‹ฌ ์—ด๊ธฐ const handleOpenTableSectionSettings = (section: FormSectionConfig) => { setSelectedSection(section); @@ -487,293 +491,310 @@ export function UniversalFormModalConfigPanel({ config, onChange, allComponents }; return ( -
-
-
- {/* ๋ชจ๋‹ฌ ๊ธฐ๋ณธ ์„ค์ • */} - - - -
- - ๋ชจ๋‹ฌ ๊ธฐ๋ณธ ์„ค์ • -
-
- -
- - updateModalConfig({ title: e.target.value })} - className="h-9 text-sm w-full max-w-full" - /> - ๋ชจ๋‹ฌ ์ƒ๋‹จ์— ํ‘œ์‹œ๋  ์ œ๋ชฉ์ž…๋‹ˆ๋‹ค -
- -
- - - ๋ชจ๋‹ฌ ์ฐฝ์˜ ํฌ๊ธฐ๋ฅผ ์„ ํƒํ•˜์„ธ์š” -
- - {/* ์ €์žฅ ๋ฒ„ํŠผ ํ‘œ์‹œ ์„ค์ • */} -
-
- updateModalConfig({ showSaveButton: checked === true })} - /> - +
+
+
+ {/* ๋ชจ๋‹ฌ ๊ธฐ๋ณธ ์„ค์ • */} + + + +
+ + ๋ชจ๋‹ฌ ๊ธฐ๋ณธ ์„ค์ •
- ์ฒดํฌ ํ•ด์ œ ์‹œ ๋ชจ๋‹ฌ ํ•˜๋‹จ์˜ ์ €์žฅ ๋ฒ„ํŠผ์ด ์ˆจ๊ฒจ์ง‘๋‹ˆ๋‹ค -
- -
+ +
- + updateModalConfig({ saveButtonText: e.target.value })} - className="h-9 text-sm w-full max-w-full" + value={config.modal.title} + onChange={(e) => updateModalConfig({ title: e.target.value })} + className="h-9 w-full max-w-full text-sm" /> + ๋ชจ๋‹ฌ ์ƒ๋‹จ์— ํ‘œ์‹œ๋  ์ œ๋ชฉ์ž…๋‹ˆ๋‹ค
+
- - updateModalConfig({ cancelButtonText: e.target.value })} - className="h-9 text-sm w-full max-w-full" - /> + + + ๋ชจ๋‹ฌ ์ฐฝ์˜ ํฌ๊ธฐ๋ฅผ ์„ ํƒํ•˜์„ธ์š”
-
- - - - {/* ์ €์žฅ ์„ค์ • */} - - - -
- - ์ €์žฅ ์„ค์ • -
-
- -
-
- -

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

- {config.saveConfig.customApiSave?.enabled && config.saveConfig.customApiSave?.multiTable?.enabled && ( - - ๋‹ค์ค‘ ํ…Œ์ด๋ธ” ๋ชจ๋“œ - - )} + {/* ์ €์žฅ ๋ฒ„ํŠผ ํ‘œ์‹œ ์„ค์ • */} +
+
+ updateModalConfig({ showSaveButton: checked === true })} + /> + +
+ ์ฒดํฌ ํ•ด์ œ ์‹œ ๋ชจ๋‹ฌ ํ•˜๋‹จ์˜ ์ €์žฅ ๋ฒ„ํŠผ์ด ์ˆจ๊ฒจ์ง‘๋‹ˆ๋‹ค
- -
- - ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•  ํ…Œ์ด๋ธ”๊ณผ ๋ฐฉ์‹์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. -
- "์ €์žฅ ์„ค์ • ์—ด๊ธฐ"๋ฅผ ํด๋ฆญํ•˜์—ฌ ์ƒ์„ธ ์„ค์ •์„ ๋ณ€๊ฒฝํ•˜์„ธ์š”. -
- - - - {/* ์„น์…˜ ๊ตฌ์„ฑ */} - - - -
- - ์„น์…˜ ๊ตฌ์„ฑ - - {config.sections.length}๊ฐœ - -
-
- - {/* ์„น์…˜ ์ถ”๊ฐ€ ๋ฒ„ํŠผ๋“ค */} -
- -