From de7fa7a71bdd21b7392d7a6a07841aad267d0653 Mon Sep 17 00:00:00 2001 From: SeongHyun Kim Date: Thu, 15 Jan 2026 14:36:00 +0900 Subject: [PATCH 1/6] =?UTF-8?q?fix:=20=EB=B0=9C=EC=A3=BC/=EC=9E=85?= =?UTF-8?q?=EA=B3=A0=EA=B4=80=EB=A6=AC=20=EA=B7=B8=EB=A3=B9=20=ED=8E=B8?= =?UTF-8?q?=EC=A7=91=20=EC=8B=9C=20=EB=8B=A8=EA=B1=B4=EB=A7=8C=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=EB=90=98=EB=8D=98=20=EB=AC=B8=EC=A0=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20EditModal.tsx:=20conditional-container=20=EC=A1=B4?= =?UTF-8?q?=EC=9E=AC=20=EC=8B=9C=20onSave=20=EB=AF=B8=EC=A0=84=EB=8B=AC=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95=20ModalRepeaterTableCom?= =?UTF-8?q?ponent.tsx:=20groupedData=20prop=20=EC=9A=B0=EC=84=A0=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/screen/EditModal.tsx | 36 ++++++------------- .../ModalRepeaterTableComponent.tsx | 16 +++++++-- 2 files changed, 23 insertions(+), 29 deletions(-) diff --git a/frontend/components/screen/EditModal.tsx b/frontend/components/screen/EditModal.tsx index b3c94ade..7c722ad6 100644 --- a/frontend/components/screen/EditModal.tsx +++ b/frontend/components/screen/EditModal.tsx @@ -309,17 +309,10 @@ export const EditModal: React.FC = ({ className }) => { // ๐Ÿ†• ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ ์กฐํšŒ ํ•จ์ˆ˜ const loadGroupData = async () => { if (!modalState.tableName || !modalState.groupByColumns || modalState.groupByColumns.length === 0) { - // console.warn("ํ…Œ์ด๋ธ”๋ช… ๋˜๋Š” ๊ทธ๋ฃนํ•‘ ์ปฌ๋Ÿผ์ด ์—†์Šต๋‹ˆ๋‹ค."); return; } try { - // console.log("๐Ÿ” ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ ์กฐํšŒ ์‹œ์ž‘:", { - // tableName: modalState.tableName, - // groupByColumns: modalState.groupByColumns, - // editData: modalState.editData, - // }); - // ๊ทธ๋ฃนํ•‘ ์ปฌ๋Ÿผ ๊ฐ’ ์ถ”์ถœ (์˜ˆ: order_no = "ORD-20251124-001") const groupValues: Record = {}; modalState.groupByColumns.forEach((column) => { @@ -329,15 +322,9 @@ export const EditModal: React.FC = ({ className }) => { }); if (Object.keys(groupValues).length === 0) { - // console.warn("๊ทธ๋ฃนํ•‘ ์ปฌ๋Ÿผ ๊ฐ’์ด ์—†์Šต๋‹ˆ๋‹ค:", modalState.groupByColumns); return; } - // console.log("๐Ÿ” ๊ทธ๋ฃน ์กฐํšŒ ์š”์ฒญ:", { - // tableName: modalState.tableName, - // groupValues, - // }); - // ๊ฐ™์€ ๊ทธ๋ฃน์˜ ๋ชจ๋“  ๋ ˆ์ฝ”๋“œ ์กฐํšŒ (entityJoinApi ์‚ฌ์šฉ) const { entityJoinApi } = await import("@/lib/api/entityJoin"); const response = await entityJoinApi.getTableDataWithJoins(modalState.tableName, { @@ -347,23 +334,19 @@ export const EditModal: React.FC = ({ className }) => { enableEntityJoin: true, }); - // console.log("๐Ÿ” ๊ทธ๋ฃน ์กฐํšŒ ์‘๋‹ต:", response); - // entityJoinApi๋Š” ๋ฐฐ์—ด ๋˜๋Š” { data: [] } ํ˜•์‹์œผ๋กœ ๋ฐ˜ํ™˜ const dataArray = Array.isArray(response) ? response : response?.data || []; if (dataArray.length > 0) { - // console.log("โœ… ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ ์กฐํšŒ ์„ฑ๊ณต:", dataArray.length, "๊ฑด"); setGroupData(dataArray); setOriginalGroupData(JSON.parse(JSON.stringify(dataArray))); // Deep copy toast.info(`${dataArray.length}๊ฐœ์˜ ๊ด€๋ จ ํ’ˆ๋ชฉ์„ ๋ถˆ๋Ÿฌ์™”์Šต๋‹ˆ๋‹ค.`); } else { - console.warn("๊ทธ๋ฃน ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค:", response); setGroupData([modalState.editData]); // ๊ธฐ๋ณธ๊ฐ’: ์„ ํƒ๋œ ํ–‰๋งŒ setOriginalGroupData([JSON.parse(JSON.stringify(modalState.editData))]); } } catch (error: any) { - console.error("โŒ ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ ์กฐํšŒ ์˜ค๋ฅ˜:", error); + console.error("๊ทธ๋ฃน ๋ฐ์ดํ„ฐ ์กฐํšŒ ์˜ค๋ฅ˜:", error); toast.error("๊ด€๋ จ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค."); setGroupData([modalState.editData]); // ๊ธฐ๋ณธ๊ฐ’: ์„ ํƒ๋œ ํ–‰๋งŒ setOriginalGroupData([JSON.parse(JSON.stringify(modalState.editData))]); @@ -1043,17 +1026,18 @@ export const EditModal: React.FC = ({ className }) => { const groupedDataProp = groupData.length > 0 ? groupData : undefined; // ๐Ÿ†• UniversalFormModal์ด ์žˆ๋Š”์ง€ ํ™•์ธ (์ž์ฒด ์ €์žฅ ๋กœ์ง ์‚ฌ์šฉ) - // ์ตœ์ƒ์œ„ ์ปดํฌ๋„ŒํŠธ ๋˜๋Š” ์กฐ๊ฑด๋ถ€ ์ปจํ…Œ์ด๋„ˆ ๋‚ด๋ถ€ ํ™”๋ฉด์— universal-form-modal์ด ์žˆ๋Š”์ง€ ํ™•์ธ + // ์ตœ์ƒ์œ„ ์ปดํฌ๋„ŒํŠธ์— universal-form-modal์ด ์žˆ๋Š”์ง€ ํ™•์ธ + // โš ๏ธ ์ˆ˜์ •: conditional-container๋Š” ์ œ์™ธ (groupData๊ฐ€ ์žˆ์œผ๋ฉด EditModal.handleSave ์‚ฌ์šฉ) const hasUniversalFormModal = screenData.components.some( (c) => { - // ์ตœ์ƒ์œ„์— universal-form-modal์ด ์žˆ๋Š” ๊ฒฝ์šฐ + // ์ตœ์ƒ์œ„์— universal-form-modal์ด ์žˆ๋Š” ๊ฒฝ์šฐ๋งŒ ์ž์ฒด ์ €์žฅ ๋กœ์ง ์‚ฌ์šฉ if (c.componentType === "universal-form-modal") return true; - // ์กฐ๊ฑด๋ถ€ ์ปจํ…Œ์ด๋„ˆ ๋‚ด๋ถ€์— universal-form-modal์ด ์žˆ๋Š” ๊ฒฝ์šฐ - // (์กฐ๊ฑด๋ถ€ ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์žˆ์œผ๋ฉด ๋‚ด๋ถ€ ํ™”๋ฉด์—์„œ universal-form-modal์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์œผ๋กœ ๊ฐ€์ •) - if (c.componentType === "conditional-container") return true; return false; } ); + + // ๐Ÿ†• ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์œผ๋ฉด EditModal.handleSave ์‚ฌ์šฉ (์ผ๊ด„ ์ €์žฅ) + const shouldUseEditModalSave = groupData.length > 0 || !hasUniversalFormModal; // ๐Ÿ”‘ ์ฒจ๋ถ€ํŒŒ์ผ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ–‰(๋ ˆ์ฝ”๋“œ) ๋‹จ์œ„๋กœ ํŒŒ์ผ์„ ์ €์žฅํ•  ์ˆ˜ ์žˆ๋„๋ก tableName ์ถ”๊ฐ€ const enrichedFormData = { @@ -1095,9 +1079,9 @@ export const EditModal: React.FC = ({ className }) => { id: modalState.screenId!, tableName: screenData.screenInfo?.tableName, }} - // ๐Ÿ†• UniversalFormModal์ด ์žˆ์œผ๋ฉด onSave ์ „๋‹ฌ ์•ˆ ํ•จ (์ž์ฒด ์ €์žฅ ๋กœ์ง ์‚ฌ์šฉ) - // ModalRepeaterTable๋งŒ ์žˆ์œผ๋ฉด ๊ธฐ์กด๋Œ€๋กœ onSave ์ „๋‹ฌ (ํ˜ธํ™˜์„ฑ ์œ ์ง€) - onSave={hasUniversalFormModal ? undefined : handleSave} + // ๐Ÿ†• ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๊ฑฐ๋‚˜ UniversalFormModal์ด ์—†์œผ๋ฉด EditModal.handleSave ์‚ฌ์šฉ + // groupData๊ฐ€ ์žˆ์œผ๋ฉด ์ผ๊ด„ ์ €์žฅ์„ ์œ„ํ•ด ๋ฐ˜๋“œ์‹œ EditModal.handleSave ์‚ฌ์šฉ + onSave={shouldUseEditModalSave ? handleSave : undefined} isInModal={true} // ๐Ÿ†• ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ๋ฅผ ModalRepeaterTable์— ์ „๋‹ฌ groupedData={groupedDataProp} diff --git a/frontend/lib/registry/components/modal-repeater-table/ModalRepeaterTableComponent.tsx b/frontend/lib/registry/components/modal-repeater-table/ModalRepeaterTableComponent.tsx index 2caf1332..153cebdf 100644 --- a/frontend/lib/registry/components/modal-repeater-table/ModalRepeaterTableComponent.tsx +++ b/frontend/lib/registry/components/modal-repeater-table/ModalRepeaterTableComponent.tsx @@ -180,8 +180,11 @@ export function ModalRepeaterTableComponent({ filterCondition: propFilterCondition, companyCode: propCompanyCode, + // ๐Ÿ†• ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ (EditModal์—์„œ ์ „๋‹ฌ, ๊ฐ™์€ ๊ทธ๋ฃน์˜ ์—ฌ๋Ÿฌ ํ’ˆ๋ชฉ) + groupedData, + ...props -}: ModalRepeaterTableComponentProps) { +}: ModalRepeaterTableComponentProps & { groupedData?: Record[] }) { // โœ… config ๋˜๋Š” component.config ๋˜๋Š” ๊ฐœ๋ณ„ prop ์šฐ์„ ์ˆœ์œ„๋กœ ๋ณ‘ํ•ฉ const componentConfig = { ...config, @@ -208,9 +211,16 @@ export function ModalRepeaterTableComponent({ // ๋ชจ๋‹ฌ ํ•„ํ„ฐ ์„ค์ • const modalFilters = componentConfig?.modalFilters || []; - // โœ… value๋Š” formData[columnName] ์šฐ์„ , ์—†์œผ๋ฉด prop ์‚ฌ์šฉ + // โœ… value๋Š” groupedData ์šฐ์„ , ์—†์œผ๋ฉด formData[columnName], ์—†์œผ๋ฉด prop ์‚ฌ์šฉ const columnName = component?.columnName; - const externalValue = (columnName && formData?.[columnName]) || componentConfig?.value || propValue || []; + + // ๐Ÿ†• groupedData๊ฐ€ ์ „๋‹ฌ๋˜๋ฉด (EditModal์—์„œ ๊ทธ๋ฃน ์กฐํšŒ ๊ฒฐ๊ณผ) ์šฐ์„  ์‚ฌ์šฉ + const externalValue = (() => { + if (groupedData && groupedData.length > 0) { + return groupedData; + } + return (columnName && formData?.[columnName]) || componentConfig?.value || propValue || []; + })(); // ๋นˆ ๊ฐ์ฒด ํŒ๋‹จ ํ•จ์ˆ˜ (์ˆ˜์ • ๋ชจ๋‹ฌ์˜ ์‹ค์ œ ๋ฐ์ดํ„ฐ๋Š” ์œ ์ง€) const isEmptyRow = (item: any): boolean => { From d4b5bdd835244c6e9b43101611ed62ef38787477 Mon Sep 17 00:00:00 2001 From: SeongHyun Kim Date: Mon, 19 Jan 2026 13:18:17 +0900 Subject: [PATCH 2/6] =?UTF-8?q?feat:=20RepeaterInput=20=ED=95=98=EC=9C=84?= =?UTF-8?q?=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=A1=B0=ED=9A=8C=20=EC=BB=AC?= =?UTF-8?q?=EB=9F=BC=20=EC=84=A4=EC=A0=95=20=EA=B8=B0=EB=8A=A5=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ํ‘œ์‹œ ์ปฌ๋Ÿผ ์ˆœ์„œ ๋ณ€๊ฒฝ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ (columnOrder) - ์กฐํšŒ ์ปฌ๋Ÿผ -> ์ €์žฅ ์ปฌ๋Ÿผ ๋งคํ•‘ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ (fieldMappings) - ์ปฌ๋Ÿผ๋ณ„ ๋ผ๋ฒจ, ์ˆœ์„œ, ์ €์žฅ ์—ฌ๋ถ€ ํ†ตํ•ฉ ์„ค์ • UI ๊ตฌํ˜„ - ํ•˜์œ„ ํ˜ธํ™˜์„ฑ ์œ ์ง€ (fieldMappings ์—†์œผ๋ฉด ๊ธฐ์กด ๋กœ์ง ์‚ฌ์šฉ) --- .../components/webtypes/RepeaterInput.tsx | 32 ++- .../webtypes/config/RepeaterConfigPanel.tsx | 196 +++++++++++++++++- .../SubDataLookupPanel.tsx | 16 +- .../repeater-field-group/useSubDataLookup.ts | 12 +- frontend/types/repeater.ts | 10 + 5 files changed, 252 insertions(+), 14 deletions(-) diff --git a/frontend/components/webtypes/RepeaterInput.tsx b/frontend/components/webtypes/RepeaterInput.tsx index 7cd4b279..49751699 100644 --- a/frontend/components/webtypes/RepeaterInput.tsx +++ b/frontend/components/webtypes/RepeaterInput.tsx @@ -309,18 +309,32 @@ export const RepeaterInput: React.FC = ({ _subDataMaxValue: maxValue, }; - // ์„ ํƒ๋œ ํ•˜์œ„ ๋ฐ์ดํ„ฐ์˜ ํ•„๋“œ ๊ฐ’์„ ์ƒ์œ„ item์— ๋ณต์‚ฌ (์„ค์ •๋œ ๊ฒฝ์šฐ) - // ์˜ˆ: warehouse_code, location_code ๋“ฑ - if (subDataLookup.lookup.displayColumns) { - subDataLookup.lookup.displayColumns.forEach((col) => { - if (selectedItem[col] !== undefined) { - // ํ•„๋“œ๊ฐ€ ์ •์˜๋˜์–ด ์žˆ์œผ๋ฉด ๋ณต์‚ฌ - const fieldDef = fields.find((f) => f.name === col); - if (fieldDef || col.includes("_code") || col.includes("_id")) { - newItems[itemIndex][col] = selectedItem[col]; + // fieldMappings๊ฐ€ ์„ค์ •๋˜์–ด ์žˆ์œผ๋ฉด ๋งคํ•‘์— ๋”ฐ๋ผ ๊ฐ’ ๋ณต์‚ฌ + if (subDataLookup.lookup.fieldMappings && subDataLookup.lookup.fieldMappings.length > 0) { + subDataLookup.lookup.fieldMappings.forEach((mapping) => { + if (mapping.targetField && mapping.targetField !== "") { + // ๋งคํ•‘๋œ ํƒ€๊ฒŸ ํ•„๋“œ์— ์†Œ์Šค ์ปฌ๋Ÿผ ๊ฐ’ ๋ณต์‚ฌ + const sourceValue = selectedItem[mapping.sourceColumn]; + if (sourceValue !== undefined) { + newItems[itemIndex][mapping.targetField] = sourceValue; } } }); + } else { + // fieldMappings๊ฐ€ ์—†์œผ๋ฉด ๊ธฐ์กด ๋กœ์ง (ํ•˜์œ„ ํ˜ธํ™˜์„ฑ) + // ์„ ํƒ๋œ ํ•˜์œ„ ๋ฐ์ดํ„ฐ์˜ ํ•„๋“œ ๊ฐ’์„ ์ƒ์œ„ item์— ๋ณต์‚ฌ (์„ค์ •๋œ ๊ฒฝ์šฐ) + // ์˜ˆ: warehouse_code, location_code ๋“ฑ + if (subDataLookup.lookup.displayColumns) { + subDataLookup.lookup.displayColumns.forEach((col) => { + if (selectedItem[col] !== undefined) { + // ํ•„๋“œ๊ฐ€ ์ •์˜๋˜์–ด ์žˆ์œผ๋ฉด ๋ณต์‚ฌ + const fieldDef = fields.find((f) => f.name === col); + if (fieldDef || col.includes("_code") || col.includes("_id")) { + newItems[itemIndex][col] = selectedItem[col]; + } + } + }); + } } setItems(newItems); diff --git a/frontend/components/webtypes/config/RepeaterConfigPanel.tsx b/frontend/components/webtypes/config/RepeaterConfigPanel.tsx index 97e20574..857ece17 100644 --- a/frontend/components/webtypes/config/RepeaterConfigPanel.tsx +++ b/frontend/components/webtypes/config/RepeaterConfigPanel.tsx @@ -319,6 +319,103 @@ export const RepeaterConfigPanel: React.FC = ({ }); }; + // ํ‘œ์‹œ ์ปฌ๋Ÿผ ์ˆœ์„œ ๊ฐ€์ ธ์˜ค๊ธฐ (columnOrder๊ฐ€ ์žˆ์œผ๋ฉด ์‚ฌ์šฉ, ์—†์œผ๋ฉด displayColumns ์ˆœ์„œ) + const getOrderedDisplayColumns = (): string[] => { + const displayColumns = config.subDataLookup?.lookup?.displayColumns || []; + const columnOrder = config.subDataLookup?.lookup?.columnOrder; + + if (columnOrder && columnOrder.length > 0) { + // columnOrder์— ์žˆ๋Š” ์ปฌ๋Ÿผ๋งŒ, ์ˆœ์„œ๋Œ€๋กœ ๋ฐ˜ํ™˜ (displayColumns์— ์žˆ๋Š” ๊ฒƒ๋งŒ) + const orderedCols = columnOrder.filter(col => displayColumns.includes(col)); + // columnOrder์— ์—†์ง€๋งŒ displayColumns์— ์žˆ๋Š” ์ปฌ๋Ÿผ ์ถ”๊ฐ€ + const remainingCols = displayColumns.filter(col => !columnOrder.includes(col)); + return [...orderedCols, ...remainingCols]; + } + return displayColumns; + }; + + // ํ‘œ์‹œ ์ปฌ๋Ÿผ ์ˆœ์„œ ๋ณ€๊ฒฝ ํ•ธ๋“ค๋Ÿฌ (์œ„๋กœ) + const handleDisplayColumnMoveUp = (columnName: string) => { + const orderedColumns = getOrderedDisplayColumns(); + const index = orderedColumns.indexOf(columnName); + if (index <= 0) return; + + const newOrder = [...orderedColumns]; + [newOrder[index - 1], newOrder[index]] = [newOrder[index], newOrder[index - 1]]; + handleSubDataLookupChange("lookup.columnOrder", newOrder); + }; + + // ํ‘œ์‹œ ์ปฌ๋Ÿผ ์ˆœ์„œ ๋ณ€๊ฒฝ ํ•ธ๋“ค๋Ÿฌ (์•„๋ž˜๋กœ) + const handleDisplayColumnMoveDown = (columnName: string) => { + const orderedColumns = getOrderedDisplayColumns(); + const index = orderedColumns.indexOf(columnName); + if (index < 0 || index >= orderedColumns.length - 1) return; + + const newOrder = [...orderedColumns]; + [newOrder[index], newOrder[index + 1]] = [newOrder[index + 1], newOrder[index]]; + handleSubDataLookupChange("lookup.columnOrder", newOrder); + }; + + // ํ‘œ์‹œ ์ปฌ๋Ÿผ ํ† ๊ธ€ ์‹œ columnOrder๋„ ์—…๋ฐ์ดํŠธ + const handleDisplayColumnToggleWithOrder = (columnName: string, checked: boolean) => { + const currentColumns = config.subDataLookup?.lookup?.displayColumns || []; + const currentOrder = config.subDataLookup?.lookup?.columnOrder || []; + const currentMappings = config.subDataLookup?.lookup?.fieldMappings || []; + + let newColumns: string[]; + let newOrder: string[]; + let newMappings: { sourceColumn: string; targetField: string }[]; + + if (checked) { + newColumns = [...currentColumns, columnName]; + newOrder = [...currentOrder, columnName]; + // ๊ธฐ๋ณธ ๋งคํ•‘ ์ถ”๊ฐ€: ๋™์ผํ•œ ์ปฌ๋Ÿผ๋ช…์ด targetTable์— ์žˆ์œผ๋ฉด ์ž๋™ ๋งคํ•‘, ์—†์œผ๋ฉด ๋นˆ ๋ฌธ์ž์—ด + const targetColumn = tableColumns.find((c) => c.columnName === columnName); + newMappings = [...currentMappings, { sourceColumn: columnName, targetField: targetColumn ? columnName : "" }]; + } else { + newColumns = currentColumns.filter((c) => c !== columnName); + newOrder = currentOrder.filter((c) => c !== columnName); + newMappings = currentMappings.filter((m) => m.sourceColumn !== columnName); + } + + // displayColumns, columnOrder, fieldMappings ํ•จ๊ป˜ ์—…๋ฐ์ดํŠธ + const newConfig = { ...config.subDataLookup } as SubDataLookupConfig; + if (!newConfig.lookup) { + newConfig.lookup = { tableName: "", linkColumn: "", displayColumns: [] }; + } + newConfig.lookup.displayColumns = newColumns; + newConfig.lookup.columnOrder = newOrder; + newConfig.lookup.fieldMappings = newMappings; + + onChange({ + ...config, + subDataLookup: newConfig, + }); + }; + + // ํ•„๋“œ ๋งคํ•‘ ๋ณ€๊ฒฝ ํ•ธ๋“ค๋Ÿฌ + const handleFieldMappingChange = (sourceColumn: string, targetField: string) => { + const currentMappings = config.subDataLookup?.lookup?.fieldMappings || []; + const existingIndex = currentMappings.findIndex((m) => m.sourceColumn === sourceColumn); + + let newMappings: { sourceColumn: string; targetField: string }[]; + if (existingIndex >= 0) { + newMappings = [...currentMappings]; + newMappings[existingIndex] = { sourceColumn, targetField }; + } else { + newMappings = [...currentMappings, { sourceColumn, targetField }]; + } + + handleSubDataLookupChange("lookup.fieldMappings", newMappings); + }; + + // ํŠน์ • ์ปฌ๋Ÿผ์˜ ํ˜„์žฌ ๋งคํ•‘๋œ ํƒ€๊ฒŸ ํ•„๋“œ ๊ฐ€์ ธ์˜ค๊ธฐ + const getFieldMapping = (sourceColumn: string): string => { + const mappings = config.subDataLookup?.lookup?.fieldMappings || []; + const mapping = mappings.find((m) => m.sourceColumn === sourceColumn); + return mapping?.targetField || ""; + }; + return (
{/* ๋Œ€์ƒ ํ…Œ์ด๋ธ” ์„ ํƒ */} @@ -588,7 +685,7 @@ export const RepeaterConfigPanel: React.FC = ({ handleDisplayColumnToggle(col.columnName, checked as boolean)} + onCheckedChange={(checked) => handleDisplayColumnToggleWithOrder(col.columnName, checked as boolean)} />
)} + {/* ์ปฌ๋Ÿผ ์„ค์ • (์ˆœ์„œ + ๋ผ๋ฒจ + ์ €์žฅ ์ปฌ๋Ÿผ) */} + {(config.subDataLookup?.lookup?.displayColumns?.length || 0) > 0 && ( +
+ +

์ˆœ์„œ, ๋ผ๋ฒจ, ์ €์žฅ ์—ฌ๋ถ€๋ฅผ ์„ค์ •ํ•˜์„ธ์š”

+
+ {getOrderedDisplayColumns().map((colName, index) => { + const col = subDataTableColumns.find((c) => c.columnName === colName); + const currentLabel = config.subDataLookup?.lookup?.columnLabels?.[colName] || ""; + const currentMapping = getFieldMapping(colName); + const orderedColumns = getOrderedDisplayColumns(); + const isFirst = index === 0; + const isLast = index === orderedColumns.length - 1; + + return ( +
+ {/* ์ƒ๋‹จ: ์ˆœ์„œ ๋ฒ„ํŠผ + ๋ฒˆํ˜ธ + ์ปฌ๋Ÿผ๋ช… */} +
+ {/* ์ˆœ์„œ ๋ณ€๊ฒฝ ๋ฒ„ํŠผ */} +
+ + +
+ + {/* ์ˆœ์„œ ๋ฒˆํ˜ธ */} + {index + 1} + + {/* ์ปฌ๋Ÿผ๋ช… */} +
+ {col?.columnLabel || colName} + ({colName}) +
+
+ + {/* ์ค‘๋‹จ: ๋ผ๋ฒจ ์ž…๋ ฅ */} +
+ ํ‘œ์‹œ ๋ผ๋ฒจ: + handleColumnLabelChange(colName, e.target.value)} + placeholder={col?.columnLabel || colName} + className="h-6 flex-1 text-xs" + /> +
+ + {/* ํ•˜๋‹จ: ์ €์žฅ ์ปฌ๋Ÿผ ์„ ํƒ */} +
+ ์ €์žฅ ์ปฌ๋Ÿผ: + +
+
+ ); + })} +
+ {config.targetTable && ( +

+ * ์ €์žฅ ๋Œ€์ƒ: {config.targetTable} +

+ )} +
+ )} + {/* ์„ ํƒ ์„ค์ • */} {(config.subDataLookup?.lookup?.displayColumns?.length || 0) > 0 && (
diff --git a/frontend/lib/registry/components/repeater-field-group/SubDataLookupPanel.tsx b/frontend/lib/registry/components/repeater-field-group/SubDataLookupPanel.tsx index 5baf0fe0..51be5a64 100644 --- a/frontend/lib/registry/components/repeater-field-group/SubDataLookupPanel.tsx +++ b/frontend/lib/registry/components/repeater-field-group/SubDataLookupPanel.tsx @@ -78,8 +78,20 @@ export const SubDataLookupPanel: React.FC = ({ return config.lookup.columnLabels?.[columnName] || columnName; }; - // ํ‘œ์‹œํ•  ์ปฌ๋Ÿผ ๋ชฉ๋ก - const displayColumns = config.lookup.displayColumns || []; + // ํ‘œ์‹œํ•  ์ปฌ๋Ÿผ ๋ชฉ๋ก (columnOrder๊ฐ€ ์žˆ์œผ๋ฉด ์ˆœ์„œ ์ ์šฉ) + const displayColumns = useMemo(() => { + const columns = config.lookup.displayColumns || []; + const columnOrder = config.lookup.columnOrder; + + if (columnOrder && columnOrder.length > 0) { + // columnOrder ์ˆœ์„œ๋Œ€๋กœ ์ •๋ ฌ (displayColumns์— ์žˆ๋Š” ๊ฒƒ๋งŒ) + const orderedCols = columnOrder.filter(col => columns.includes(col)); + // columnOrder์— ์—†์ง€๋งŒ displayColumns์— ์žˆ๋Š” ์ปฌ๋Ÿผ ์ถ”๊ฐ€ + const remainingCols = columns.filter(col => !columnOrder.includes(col)); + return [...orderedCols, ...remainingCols]; + } + return columns; + }, [config.lookup.displayColumns, config.lookup.columnOrder]); // ์š”์•ฝ ์ •๋ณด ํ‘œ์‹œ์šฉ ์„ ํƒ ์ƒํƒœ const summaryText = useMemo(() => { diff --git a/frontend/lib/registry/components/repeater-field-group/useSubDataLookup.ts b/frontend/lib/registry/components/repeater-field-group/useSubDataLookup.ts index b2c44e3d..2753dd16 100644 --- a/frontend/lib/registry/components/repeater-field-group/useSubDataLookup.ts +++ b/frontend/lib/registry/components/repeater-field-group/useSubDataLookup.ts @@ -197,10 +197,18 @@ export function useSubDataLookup(props: UseSubDataLookupProps): UseSubDataLookup return "์„ ํƒ ์•ˆ๋จ"; } - const { displayColumns, columnLabels } = config.lookup; + const { displayColumns, columnLabels, columnOrder } = config.lookup; const parts: string[] = []; - displayColumns.forEach((col) => { + // columnOrder๊ฐ€ ์žˆ์œผ๋ฉด ์ˆœ์„œ ์ ์šฉ, ์—†์œผ๋ฉด displayColumns ์ˆœ์„œ + let orderedColumns = displayColumns; + if (columnOrder && columnOrder.length > 0) { + const orderedCols = columnOrder.filter(col => displayColumns.includes(col)); + const remainingCols = displayColumns.filter(col => !columnOrder.includes(col)); + orderedColumns = [...orderedCols, ...remainingCols]; + } + + orderedColumns.forEach((col) => { const value = selectedItem[col]; if (value !== undefined && value !== null && value !== "") { const label = columnLabels?.[col] || col; diff --git a/frontend/types/repeater.ts b/frontend/types/repeater.ts index 2362210b..bbdc8727 100644 --- a/frontend/types/repeater.ts +++ b/frontend/types/repeater.ts @@ -113,6 +113,14 @@ export type RepeaterData = RepeaterItemData[]; // ํ’ˆ๋ชฉ ์„ ํƒ ์‹œ ์žฌ๊ณ /๋‹จ๊ฐ€ ๋“ฑ ๊ด€๋ จ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•˜๊ณ  ์„ ํƒํ•˜๋Š” ๊ธฐ๋Šฅ // ============================================================ +/** + * ์„ ํƒ ๋ฐ์ดํ„ฐ ํ•„๋“œ ๋งคํ•‘ ์„ค์ • + */ +export interface SubDataFieldMapping { + sourceColumn: string; // ์กฐํšŒ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ (์˜ˆ: lot_number) + targetField: string; // ์ €์žฅ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ (์˜ˆ: lot_number) ๋˜๋Š” "" (์„ ํƒ์•ˆํ•จ) +} + /** * ํ•˜์œ„ ๋ฐ์ดํ„ฐ ์กฐํšŒ ํ…Œ์ด๋ธ” ์„ค์ • */ @@ -121,6 +129,8 @@ export interface SubDataLookupSettings { linkColumn: string; // ์ƒ์œ„ ๋ฐ์ดํ„ฐ์™€ ์—ฐ๊ฒฐํ•  ์ปฌ๋Ÿผ (์˜ˆ: item_code) displayColumns: string[]; // ํ‘œ์‹œํ•  ์ปฌ๋Ÿผ๋“ค (์˜ˆ: ["warehouse_code", "location_code", "quantity"]) columnLabels?: Record; // ์ปฌ๋Ÿผ ๋ผ๋ฒจ (์˜ˆ: { warehouse_code: "์ฐฝ๊ณ " }) + columnOrder?: string[]; // ์ปฌ๋Ÿผ ํ‘œ์‹œ ์ˆœ์„œ (์—†์œผ๋ฉด displayColumns ์ˆœ์„œ ์‚ฌ์šฉ) + fieldMappings?: SubDataFieldMapping[]; // ์„ ํƒ ๋ฐ์ดํ„ฐ ์ €์žฅ ๋งคํ•‘ (์กฐํšŒ ์ปฌ๋Ÿผ โ†’ ์ €์žฅ ์ปฌ๋Ÿผ) additionalFilters?: Record; // ์ถ”๊ฐ€ ํ•„ํ„ฐ ์กฐ๊ฑด } From b62a0b7e3b1572eb8485425968ace76afd7b3648 Mon Sep 17 00:00:00 2001 From: SeongHyun Kim Date: Mon, 19 Jan 2026 18:48:18 +0900 Subject: [PATCH 3/6] =?UTF-8?q?fix:=20=EB=B6=84=ED=95=A0=ED=8C=A8=EB=84=90?= =?UTF-8?q?=20=ED=99=94=EB=A9=B4=20=EB=B3=B5=EA=B5=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SplitPanelLayoutComponent.tsx | 470 ++++++++++++++++-- 1 file changed, 439 insertions(+), 31 deletions(-) diff --git a/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx b/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx index 50f7c41b..869d2c3c 100644 --- a/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx +++ b/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx @@ -33,6 +33,7 @@ import { DialogDescription, } from "@/components/ui/dialog"; import { Label } from "@/components/ui/label"; +import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { useTableOptions } from "@/contexts/TableOptionsContext"; import { TableFilter, ColumnVisibility, GroupSumConfig } from "@/types/table-options"; import { useAuth } from "@/hooks/useAuth"; @@ -171,6 +172,12 @@ export const SplitPanelLayoutComponent: React.FC const [rightSearchQuery, setRightSearchQuery] = useState(""); const [isLoadingLeft, setIsLoadingLeft] = useState(false); const [isLoadingRight, setIsLoadingRight] = useState(false); + + // ๐Ÿ†• ์ถ”๊ฐ€ ํƒญ ๊ด€๋ จ ์ƒํƒœ + const [activeTabIndex, setActiveTabIndex] = useState(0); // 0 = ๊ธฐ๋ณธ ํƒญ (์šฐ์ธก ํŒจ๋„), 1+ = ์ถ”๊ฐ€ ํƒญ + const [tabsData, setTabsData] = useState>({}); // ํƒญ๋ณ„ ๋ฐ์ดํ„ฐ ์บ์‹œ + const [tabsLoading, setTabsLoading] = useState>({}); // ํƒญ๋ณ„ ๋กœ๋”ฉ ์ƒํƒœ + const [rightTableColumns, setRightTableColumns] = useState([]); // ์šฐ์ธก ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ์ •๋ณด const [expandedItems, setExpandedItems] = useState>(new Set()); // ํŽผ์ณ์ง„ ํ•ญ๋ชฉ๋“ค const [leftColumnLabels, setLeftColumnLabels] = useState>({}); // ์ขŒ์ธก ์ปฌ๋Ÿผ ๋ผ๋ฒจ @@ -1001,12 +1008,137 @@ export const SplitPanelLayoutComponent: React.FC ], ); + // ๐Ÿ†• ์ถ”๊ฐ€ ํƒญ ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ํ•จ์ˆ˜ + const loadTabData = useCallback( + async (tabIndex: number, leftItem: any) => { + const tabConfig = componentConfig.rightPanel?.additionalTabs?.[tabIndex - 1]; + if (!tabConfig || !leftItem || isDesignMode) return; + + const tabTableName = tabConfig.tableName; + if (!tabTableName) return; + + setTabsLoading((prev) => ({ ...prev, [tabIndex]: true })); + try { + // ์กฐ์ธ ํ‚ค ํ™•์ธ + const keys = tabConfig.relation?.keys; + const leftColumn = tabConfig.relation?.leftColumn || keys?.[0]?.leftColumn; + const rightColumn = tabConfig.relation?.foreignKey || keys?.[0]?.rightColumn; + + let resultData: any[] = []; + + if (leftColumn && rightColumn) { + // ์กฐ์ธ ์กฐ๊ฑด์ด ์žˆ๋Š” ๊ฒฝ์šฐ + const { entityJoinApi } = await import("@/lib/api/entityJoin"); + const searchConditions: Record = {}; + + if (keys && keys.length > 0) { + // ๋ณตํ•ฉํ‚ค + keys.forEach((key) => { + if (key.leftColumn && key.rightColumn && leftItem[key.leftColumn] !== undefined) { + searchConditions[key.rightColumn] = leftItem[key.leftColumn]; + } + }); + } else { + // ๋‹จ์ผํ‚ค + const leftValue = leftItem[leftColumn]; + if (leftValue !== undefined) { + searchConditions[rightColumn] = leftValue; + } + } + + console.log(`๐Ÿ”— [์ถ”๊ฐ€ํƒญ ${tabIndex}] ์กฐํšŒ ์กฐ๊ฑด:`, searchConditions); + + const result = await entityJoinApi.getTableDataWithJoins(tabTableName, { + search: searchConditions, + enableEntityJoin: true, + size: 1000, + }); + + resultData = result.data || []; + } else { + // ์กฐ์ธ ์กฐ๊ฑด์ด ์—†๋Š” ๊ฒฝ์šฐ: ์ „์ฒด ๋ฐ์ดํ„ฐ ์กฐํšŒ (๋…๋ฆฝ ํƒญ) + const { entityJoinApi } = await import("@/lib/api/entityJoin"); + const result = await entityJoinApi.getTableDataWithJoins(tabTableName, { + enableEntityJoin: true, + size: 1000, + }); + resultData = result.data || []; + } + + // ๋ฐ์ดํ„ฐ ํ•„ํ„ฐ ์ ์šฉ + const dataFilter = tabConfig.dataFilter; + if (dataFilter?.enabled && dataFilter.conditions?.length > 0) { + resultData = resultData.filter((item: any) => { + return dataFilter.conditions.every((cond: any) => { + const value = item[cond.column]; + const condValue = cond.value; + switch (cond.operator) { + case "equals": + return value === condValue; + case "notEquals": + return value !== condValue; + case "contains": + return String(value).includes(String(condValue)); + default: + return true; + } + }); + }); + } + + // ์ค‘๋ณต ์ œ๊ฑฐ ์ ์šฉ + const deduplication = tabConfig.deduplication; + if (deduplication?.enabled && deduplication.groupByColumn) { + const groupedMap = new Map(); + resultData.forEach((item) => { + const key = String(item[deduplication.groupByColumn] || ""); + const existing = groupedMap.get(key); + if (!existing) { + groupedMap.set(key, item); + } else { + // keepStrategy์— ๋”ฐ๋ผ ์œ ์ง€ํ•  ํ•ญ๋ชฉ ๊ฒฐ์ • + const sortCol = deduplication.sortColumn || "start_date"; + const existingVal = existing[sortCol]; + const newVal = item[sortCol]; + if (deduplication.keepStrategy === "latest" && newVal > existingVal) { + groupedMap.set(key, item); + } else if (deduplication.keepStrategy === "earliest" && newVal < existingVal) { + groupedMap.set(key, item); + } + } + }); + resultData = Array.from(groupedMap.values()); + } + + console.log(`๐Ÿ”— [์ถ”๊ฐ€ํƒญ ${tabIndex}] ๊ฒฐ๊ณผ ๋ฐ์ดํ„ฐ:`, resultData.length); + setTabsData((prev) => ({ ...prev, [tabIndex]: resultData })); + } catch (error) { + console.error(`์ถ”๊ฐ€ํƒญ ${tabIndex} ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์‹คํŒจ:`, error); + toast({ + title: "๋ฐ์ดํ„ฐ ๋กœ๋“œ ์‹คํŒจ", + description: `ํƒญ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.`, + variant: "destructive", + }); + } finally { + setTabsLoading((prev) => ({ ...prev, [tabIndex]: false })); + } + }, + [componentConfig.rightPanel?.additionalTabs, isDesignMode, toast], + ); + // ์ขŒ์ธก ํ•ญ๋ชฉ ์„ ํƒ ํ•ธ๋“ค๋Ÿฌ const handleLeftItemSelect = useCallback( (item: any) => { setSelectedLeftItem(item); setExpandedRightItems(new Set()); // ์ขŒ์ธก ํ•ญ๋ชฉ ๋ณ€๊ฒฝ ์‹œ ์šฐ์ธก ํ™•์žฅ ์ดˆ๊ธฐํ™” - loadRightData(item); + setTabsData({}); // ๐Ÿ†• ๋ชจ๋“  ํƒญ ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐํ™” + + // ๐Ÿ†• ํ˜„์žฌ ํ™œ์„ฑ ํƒญ์— ๋”ฐ๋ผ ๋ฐ์ดํ„ฐ ๋กœ๋“œ + if (activeTabIndex === 0) { + loadRightData(item); + } else { + loadTabData(activeTabIndex, item); + } // ๐Ÿ†• modalDataStore์— ์„ ํƒ๋œ ์ขŒ์ธก ํ•ญ๋ชฉ ์ €์žฅ (๋‹จ์ผ ์„ ํƒ) const leftTableName = componentConfig.leftPanel?.tableName; @@ -1017,7 +1149,30 @@ export const SplitPanelLayoutComponent: React.FC }); } }, - [loadRightData, componentConfig.leftPanel?.tableName, isDesignMode], + [loadRightData, loadTabData, activeTabIndex, componentConfig.leftPanel?.tableName, isDesignMode], + ); + + // ๐Ÿ†• ํƒญ ๋ณ€๊ฒฝ ํ•ธ๋“ค๋Ÿฌ + const handleTabChange = useCallback( + (newTabIndex: number) => { + setActiveTabIndex(newTabIndex); + + // ์„ ํƒ๋œ ์ขŒ์ธก ํ•ญ๋ชฉ์ด ์žˆ์œผ๋ฉด ํ•ด๋‹น ํƒญ์˜ ๋ฐ์ดํ„ฐ ๋กœ๋“œ + if (selectedLeftItem) { + if (newTabIndex === 0) { + // ๊ธฐ๋ณธ ํƒญ: ์šฐ์ธก ํŒจ๋„ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์œผ๋ฉด ๋กœ๋“œ + if (!rightData || (Array.isArray(rightData) && rightData.length === 0)) { + loadRightData(selectedLeftItem); + } + } else { + // ์ถ”๊ฐ€ ํƒญ: ํ•ด๋‹น ํƒญ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์œผ๋ฉด ๋กœ๋“œ + if (!tabsData[newTabIndex]) { + loadTabData(newTabIndex, selectedLeftItem); + } + } + } + }, + [selectedLeftItem, rightData, tabsData, loadRightData, loadTabData], ); // ์šฐ์ธก ํ•ญ๋ชฉ ํ™•์žฅ/์ถ•์†Œ ํ† ๊ธ€ @@ -2534,6 +2689,34 @@ export const SplitPanelLayoutComponent: React.FC className="flex flex-shrink-0 flex-col" > + {/* ๐Ÿ†• ํƒญ ๋ฐ” (์ถ”๊ฐ€ ํƒญ์ด ์žˆ์„ ๋•Œ๋งŒ ํ‘œ์‹œ) */} + {(componentConfig.rightPanel?.additionalTabs?.length || 0) > 0 && ( +
+ handleTabChange(Number(value))} + className="w-full" + > + + + {componentConfig.rightPanel?.title || "๊ธฐ๋ณธ"} + + {componentConfig.rightPanel?.additionalTabs?.map((tab, index) => ( + + {tab.label || `ํƒญ ${index + 1}`} + + ))} + + +
+ )} >
- {componentConfig.rightPanel?.title || "์šฐ์ธก ํŒจ๋„"} + {activeTabIndex === 0 + ? componentConfig.rightPanel?.title || "์šฐ์ธก ํŒจ๋„" + : componentConfig.rightPanel?.additionalTabs?.[activeTabIndex - 1]?.title || + componentConfig.rightPanel?.additionalTabs?.[activeTabIndex - 1]?.label || + "์šฐ์ธก ํŒจ๋„"} {!isDesignMode && (
- {componentConfig.rightPanel?.showAdd && ( - - )} + {/* ๐Ÿ†• ํ˜„์žฌ ํ™œ์„ฑ ํƒญ์— ๋”ฐ๋ฅธ ์ถ”๊ฐ€ ๋ฒ„ํŠผ */} + {activeTabIndex === 0 + ? componentConfig.rightPanel?.showAdd && ( + + ) + : componentConfig.rightPanel?.additionalTabs?.[activeTabIndex - 1]?.showAdd && ( + + )} {/* ์šฐ์ธก ํŒจ๋„ ์ˆ˜์ •/์‚ญ์ œ๋Š” ๊ฐ ์นด๋“œ์—์„œ ์ฒ˜๋ฆฌ */}
)} @@ -2575,20 +2770,231 @@ export const SplitPanelLayoutComponent: React.FC
)} - {/* ์šฐ์ธก ๋ฐ์ดํ„ฐ */} - {isLoadingRight ? ( - // ๋กœ๋”ฉ ์ค‘ -
-
- -

๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘...

-
-
- ) : rightData ? ( - // ์‹ค์ œ ๋ฐ์ดํ„ฐ ํ‘œ์‹œ - Array.isArray(rightData) ? ( - // ์กฐ์ธ ๋ชจ๋“œ: ์—ฌ๋Ÿฌ ๋ฐ์ดํ„ฐ๋ฅผ ํ…Œ์ด๋ธ”/๋ฆฌ์ŠคํŠธ๋กœ ํ‘œ์‹œ - (() => { + {/* ๐Ÿ†• ์ถ”๊ฐ€ ํƒญ ๋ฐ์ดํ„ฐ ๋ Œ๋”๋ง */} + {activeTabIndex > 0 ? ( + // ์ถ”๊ฐ€ ํƒญ ์ปจํ…์ธ  + (() => { + const currentTabConfig = componentConfig.rightPanel?.additionalTabs?.[activeTabIndex - 1]; + const currentTabData = tabsData[activeTabIndex] || []; + const isTabLoading = tabsLoading[activeTabIndex]; + + if (isTabLoading) { + return ( +
+
+ +

๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘...

+
+
+ ); + } + + if (!selectedLeftItem) { + return ( +
+

์ขŒ์ธก์—์„œ ํ•ญ๋ชฉ์„ ์„ ํƒํ•˜์„ธ์š”

+
+ ); + } + + if (currentTabData.length === 0) { + return ( +
+

๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค

+
+ ); + } + + // ํƒญ ๋ฐ์ดํ„ฐ ๋ Œ๋”๋ง (๋ชฉ๋ก/ํ…Œ์ด๋ธ” ๋ชจ๋“œ) + const isTableMode = currentTabConfig?.displayMode === "table"; + + if (isTableMode) { + // ํ…Œ์ด๋ธ” ๋ชจ๋“œ + const displayColumns = currentTabConfig?.columns || []; + const columnsToShow = + displayColumns.length > 0 + ? displayColumns.map((col) => ({ + ...col, + label: col.label || col.name, + })) + : Object.keys(currentTabData[0] || {}) + .filter(shouldShowField) + .slice(0, 8) + .map((key) => ({ name: key, label: key })); + + return ( +
+ + + + {columnsToShow.map((col: any) => ( + + ))} + {(currentTabConfig?.showEdit || currentTabConfig?.showDelete) && ( + + )} + + + + {currentTabData.map((item: any, idx: number) => ( + + {columnsToShow.map((col: any) => ( + + ))} + {(currentTabConfig?.showEdit || currentTabConfig?.showDelete) && ( + + )} + + ))} + +
+ {col.label} + ์ž‘์—…
+ {formatCellValue(col.name, item[col.name], {}, col.format)} + +
+ {currentTabConfig?.showEdit && ( + + )} + {currentTabConfig?.showDelete && ( + + )} +
+
+
+ ); + } else { + // ๋ชฉ๋ก (์นด๋“œ) ๋ชจ๋“œ + const displayColumns = currentTabConfig?.columns || []; + const summaryCount = currentTabConfig?.summaryColumnCount ?? 3; + const showLabel = currentTabConfig?.summaryShowLabel ?? true; + + return ( +
+ {currentTabData.map((item: any, idx: number) => { + const itemId = item.id || idx; + const isExpanded = expandedRightItems.has(itemId); + + // ํ‘œ์‹œํ•  ์ปฌ๋Ÿผ ๊ฒฐ์ • + const columnsToShow = + displayColumns.length > 0 + ? displayColumns + : Object.keys(item) + .filter(shouldShowField) + .slice(0, 8) + .map((key) => ({ name: key, label: key })); + + const summaryColumns = columnsToShow.slice(0, summaryCount); + const detailColumns = columnsToShow.slice(summaryCount); + + return ( +
+
toggleRightItemExpansion(itemId)} + > +
+
+ {summaryColumns.map((col: any) => ( +
+ {showLabel && ( + {col.label}: + )} + + {formatCellValue(col.name, item[col.name], {}, col.format)} + +
+ ))} +
+
+
+ {currentTabConfig?.showEdit && ( + + )} + {currentTabConfig?.showDelete && ( + + )} + {detailColumns.length > 0 && + (isExpanded ? ( + + ) : ( + + ))} +
+
+ {isExpanded && detailColumns.length > 0 && ( +
+
+ {detailColumns.map((col: any) => ( +
+ {col.label}: + {formatCellValue(col.name, item[col.name], {}, col.format)} +
+ ))} +
+
+ )} +
+ ); + })} +
+ ); + } + })() + ) : ( + /* ๊ธฐ๋ณธ ํƒญ (์šฐ์ธก ํŒจ๋„) ๋ฐ์ดํ„ฐ */ + <> + {isLoadingRight ? ( + // ๋กœ๋”ฉ ์ค‘ +
+
+ +

๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘...

+
+
+ ) : rightData ? ( + // ์‹ค์ œ ๋ฐ์ดํ„ฐ ํ‘œ์‹œ + Array.isArray(rightData) ? ( + // ์กฐ์ธ ๋ชจ๋“œ: ์—ฌ๋Ÿฌ ๋ฐ์ดํ„ฐ๋ฅผ ํ…Œ์ด๋ธ”/๋ฆฌ์ŠคํŠธ๋กœ ํ‘œ์‹œ + (() => { // ๊ฒ€์ƒ‰ ํ•„ํ„ฐ๋ง const filteredData = rightSearchQuery ? rightData.filter((item) => { @@ -3018,14 +3424,16 @@ export const SplitPanelLayoutComponent: React.FC
- ) : ( - // ์„ ํƒ ์—†์Œ -
-
-

์ขŒ์ธก์—์„œ ํ•ญ๋ชฉ์„ ์„ ํƒํ•˜์„ธ์š”

-

์„ ํƒํ•œ ํ•ญ๋ชฉ์˜ ์ƒ์„ธ ์ •๋ณด๊ฐ€ ์—ฌ๊ธฐ์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค

-
-
+ ) : ( + // ์„ ํƒ ์—†์Œ +
+
+

์ขŒ์ธก์—์„œ ํ•ญ๋ชฉ์„ ์„ ํƒํ•˜์„ธ์š”

+

์„ ํƒํ•œ ํ•ญ๋ชฉ์˜ ์ƒ์„ธ ์ •๋ณด๊ฐ€ ์—ฌ๊ธฐ์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค

+
+
+ )} + )} From 585febfb52a310031725444c74beafb0cee5f25f Mon Sep 17 00:00:00 2001 From: SeongHyun Kim Date: Mon, 19 Jan 2026 18:58:23 +0900 Subject: [PATCH 4/6] =?UTF-8?q?make:=20RepeaterFieldGroup=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20-=20=ED=95=98=EC=9C=84=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=A1=B0=ED=9A=8C=20=EC=97=B0?= =?UTF-8?q?=EB=8F=99=20=EB=B0=A9=EC=8B=9D=20=EA=B0=9C=EC=84=A0=20-=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=20=EC=A0=95=EC=9D=98=20=EB=A0=88=EB=B2=A8?= =?UTF-8?q?=EC=97=90=EC=84=9C=20subDataSource=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20-=20=ED=95=84=EB=93=9C=EB=B3=84=20?= =?UTF-8?q?=EC=88=A8=EA=B9=80(isHidden)=20=EC=98=B5=EC=85=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20-=20=EA=B8=B0=EC=A1=B4=20fieldMappings=20=EB=B0=A9?= =?UTF-8?q?=EC=8B=9D=20=EC=A0=9C=EA=B1=B0,=20=ED=95=84=EB=93=9C=EB=B3=84?= =?UTF-8?q?=20=EC=97=B0=EB=8F=99=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=5FrepeaterFieldsConfig=20=EB=A9=94=ED=83=80=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=EB=A1=9C=20=EC=84=A4=EC=A0=95=20=EC=A0=84=EB=8B=AC=20?= =?UTF-8?q?:=20"=EC=9D=B4=20=ED=95=84=EB=93=9C=EB=93=A4=EC=9D=98=20?= =?UTF-8?q?=ED=95=98=EC=9C=84=20=EC=A1=B0=ED=9A=8C=20=EA=B2=B0=EA=B3=BC?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EA=B0=92=20=EA=B0=80=EC=A0=B8=EC=99=80?= =?UTF-8?q?=EC=84=9C=20=EC=B6=94=EA=B0=80=EB=A1=9C=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=ED=95=B4=EC=A4=98"=EB=9D=BC=EB=8A=94=20=EC=A3=BC=EB=AC=B8?= =?UTF-8?q?=EC=84=9C=20=EC=97=AD=ED=95=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/webtypes/RepeaterInput.tsx | 18 +- .../webtypes/config/RepeaterConfigPanel.tsx | 185 ++++++++++-------- .../RepeaterFieldGroupRenderer.tsx | 18 ++ frontend/lib/utils/buttonActions.ts | 38 +++- frontend/types/repeater.ts | 14 +- 5 files changed, 182 insertions(+), 91 deletions(-) diff --git a/frontend/components/webtypes/RepeaterInput.tsx b/frontend/components/webtypes/RepeaterInput.tsx index 49751699..050b386b 100644 --- a/frontend/components/webtypes/RepeaterInput.tsx +++ b/frontend/components/webtypes/RepeaterInput.tsx @@ -907,6 +907,10 @@ export const RepeaterInput: React.FC = ({ const renderGridLayout = () => { // ํ•˜์œ„ ๋ฐ์ดํ„ฐ ์กฐํšŒ ์„ค์ •์ด ์žˆ์œผ๋ฉด ์—ฐ๊ฒฐ ์ปฌ๋Ÿผ ์ฐพ๊ธฐ const linkColumn = subDataLookup?.lookup?.linkColumn; + + // hidden์ด ์•„๋‹Œ ํ•„๋“œ๋งŒ ํ‘œ์‹œ + // isHidden์ด true์ด๊ฑฐ๋‚˜ displayMode๊ฐ€ hidden์ธ ํ•„๋“œ๋Š” ์ œ์™ธ (ํ•˜์œ„ ํ˜ธํ™˜์„ฑ ์œ ์ง€) + const visibleFields = fields.filter((f) => !f.isHidden && f.displayMode !== "hidden"); return (
@@ -919,7 +923,7 @@ export const RepeaterInput: React.FC = ({ {allowReorder && ( )} - {fields.map((field) => ( + {visibleFields.map((field) => ( {field.label} {field.required && *} @@ -958,8 +962,8 @@ export const RepeaterInput: React.FC = ({ )} - {/* ํ•„๋“œ๋“ค */} - {fields.map((field) => ( + {/* ํ•„๋“œ๋“ค (hidden ์ œ์™ธ) */} + {visibleFields.map((field) => ( {renderField(field, itemIndex, item[field.name])} @@ -987,7 +991,7 @@ export const RepeaterInput: React.FC = ({ @@ -1016,6 +1020,10 @@ export const RepeaterInput: React.FC = ({ const renderCardLayout = () => { // ํ•˜์œ„ ๋ฐ์ดํ„ฐ ์กฐํšŒ ์„ค์ •์ด ์žˆ์œผ๋ฉด ์—ฐ๊ฒฐ ์ปฌ๋Ÿผ ์ฐพ๊ธฐ const linkColumn = subDataLookup?.lookup?.linkColumn; + + // hidden์ด ์•„๋‹Œ ํ•„๋“œ๋งŒ ํ‘œ์‹œ + // isHidden์ด true์ด๊ฑฐ๋‚˜ displayMode๊ฐ€ hidden์ธ ํ•„๋“œ๋Š” ์ œ์™ธ (ํ•˜์œ„ ํ˜ธํ™˜์„ฑ ์œ ์ง€) + const visibleFields = fields.filter((f) => !f.isHidden && f.displayMode !== "hidden"); return ( <> @@ -1084,7 +1092,7 @@ export const RepeaterInput: React.FC = ({ {!isCollapsed && (
- {fields.map((field) => ( + {visibleFields.map((field) => (
); })}
- {config.targetTable && ( -

- * ์ €์žฅ ๋Œ€์ƒ: {config.targetTable} -

- )} +

+ * ์ €์žฅ ์„ค์ •์€ ํ•„๋“œ ์ •์˜์—์„œ "ํ•˜์œ„ ๋ฐ์ดํ„ฐ ์กฐํšŒ์—์„œ ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ"๋กœ ์„ค์ •ํ•˜์„ธ์š” +

)} @@ -1545,35 +1491,106 @@ export const RepeaterConfigPanel: React.FC = ({ {/* ์นดํ…Œ๊ณ ๋ฆฌ ํƒ€์ž…์ด ์•„๋‹ ๋•Œ๋งŒ ํ‘œ์‹œ ๋ชจ๋“œ ์„ ํƒ */} {field.type !== "category" && ( -
-
- - -
+
+
+
+ + +
-
-
- updateField(index, { required: checked as boolean })} - /> - +
+
+ updateField(index, { required: checked as boolean })} + /> + +
+ + {/* ์ˆจ๊น€ ์ฒดํฌ๋ฐ•์Šค */} +
+ updateField(index, { isHidden: checked as boolean })} + /> + +
+ + {/* ํ•˜์œ„ ๋ฐ์ดํ„ฐ ์กฐํšŒ์—์„œ ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ */} + {config.subDataLookup?.enabled && ( +
+
+ { + updateField(index, { + subDataSource: { + enabled: checked as boolean, + sourceColumn: field.subDataSource?.sourceColumn || "", + }, + }); + }} + /> + +
+ + {field.subDataSource?.enabled && ( +
+ + +

+ ์žฌ๊ณ  ์กฐํšŒ ๊ฒฐ๊ณผ์—์„œ ์ด ์ปฌ๋Ÿผ์˜ ๊ฐ’์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค +

+
+ )} +
+ )}
)} diff --git a/frontend/lib/registry/components/repeater-field-group/RepeaterFieldGroupRenderer.tsx b/frontend/lib/registry/components/repeater-field-group/RepeaterFieldGroupRenderer.tsx index 2aefb047..63e1cbb9 100644 --- a/frontend/lib/registry/components/repeater-field-group/RepeaterFieldGroupRenderer.tsx +++ b/frontend/lib/registry/components/repeater-field-group/RepeaterFieldGroupRenderer.tsx @@ -287,12 +287,18 @@ const RepeaterFieldGroupComponent: React.FC = (props) => if (onChange && items.length > 0) { // ๐Ÿ†• RepeaterFieldGroup์ด ๊ด€๋ฆฌํ•˜๋Š” ํ•„๋“œ ๋ชฉ๋ก ์ถ”์ถœ const repeaterFieldNames = (configRef.current.fields || []).map((f: any) => f.name); + // ๐Ÿ†• subDataSource ์„ค์ •์ด ์žˆ๋Š” ํ•„๋“œ ๋ชฉ๋ก (ํ•˜์œ„ ๋ฐ์ดํ„ฐ ์กฐํšŒ ์—ฐ๋™) + const fieldsConfig = (configRef.current.fields || []).map((f: any) => ({ + name: f.name, + subDataSource: f.subDataSource, + })); const dataWithMeta = items.map((item: any) => ({ ...item, _targetTable: targetTable, _originalItemIds: itemIds, // ๐Ÿ†• ์›๋ณธ ID ๋ชฉ๋ก๋„ ํ•จ๊ป˜ ์ „๋‹ฌ _existingRecord: !!item.id, // ๐Ÿ†• ๊ธฐ์กด ๋ ˆ์ฝ”๋“œ ํ”Œ๋ž˜๊ทธ (id๊ฐ€ ์žˆ์œผ๋ฉด ๊ธฐ์กด ๋ ˆ์ฝ”๋“œ) _repeaterFields: repeaterFieldNames, // ๐Ÿ†• ํ’ˆ๋ชฉ ๊ณ ์œ  ํ•„๋“œ ๋ชฉ๋ก + _repeaterFieldsConfig: fieldsConfig, // ๐Ÿ†• ํ•„๋“œ ์„ค์ • (subDataSource ๋“ฑ) })); onChange(dataWithMeta); } @@ -393,11 +399,17 @@ const RepeaterFieldGroupComponent: React.FC = (props) => if (items.length > 0) { // ๐Ÿ†• RepeaterFieldGroup์ด ๊ด€๋ฆฌํ•˜๋Š” ํ•„๋“œ ๋ชฉ๋ก ์ถ”์ถœ const repeaterFieldNames = (configRef.current.fields || []).map((f: any) => f.name); + // ๐Ÿ†• subDataSource ์„ค์ •์ด ์žˆ๋Š” ํ•„๋“œ ๋ชฉ๋ก + const fieldsConfig = (configRef.current.fields || []).map((f: any) => ({ + name: f.name, + subDataSource: f.subDataSource, + })); const dataWithMeta = items.map((item: any) => ({ ...item, _targetTable: effectiveTargetTable, _existingRecord: !!item.id, _repeaterFields: repeaterFieldNames, // ๐Ÿ†• ํ’ˆ๋ชฉ ๊ณ ์œ  ํ•„๋“œ ๋ชฉ๋ก + _repeaterFieldsConfig: fieldsConfig, // ๐Ÿ†• ํ•„๋“œ ์„ค์ • (subDataSource ๋“ฑ) })); onChange(dataWithMeta); } else { @@ -681,6 +693,11 @@ const RepeaterFieldGroupComponent: React.FC = (props) => (newValue: any[]) => { // ๐Ÿ†• RepeaterFieldGroup์ด ๊ด€๋ฆฌํ•˜๋Š” ํ•„๋“œ ๋ชฉ๋ก ์ถ”์ถœ const repeaterFieldNames = (configRef.current.fields || []).map((f: any) => f.name); + // ๐Ÿ†• subDataSource ์„ค์ •์ด ์žˆ๋Š” ํ•„๋“œ ๋ชฉ๋ก + const fieldsConfig = (configRef.current.fields || []).map((f: any) => ({ + name: f.name, + subDataSource: f.subDataSource, + })); // ๐Ÿ†• ๋ชจ๋“  ํ•ญ๋ชฉ์— ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ถ”๊ฐ€ let valueWithMeta = newValue.map((item: any) => ({ @@ -688,6 +705,7 @@ const RepeaterFieldGroupComponent: React.FC = (props) => _targetTable: effectiveTargetTable || targetTable, _existingRecord: !!item.id, _repeaterFields: repeaterFieldNames, // ๐Ÿ†• ํ’ˆ๋ชฉ ๊ณ ์œ  ํ•„๋“œ ๋ชฉ๋ก + _repeaterFieldsConfig: fieldsConfig, // ๐Ÿ†• ํ•„๋“œ ์„ค์ • (subDataSource ๋“ฑ) })); // ๐Ÿ†• ๋ถ„ํ•  ํŒจ๋„์—์„œ ์šฐ์ธก์ธ ๊ฒฝ์šฐ, FK ๊ฐ’ ์ถ”๊ฐ€ diff --git a/frontend/lib/utils/buttonActions.ts b/frontend/lib/utils/buttonActions.ts index debf58b2..b0ac2ba9 100644 --- a/frontend/lib/utils/buttonActions.ts +++ b/frontend/lib/utils/buttonActions.ts @@ -803,7 +803,7 @@ export class ButtonActionExecutor { for (const item of parsedData) { // ๋ฉ”ํƒ€ ํ•„๋“œ ์ œ๊ฑฐ - const { _targetTable, _isNewItem, _existingRecord, _originalItemIds, _deletedItemIds, _repeaterFields, ...itemData } = item; + const { _targetTable, _isNewItem, _existingRecord, _originalItemIds, _deletedItemIds, _repeaterFields, _subDataSelection, _subDataMaxValue, ...itemData } = item; // ๐Ÿ”ง ํ’ˆ๋ชฉ ๊ณ ์œ  ํ•„๋“œ๋งŒ ์ถ”์ถœ (RepeaterFieldGroup ์„ค์ • ๊ธฐ๋ฐ˜) const itemOnlyData: Record = {}; @@ -812,6 +812,42 @@ export class ButtonActionExecutor { itemOnlyData[field] = itemData[field]; } }); + + // ๐Ÿ†• ํ•˜์œ„ ๋ฐ์ดํ„ฐ ์„ ํƒ์—์„œ ๊ฐ’ ์ถ”์ถœ (subDataSource ์„ค์ • ๊ธฐ๋ฐ˜) + // ํ•„๋“œ ์ •์˜์—์„œ subDataSource.enabled๊ฐ€ true์ด๊ณ  sourceColumn์ด ์„ค์ •๋œ ํ•„๋“œ๋งŒ ์ฒ˜๋ฆฌ + if (_subDataSelection && typeof _subDataSelection === 'object') { + // _repeaterFieldsConfig์—์„œ subDataSource ์„ค์ • ํ™•์ธ + const fieldsConfig = item._repeaterFieldsConfig as Array<{ + name: string; + subDataSource?: { enabled: boolean; sourceColumn: string }; + }> | undefined; + + if (fieldsConfig && Array.isArray(fieldsConfig)) { + fieldsConfig.forEach((fieldConfig) => { + if (fieldConfig.subDataSource?.enabled && fieldConfig.subDataSource?.sourceColumn) { + const targetField = fieldConfig.name; // ํ•„๋“œ๋ช… = ์ €์žฅํ•  ์ปฌ๋Ÿผ๋ช… + const sourceColumn = fieldConfig.subDataSource.sourceColumn; + const sourceValue = _subDataSelection[sourceColumn]; + + if (sourceValue !== undefined && sourceValue !== null) { + itemOnlyData[targetField] = sourceValue; + console.log(`๐Ÿ“‹ [handleSave] ํ•˜์œ„ ๋ฐ์ดํ„ฐ ๊ฐ’ ๋งคํ•‘: ${sourceColumn} โ†’ ${targetField} = ${sourceValue}`); + } + } + }); + } else { + // ํ•˜์œ„ ํ˜ธํ™˜์„ฑ: fieldsConfig๊ฐ€ ์—†์œผ๋ฉด ๊ธฐ์กด ๋ฐฉ์‹ ์‚ฌ์šฉ + Object.keys(_subDataSelection).forEach((subDataKey) => { + if (itemOnlyData[subDataKey] === undefined || itemOnlyData[subDataKey] === null || itemOnlyData[subDataKey] === '') { + const subDataValue = _subDataSelection[subDataKey]; + if (subDataValue !== undefined && subDataValue !== null) { + itemOnlyData[subDataKey] = subDataValue; + console.log(`๐Ÿ“‹ [handleSave] ํ•˜์œ„ ๋ฐ์ดํ„ฐ ์„ ํƒ ๊ฐ’ ์ถ”๊ฐ€ (๋ ˆ๊ฑฐ์‹œ): ${subDataKey} = ${subDataValue}`); + } + } + }); + } + } // ๐Ÿ”ง ๋งˆ์Šคํ„ฐ ์ •๋ณด + ํ’ˆ๋ชฉ ๊ณ ์œ  ์ •๋ณด ๋ณ‘ํ•ฉ // masterFields: ์ƒ๋‹จ ํผ์—์„œ ์ˆ˜์ •ํ•œ ์ตœ์‹  ๋งˆ์Šคํ„ฐ ์ •๋ณด diff --git a/frontend/types/repeater.ts b/frontend/types/repeater.ts index bbdc8727..c7f0fa98 100644 --- a/frontend/types/repeater.ts +++ b/frontend/types/repeater.ts @@ -43,9 +43,19 @@ export interface CalculationFormula { * ํ•„๋“œ ํ‘œ์‹œ ๋ชจ๋“œ * - input: ์ž…๋ ฅ ํ•„๋“œ๋กœ ํ‘œ์‹œ (ํŽธ์ง‘ ๊ฐ€๋Šฅ) * - readonly: ์ฝ๊ธฐ ์ „์šฉ ํ…์ŠคํŠธ๋กœ ํ‘œ์‹œ + * - hidden: ์ˆจ๊น€ (UI์— ํ‘œ์‹œ๋˜์ง€ ์•Š์ง€๋งŒ ๋ฐ์ดํ„ฐ์— ํฌํ•จ๋จ) * - (์นดํ…Œ๊ณ ๋ฆฌ ํƒ€์ž…์€ ์ž๋™์œผ๋กœ ๋ฐฐ์ง€๋กœ ํ‘œ์‹œ๋จ) */ -export type RepeaterFieldDisplayMode = "input" | "readonly"; +export type RepeaterFieldDisplayMode = "input" | "readonly" | "hidden"; + +/** + * ํ•˜์œ„ ๋ฐ์ดํ„ฐ ์กฐํšŒ ์†Œ์Šค ์„ค์ • + * ํ•„๋“œ ๊ฐ’์„ ํ•˜์œ„ ๋ฐ์ดํ„ฐ ์กฐํšŒ ๊ฒฐ๊ณผ์—์„œ ๊ฐ€์ ธ์˜ฌ ๋•Œ ์‚ฌ์šฉ + */ +export interface SubDataSourceConfig { + enabled: boolean; // ํ™œ์„ฑํ™” ์—ฌ๋ถ€ + sourceColumn: string; // ํ•˜์œ„ ๋ฐ์ดํ„ฐ ์กฐํšŒ ํ…Œ์ด๋ธ”์˜ ์†Œ์Šค ์ปฌ๋Ÿผ (์˜ˆ: lot_number) +} /** * ๋ฐ˜๋ณต ๊ทธ๋ฃน ๋‚ด ๊ฐœ๋ณ„ ํ•„๋“œ ์ •์˜ @@ -60,6 +70,8 @@ export interface RepeaterFieldDefinition { options?: Array<{ label: string; value: string }>; // select์šฉ width?: string; // ํ•„๋“œ ๋„ˆ๋น„ (์˜ˆ: "200px", "50%") displayMode?: RepeaterFieldDisplayMode; // ํ‘œ์‹œ ๋ชจ๋“œ: input(์ž…๋ ฅ), readonly(์ฝ๊ธฐ์ „์šฉ) + isHidden?: boolean; // ์ˆจ๊น€ ์—ฌ๋ถ€ (true๋ฉด ํ…Œ์ด๋ธ”์— ํ‘œ์‹œ ์•ˆ ํ•จ, ๋ฐ์ดํ„ฐ๋Š” ์ €์žฅ) + subDataSource?: SubDataSourceConfig; // ํ•˜์œ„ ๋ฐ์ดํ„ฐ ์กฐํšŒ์—์„œ ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ ์„ค์ • categoryCode?: string; // category ํƒ€์ž…์ผ ๋•Œ ์‚ฌ์šฉํ•  ์นดํ…Œ๊ณ ๋ฆฌ ์ฝ”๋“œ formula?: CalculationFormula; // ๊ณ„์‚ฐ์‹ (type์ด "calculated"์ผ ๋•Œ ์‚ฌ์šฉ) numberFormat?: { From c31b0540aa2bfcb9e170c2f0512b9d6ffe5b7102 Mon Sep 17 00:00:00 2001 From: SeongHyun Kim Date: Tue, 20 Jan 2026 16:08:38 +0900 Subject: [PATCH 5/6] =?UTF-8?q?fix:=20=EB=B6=84=ED=95=A0=ED=8C=A8=EB=84=90?= =?UTF-8?q?=20=EC=97=B0=EA=B2=B0=20=ED=95=84=ED=84=B0=EC=97=90=20operator?= =?UTF-8?q?=20equals=20=EB=88=84=EB=9D=BD=EC=9C=BC=EB=A1=9C=20=EC=9D=B8?= =?UTF-8?q?=ED=95=9C=20=EC=A1=B0=ED=9A=8C=20=EC=8B=A4=ED=8C=A8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20:=20=EC=9A=B0=EC=B8=A1=20=ED=8C=A8=EB=84=90?= =?UTF-8?q?=EC=97=90=20=EC=97=B0=EA=B4=80=20=EB=8D=B0=EC=9D=B4=ED=84=B0(?= =?UTF-8?q?=EB=B6=80=EC=84=9C=EC=9D=B8=EC=9B=90)=EA=B0=80=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=EB=90=98=EC=A7=80=20=EC=95=8A=EB=8D=98=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=20=EC=88=98=EC=A0=95=20-=20=EC=9E=AC=EA=B3=A0?= =?UTF-8?q?=EC=9D=B4=EB=A0=A5=EC=97=90=EC=84=9C=20=ED=92=88=EB=B2=88?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=B6=9C=EB=A0=A5=EC=95=88=EB=90=98?= =?UTF-8?q?=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0=20:=20?= =?UTF-8?q?=EC=97=94=ED=8B=B0=ED=8B=B0=20=EC=A1=B0=EC=9D=B8=20=EC=BF=BC?= =?UTF-8?q?=EB=A6=AC=20=EC=83=9D=EC=84=B1=20=EC=8B=9C=20=EB=8F=99=EC=9D=BC?= =?UTF-8?q?=ED=95=9C=20=EC=BB=AC=EB=9F=BC=20=EB=B3=84=EC=B9=AD=EC=9D=B4=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=20=EC=83=9D=EC=84=B1=EB=90=98=EC=96=B4=20SQL?= =?UTF-8?q?=20=EC=97=90=EB=9F=AC=20=EB=B0=9C=EC=83=9D=ED=95=98=EB=8D=98=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/services/entityJoinService.ts | 36 +++++++++++++++---- .../SplitPanelLayoutComponent.tsx | 19 ++++++++-- 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/backend-node/src/services/entityJoinService.ts b/backend-node/src/services/entityJoinService.ts index 25d96927..86762b64 100644 --- a/backend-node/src/services/entityJoinService.ts +++ b/backend-node/src/services/entityJoinService.ts @@ -334,6 +334,10 @@ export class EntityJoinService { ); }); + // ๐Ÿ”ง _label ๋ณ„์นญ ์ค‘๋ณต ๋ฐฉ์ง€๋ฅผ ์œ„ํ•œ Set + // ๊ฐ™์€ sourceColumn์—์„œ ์—ฌ๋Ÿฌ ์กฐ์ธ ์„ค์ •์ด ์žˆ์„ ๋•Œ _label์€ ์ฒซ ๋ฒˆ์งธ๋งŒ ์ƒ์„ฑ + const generatedLabelAliases = new Set(); + const joinColumns = joinConfigs .map((config) => { const aliasKey = `${config.referenceTable}:${config.sourceColumn}`; @@ -368,16 +372,26 @@ export class EntityJoinService { // _label ํ•„๋“œ๋„ ํ•จ๊ป˜ SELECT (ํ”„๋ก ํŠธ์—”๋“œ getColumnUniqueValues์šฉ) // sourceColumn_label ํ˜•์‹์œผ๋กœ ์ถ”๊ฐ€ - resultColumns.push( - `COALESCE(${alias}.${col}::TEXT, '') AS ${config.sourceColumn}_label` - ); + // ๐Ÿ”ง ์ค‘๋ณต ๋ฐฉ์ง€: ๊ฐ™์€ sourceColumn์—์„œ _label์€ ์ฒซ ๋ฒˆ์งธ๋งŒ ์ƒ์„ฑ + const labelAlias = `${config.sourceColumn}_label`; + if (!generatedLabelAliases.has(labelAlias)) { + resultColumns.push( + `COALESCE(${alias}.${col}::TEXT, '') AS ${labelAlias}` + ); + generatedLabelAliases.add(labelAlias); + } // ๐Ÿ†• referenceColumn (PK)๋„ ํ•ญ์ƒ SELECT (parentDataMapping์šฉ) // ์˜ˆ: customer_code, item_number ๋“ฑ // col๊ณผ ๋™์ผํ•ด๋„ ๋ณ„๋„์˜ alias๋กœ ์ถ”๊ฐ€ (customer_code as customer_code) - resultColumns.push( - `COALESCE(${alias}.${config.referenceColumn}::TEXT, '') AS ${config.referenceColumn}` - ); + // ๐Ÿ”ง ์ค‘๋ณต ๋ฐฉ์ง€: referenceColumn๋„ ํ•œ ๋ฒˆ๋งŒ ์ถ”๊ฐ€ + const refColAlias = config.referenceColumn; + if (!generatedLabelAliases.has(refColAlias)) { + resultColumns.push( + `COALESCE(${alias}.${config.referenceColumn}::TEXT, '') AS ${refColAlias}` + ); + generatedLabelAliases.add(refColAlias); + } } else { resultColumns.push( `COALESCE(main.${col}::TEXT, '') AS ${config.aliasColumn}` @@ -392,6 +406,11 @@ export class EntityJoinService { const individualAlias = `${config.sourceColumn}_${col}`; + // ๐Ÿ”ง ์ค‘๋ณต ๋ฐฉ์ง€: ๊ฐ™์€ alias๊ฐ€ ์ด๋ฏธ ์ƒ์„ฑ๋˜์—ˆ์œผ๋ฉด ์Šคํ‚ต + if (generatedLabelAliases.has(individualAlias)) { + return; + } + if (isJoinTableColumn) { // ์กฐ์ธ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ์€ ์กฐ์ธ ๋ณ„์นญ ์‚ฌ์šฉ resultColumns.push( @@ -403,6 +422,7 @@ export class EntityJoinService { `COALESCE(main.${col}::TEXT, '') AS ${individualAlias}` ); } + generatedLabelAliases.add(individualAlias); }); // ๐Ÿ†• referenceColumn (PK)๋„ ํ•จ๊ป˜ SELECT (parentDataMapping์šฉ) @@ -410,11 +430,13 @@ export class EntityJoinService { config.referenceTable && config.referenceTable !== tableName; if ( isJoinTableColumn && - !displayColumns.includes(config.referenceColumn) + !displayColumns.includes(config.referenceColumn) && + !generatedLabelAliases.has(config.referenceColumn) // ๐Ÿ”ง ์ค‘๋ณต ๋ฐฉ์ง€ ) { resultColumns.push( `COALESCE(${alias}.${config.referenceColumn}::TEXT, '') AS ${config.referenceColumn}` ); + generatedLabelAliases.add(config.referenceColumn); } } diff --git a/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx b/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx index 869d2c3c..ab387348 100644 --- a/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx +++ b/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx @@ -924,10 +924,15 @@ export const SplitPanelLayoutComponent: React.FC const { entityJoinApi } = await import("@/lib/api/entityJoin"); // ๋ณตํ•ฉํ‚ค ์กฐ๊ฑด ์ƒ์„ฑ + // ๐Ÿ”ง entity ํƒ€์ž… ์ปฌ๋Ÿผ์€ ์ฝ”๋“œ ๊ฐ’์œผ๋กœ ์ •ํ™•ํžˆ ๋งค์นญํ•ด์•ผ ํ•˜๋ฏ€๋กœ operator: 'equals' ์‚ฌ์šฉ const searchConditions: Record = {}; keys.forEach((key) => { if (key.leftColumn && key.rightColumn && leftItem[key.leftColumn] !== undefined) { - searchConditions[key.rightColumn] = leftItem[key.leftColumn]; + // ์—ฐ๊ฒฐ ํ•„ํ„ฐ๋Š” ์ •ํ™•ํ•œ ๊ฐ’ ๋งค์นญ์ด ํ•„์š”ํ•˜๋ฏ€๋กœ equals ์—ฐ์‚ฐ์ž ์‚ฌ์šฉ + searchConditions[key.rightColumn] = { + value: leftItem[key.leftColumn], + operator: "equals", + }; } }); @@ -1033,16 +1038,24 @@ export const SplitPanelLayoutComponent: React.FC if (keys && keys.length > 0) { // ๋ณตํ•ฉํ‚ค + // ๐Ÿ”ง entity ํƒ€์ž… ์ปฌ๋Ÿผ์€ ์ฝ”๋“œ ๊ฐ’์œผ๋กœ ์ •ํ™•ํžˆ ๋งค์นญํ•ด์•ผ ํ•˜๋ฏ€๋กœ operator: 'equals' ์‚ฌ์šฉ keys.forEach((key) => { if (key.leftColumn && key.rightColumn && leftItem[key.leftColumn] !== undefined) { - searchConditions[key.rightColumn] = leftItem[key.leftColumn]; + searchConditions[key.rightColumn] = { + value: leftItem[key.leftColumn], + operator: "equals", + }; } }); } else { // ๋‹จ์ผํ‚ค + // ๐Ÿ”ง entity ํƒ€์ž… ์ปฌ๋Ÿผ์€ ์ฝ”๋“œ ๊ฐ’์œผ๋กœ ์ •ํ™•ํžˆ ๋งค์นญํ•ด์•ผ ํ•˜๋ฏ€๋กœ operator: 'equals' ์‚ฌ์šฉ const leftValue = leftItem[leftColumn]; if (leftValue !== undefined) { - searchConditions[rightColumn] = leftValue; + searchConditions[rightColumn] = { + value: leftValue, + operator: "equals", + }; } } From 0907d318ebd096b658b5a56ab407f7d8ff7ddcb5 Mon Sep 17 00:00:00 2001 From: SeongHyun Kim Date: Tue, 20 Jan 2026 17:05:36 +0900 Subject: [PATCH 6/6] =?UTF-8?q?fix:=20=EC=88=98=EC=A0=95=20=EB=AA=A8?= =?UTF-8?q?=EB=93=9C=EC=97=90=EC=84=9C=20=EC=B1=84=EB=B2=88=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=9E=AC=ED=95=A0=EB=8B=B9=20=EB=B0=A9=EC=A7=80=20?= =?UTF-8?q?handleSave():=20formData.id=20=EC=B2=B4=ED=81=AC=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=AA=A8=EB=93=9C=20=ED=8C=90=EB=B3=84,?= =?UTF-8?q?=20=EA=B8=B0=EC=A1=B4=20=EB=B2=88=ED=98=B8=20=EC=9C=A0=EC=A7=80?= =?UTF-8?q?=20handleUniversalFormModalTableSectionSave():=20formData.id=20?= =?UTF-8?q?=EB=B0=8F=20originalGroupedData=20=EC=B2=B4=ED=81=AC=EB=A1=9C?= =?UTF-8?q?=20=EC=88=98=EC=A0=95=20=EB=AA=A8=EB=93=9C=20=ED=8C=90=EB=B3=84?= =?UTF-8?q?=20=EC=8B=A0=EA=B7=9C=20=EB=93=B1=EB=A1=9D=20=EC=8B=9C=EC=97=90?= =?UTF-8?q?=EB=A7=8C=20allocateCode=20=ED=98=B8=EC=B6=9C=ED=95=98=EC=97=AC?= =?UTF-8?q?=20=EC=B1=84=EB=B2=88=20=EC=BD=94=EB=93=9C=20=ED=95=A0=EB=8B=B9?= =?UTF-8?q?=20=EC=9E=85=EA=B3=A0=EA=B4=80=EB=A6=AC=20=ED=99=94=EB=A9=B4?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=88=98=EC=A0=95=20=EC=8B=9C=20=EC=9E=85?= =?UTF-8?q?=EA=B3=A0=EB=B2=88=ED=98=B8=20=EC=A6=9D=EA=B0=80=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/lib/utils/buttonActions.ts | 64 +++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 18 deletions(-) diff --git a/frontend/lib/utils/buttonActions.ts b/frontend/lib/utils/buttonActions.ts index b0ac2ba9..af342a1f 100644 --- a/frontend/lib/utils/buttonActions.ts +++ b/frontend/lib/utils/buttonActions.ts @@ -707,12 +707,19 @@ export class ButtonActionExecutor { if (repeaterJsonKeys.length > 0) { console.log("๐Ÿ”„ [handleSave] RepeaterFieldGroup JSON ๋ฌธ์ž์—ด ๊ฐ์ง€:", repeaterJsonKeys); - + // ๐ŸŽฏ ์ฑ„๋ฒˆ ๊ทœ์น™ ํ• ๋‹น ์ฒ˜๋ฆฌ (RepeaterFieldGroup ์ €์žฅ ์ „์— ์‹คํ–‰) - console.log("๐Ÿ” [handleSave-RepeaterFieldGroup] ์ฑ„๋ฒˆ ๊ทœ์น™ ํ• ๋‹น ์ฒดํฌ ์‹œ์ž‘"); - + // ๐Ÿ”ง ์ˆ˜์ • ๋ชจ๋“œ ์ฒดํฌ: formData.id๊ฐ€ ์กด์žฌํ•˜๋ฉด UPDATE ๋ชจ๋“œ์ด๋ฏ€๋กœ ์ฑ„๋ฒˆ ์ฝ”๋“œ ์žฌํ• ๋‹น ๊ธˆ์ง€ + const isEditModeRepeater = + context.formData.id !== undefined && context.formData.id !== null && context.formData.id !== ""; + + console.log("๐Ÿ” [handleSave-RepeaterFieldGroup] ์ฑ„๋ฒˆ ๊ทœ์น™ ํ• ๋‹น ์ฒดํฌ ์‹œ์ž‘", { + isEditMode: isEditModeRepeater, + formDataId: context.formData.id, + }); + const fieldsWithNumberingRepeater: Record = {}; - + // formData์—์„œ ์ฑ„๋ฒˆ ๊ทœ์น™์ด ์„ค์ •๋œ ํ•„๋“œ ์ฐพ๊ธฐ for (const [key, value] of Object.entries(context.formData)) { if (key.endsWith("_numberingRuleId") && value) { @@ -721,22 +728,27 @@ export class ButtonActionExecutor { console.log(`๐ŸŽฏ [handleSave-RepeaterFieldGroup] ์ฑ„๋ฒˆ ํ•„๋“œ ๋ฐœ๊ฒฌ: ${fieldName} โ†’ ๊ทœ์น™ ${value}`); } } - + console.log("๐Ÿ“‹ [handleSave-RepeaterFieldGroup] ์ฑ„๋ฒˆ ๊ทœ์น™์ด ์„ค์ •๋œ ํ•„๋“œ:", fieldsWithNumberingRepeater); - - // ์ฑ„๋ฒˆ ๊ทœ์น™์ด ์žˆ๋Š” ํ•„๋“œ์— ๋Œ€ํ•ด allocateCode ํ˜ธ์ถœ - if (Object.keys(fieldsWithNumberingRepeater).length > 0) { - console.log("๐ŸŽฏ [handleSave-RepeaterFieldGroup] ์ฑ„๋ฒˆ ๊ทœ์น™ ํ• ๋‹น ์‹œ์ž‘ (allocateCode ํ˜ธ์ถœ)"); + + // ๐Ÿ”ง ์ˆ˜์ • ๋ชจ๋“œ์—์„œ๋Š” ์ฑ„๋ฒˆ ์ฝ”๋“œ ํ• ๋‹น ๊ฑด๋„ˆ๋›ฐ๊ธฐ (๊ธฐ์กด ๋ฒˆํ˜ธ ์œ ์ง€) + // ์‹ ๊ทœ ๋“ฑ๋ก ๋ชจ๋“œ์—์„œ๋งŒ allocateCode ํ˜ธ์ถœํ•˜์—ฌ ์ƒˆ ๋ฒˆํ˜ธ ํ• ๋‹น + if (Object.keys(fieldsWithNumberingRepeater).length > 0 && !isEditModeRepeater) { + console.log( + "๐ŸŽฏ [handleSave-RepeaterFieldGroup] ์‹ ๊ทœ ๋“ฑ๋ก ๋ชจ๋“œ - ์ฑ„๋ฒˆ ๊ทœ์น™ ํ• ๋‹น ์‹œ์ž‘ (allocateCode ํ˜ธ์ถœ)", + ); const { allocateNumberingCode } = await import("@/lib/api/numberingRule"); - + for (const [fieldName, ruleId] of Object.entries(fieldsWithNumberingRepeater)) { try { console.log(`๐Ÿ”„ [handleSave-RepeaterFieldGroup] ${fieldName} ํ•„๋“œ์— ๋Œ€ํ•ด allocateCode ํ˜ธ์ถœ: ${ruleId}`); const allocateResult = await allocateNumberingCode(ruleId); - + if (allocateResult.success && allocateResult.data?.generatedCode) { const newCode = allocateResult.data.generatedCode; - console.log(`โœ… [handleSave-RepeaterFieldGroup] ${fieldName} ์ƒˆ ์ฝ”๋“œ ํ• ๋‹น: ${context.formData[fieldName]} โ†’ ${newCode}`); + console.log( + `โœ… [handleSave-RepeaterFieldGroup] ${fieldName} ์ƒˆ ์ฝ”๋“œ ํ• ๋‹น: ${context.formData[fieldName]} โ†’ ${newCode}`, + ); context.formData[fieldName] = newCode; } else { console.warn(`โš ๏ธ [handleSave-RepeaterFieldGroup] ${fieldName} ์ฝ”๋“œ ํ• ๋‹น ์‹คํŒจ:`, allocateResult.error); @@ -745,9 +757,11 @@ export class ButtonActionExecutor { console.error(`โŒ [handleSave-RepeaterFieldGroup] ${fieldName} ์ฝ”๋“œ ํ• ๋‹น ์˜ค๋ฅ˜:`, allocateError); } } + } else if (isEditModeRepeater) { + console.log("โญ๏ธ [handleSave-RepeaterFieldGroup] ์ˆ˜์ • ๋ชจ๋“œ - ์ฑ„๋ฒˆ ์ฝ”๋“œ ํ• ๋‹น ๊ฑด๋„ˆ๋œ€ (๊ธฐ์กด ๋ฒˆํ˜ธ ์œ ์ง€)"); } - - console.log("โœ… [handleSave-RepeaterFieldGroup] ์ฑ„๋ฒˆ ๊ทœ์น™ ํ• ๋‹น ์™„๋ฃŒ"); + + console.log("โœ… [handleSave-RepeaterFieldGroup] ์ฑ„๋ฒˆ ๊ทœ์น™ ํ• ๋‹น ์ฒ˜๋ฆฌ ์™„๋ฃŒ"); // ๐Ÿ†• ์ƒ๋‹จ ํผ ๋ฐ์ดํ„ฐ(๋งˆ์Šคํ„ฐ ์ •๋ณด) ์ถ”์ถœ // RepeaterFieldGroup JSON๊ณผ ์ปดํฌ๋„ŒํŠธ ํ‚ค๋ฅผ ์ œ์™ธํ•œ ๋‚˜๋จธ์ง€๊ฐ€ ๋งˆ์Šคํ„ฐ ์ •๋ณด @@ -1951,7 +1965,16 @@ export class ButtonActionExecutor { } // ๐ŸŽฏ ์ฑ„๋ฒˆ ๊ทœ์น™ ํ• ๋‹น ์ฒ˜๋ฆฌ (์ €์žฅ ์‹œ์ ์— ์‹ค์ œ ์ˆœ๋ฒˆ ์ฆ๊ฐ€) - console.log("๐Ÿ” [handleUniversalFormModalTableSectionSave] ์ฑ„๋ฒˆ ๊ทœ์น™ ํ• ๋‹น ์ฒดํฌ ์‹œ์ž‘"); + // ๐Ÿ”ง ์ˆ˜์ • ๋ชจ๋“œ ์ฒดํฌ: formData.id ๋˜๋Š” originalGroupedData๊ฐ€ ์žˆ์œผ๋ฉด UPDATE ๋ชจ๋“œ + const isEditModeUniversal = + (formData.id !== undefined && formData.id !== null && formData.id !== "") || + originalGroupedData.length > 0; + + console.log("๐Ÿ” [handleUniversalFormModalTableSectionSave] ์ฑ„๋ฒˆ ๊ทœ์น™ ํ• ๋‹น ์ฒดํฌ ์‹œ์ž‘", { + isEditMode: isEditModeUniversal, + formDataId: formData.id, + originalGroupedDataCount: originalGroupedData.length, + }); const fieldsWithNumbering: Record = {}; @@ -1977,9 +2000,12 @@ export class ButtonActionExecutor { console.log("๐Ÿ“‹ [handleUniversalFormModalTableSectionSave] ์ฑ„๋ฒˆ ๊ทœ์น™์ด ์„ค์ •๋œ ํ•„๋“œ:", fieldsWithNumbering); - // ๐Ÿ”ฅ ์ €์žฅ ์‹œ์ ์— allocateCode ํ˜ธ์ถœํ•˜์—ฌ ์‹ค์ œ ์ˆœ๋ฒˆ ์ฆ๊ฐ€ - if (Object.keys(fieldsWithNumbering).length > 0) { - console.log("๐ŸŽฏ [handleUniversalFormModalTableSectionSave] ์ฑ„๋ฒˆ ๊ทœ์น™ ํ• ๋‹น ์‹œ์ž‘ (allocateCode ํ˜ธ์ถœ)"); + // ๐Ÿ”ง ์ˆ˜์ • ๋ชจ๋“œ์—์„œ๋Š” ์ฑ„๋ฒˆ ์ฝ”๋“œ ํ• ๋‹น ๊ฑด๋„ˆ๋›ฐ๊ธฐ (๊ธฐ์กด ๋ฒˆํ˜ธ ์œ ์ง€) + // ์‹ ๊ทœ ๋“ฑ๋ก ๋ชจ๋“œ์—์„œ๋งŒ allocateCode ํ˜ธ์ถœํ•˜์—ฌ ์ƒˆ ๋ฒˆํ˜ธ ํ• ๋‹น + if (Object.keys(fieldsWithNumbering).length > 0 && !isEditModeUniversal) { + console.log( + "๐ŸŽฏ [handleUniversalFormModalTableSectionSave] ์‹ ๊ทœ ๋“ฑ๋ก ๋ชจ๋“œ - ์ฑ„๋ฒˆ ๊ทœ์น™ ํ• ๋‹น ์‹œ์ž‘ (allocateCode ํ˜ธ์ถœ)", + ); const { allocateNumberingCode } = await import("@/lib/api/numberingRule"); for (const [fieldName, ruleId] of Object.entries(fieldsWithNumbering)) { @@ -2006,6 +2032,8 @@ export class ButtonActionExecutor { // ์˜ค๋ฅ˜ ์‹œ ๊ธฐ์กด ๊ฐ’ ์œ ์ง€ } } + } else if (isEditModeUniversal) { + console.log("โญ๏ธ [handleUniversalFormModalTableSectionSave] ์ˆ˜์ • ๋ชจ๋“œ - ์ฑ„๋ฒˆ ์ฝ”๋“œ ํ• ๋‹น ๊ฑด๋„ˆ๋œ€ (๊ธฐ์กด ๋ฒˆํ˜ธ ์œ ์ง€)"); } console.log("โœ… [handleUniversalFormModalTableSectionSave] ์ฑ„๋ฒˆ ๊ทœ์น™ ํ• ๋‹น ์™„๋ฃŒ");