From 7d6bff49aa7d737fb6bb6fa78490b99b2d595a0f Mon Sep 17 00:00:00 2001 From: kjs Date: Tue, 30 Dec 2025 15:36:28 +0900 Subject: [PATCH 1/5] =?UTF-8?q?=ED=8F=BC=20=EC=B1=84=EB=B2=88=20=EC=98=A4?= =?UTF-8?q?=EC=9E=91=EB=8F=99=20=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/lib/utils/buttonActions.ts | 60 +++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/frontend/lib/utils/buttonActions.ts b/frontend/lib/utils/buttonActions.ts index 9a6a606e..944f7126 100644 --- a/frontend/lib/utils/buttonActions.ts +++ b/frontend/lib/utils/buttonActions.ts @@ -1608,6 +1608,66 @@ export class ButtonActionExecutor { return { handled: false, success: false }; } + // ๐ŸŽฏ ์ฑ„๋ฒˆ ๊ทœ์น™ ํ• ๋‹น ์ฒ˜๋ฆฌ (์ €์žฅ ์‹œ์ ์— ์‹ค์ œ ์ˆœ๋ฒˆ ์ฆ๊ฐ€) + console.log("๐Ÿ” [handleUniversalFormModalTableSectionSave] ์ฑ„๋ฒˆ ๊ทœ์น™ ํ• ๋‹น ์ฒดํฌ ์‹œ์ž‘"); + + const fieldsWithNumbering: Record = {}; + + // commonFieldsData์™€ modalData์—์„œ ์ฑ„๋ฒˆ ๊ทœ์น™์ด ์„ค์ •๋œ ํ•„๋“œ ์ฐพ๊ธฐ + for (const [key, value] of Object.entries(modalData)) { + if (key.endsWith("_numberingRuleId") && value) { + const fieldName = key.replace("_numberingRuleId", ""); + fieldsWithNumbering[fieldName] = value as string; + console.log(`๐ŸŽฏ [handleUniversalFormModalTableSectionSave] ์ฑ„๋ฒˆ ํ•„๋“œ ๋ฐœ๊ฒฌ: ${fieldName} โ†’ ๊ทœ์น™ ${value}`); + } + } + + // formData์—์„œ๋„ ํ™•์ธ (๋ชจ๋‹ฌ ์™ธ๋ถ€์— ์žˆ์„ ์ˆ˜ ์žˆ์Œ) + for (const [key, value] of Object.entries(formData)) { + if (key.endsWith("_numberingRuleId") && value && !fieldsWithNumbering[key.replace("_numberingRuleId", "")]) { + const fieldName = key.replace("_numberingRuleId", ""); + fieldsWithNumbering[fieldName] = value as string; + console.log( + `๐ŸŽฏ [handleUniversalFormModalTableSectionSave] ์ฑ„๋ฒˆ ํ•„๋“œ ๋ฐœ๊ฒฌ (formData): ${fieldName} โ†’ ๊ทœ์น™ ${value}`, + ); + } + } + + console.log("๐Ÿ“‹ [handleUniversalFormModalTableSectionSave] ์ฑ„๋ฒˆ ๊ทœ์น™์ด ์„ค์ •๋œ ํ•„๋“œ:", fieldsWithNumbering); + + // ๐Ÿ”ฅ ์ €์žฅ ์‹œ์ ์— allocateCode ํ˜ธ์ถœํ•˜์—ฌ ์‹ค์ œ ์ˆœ๋ฒˆ ์ฆ๊ฐ€ + if (Object.keys(fieldsWithNumbering).length > 0) { + console.log("๐ŸŽฏ [handleUniversalFormModalTableSectionSave] ์ฑ„๋ฒˆ ๊ทœ์น™ ํ• ๋‹น ์‹œ์ž‘ (allocateCode ํ˜ธ์ถœ)"); + const { allocateNumberingCode } = await import("@/lib/api/numberingRule"); + + for (const [fieldName, ruleId] of Object.entries(fieldsWithNumbering)) { + try { + console.log( + `๐Ÿ”„ [handleUniversalFormModalTableSectionSave] ${fieldName} ํ•„๋“œ์— ๋Œ€ํ•ด allocateCode ํ˜ธ์ถœ: ${ruleId}`, + ); + const allocateResult = await allocateNumberingCode(ruleId); + + if (allocateResult.success && allocateResult.data?.generatedCode) { + const newCode = allocateResult.data.generatedCode; + console.log( + `โœ… [handleUniversalFormModalTableSectionSave] ${fieldName} ์ƒˆ ์ฝ”๋“œ ํ• ๋‹น: ${commonFieldsData[fieldName]} โ†’ ${newCode}`, + ); + commonFieldsData[fieldName] = newCode; + } else { + console.warn( + `โš ๏ธ [handleUniversalFormModalTableSectionSave] ${fieldName} ์ฝ”๋“œ ํ• ๋‹น ์‹คํŒจ, ๊ธฐ์กด ๊ฐ’ ์œ ์ง€:`, + allocateResult.error, + ); + } + } catch (allocateError) { + console.error(`โŒ [handleUniversalFormModalTableSectionSave] ${fieldName} ์ฝ”๋“œ ํ• ๋‹น ์˜ค๋ฅ˜:`, allocateError); + // ์˜ค๋ฅ˜ ์‹œ ๊ธฐ์กด ๊ฐ’ ์œ ์ง€ + } + } + } + + console.log("โœ… [handleUniversalFormModalTableSectionSave] ์ฑ„๋ฒˆ ๊ทœ์น™ ํ• ๋‹น ์™„๋ฃŒ"); + try { // ์‚ฌ์šฉ์ž ์ •๋ณด ์ถ”๊ฐ€ if (!context.userId) { From bd49db16c6c55c226a28151491864797ae777203 Mon Sep 17 00:00:00 2001 From: kjs Date: Tue, 30 Dec 2025 17:45:38 +0900 Subject: [PATCH 2/5] 1 --- frontend/app/(main)/admin/systemMng/dataflow/page.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/app/(main)/admin/systemMng/dataflow/page.tsx b/frontend/app/(main)/admin/systemMng/dataflow/page.tsx index 87d937ec..d55a6cf1 100644 --- a/frontend/app/(main)/admin/systemMng/dataflow/page.tsx +++ b/frontend/app/(main)/admin/systemMng/dataflow/page.tsx @@ -51,17 +51,17 @@ export default function DataFlowPage() { // ์—๋””ํ„ฐ ๋ชจ๋“œ์ผ ๋•Œ๋Š” ๋ ˆ์ด์•„์›ƒ ์—†์ด ์ „์ฒด ํ™”๋ฉด ์‚ฌ์šฉ if (isEditorMode) { return ( -
+
{/* ์—๋””ํ„ฐ ํ—ค๋” */} -
+

๋…ธ๋“œ ํ”Œ๋กœ์šฐ ์—๋””ํ„ฐ

-

+

๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ์œผ๋กœ ๋ฐ์ดํ„ฐ ์ œ์–ด ํ”Œ๋กœ์šฐ๋ฅผ ์‹œ๊ฐ์ ์œผ๋กœ ์„ค๊ณ„ํ•ฉ๋‹ˆ๋‹ค

@@ -77,12 +77,12 @@ export default function DataFlowPage() { } return ( -
+
{/* ํŽ˜์ด์ง€ ํ—ค๋” */}

์ œ์–ด ๊ด€๋ฆฌ

-

๋…ธ๋“œ ๊ธฐ๋ฐ˜ ๋ฐ์ดํ„ฐ ํ”Œ๋กœ์šฐ๋ฅผ ์‹œ๊ฐ์ ์œผ๋กœ ์„ค๊ณ„ํ•˜๊ณ  ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค

+

๋…ธ๋“œ ๊ธฐ๋ฐ˜ ๋ฐ์ดํ„ฐ ํ”Œ๋กœ์šฐ๋ฅผ ์‹œ๊ฐ์ ์œผ๋กœ ์„ค๊ณ„ํ•˜๊ณ  ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค

{/* ํ”Œ๋กœ์šฐ ๋ชฉ๋ก */} From 5bdc903b0dee0382f21de3792b916937cf36fe8c Mon Sep 17 00:00:00 2001 From: kjs Date: Wed, 31 Dec 2025 10:54:07 +0900 Subject: [PATCH 3/5] =?UTF-8?q?=EB=B2=94=EC=9A=A9=20=ED=8F=BC=EB=AA=A8?= =?UTF-8?q?=EB=8B=AC=20=EC=A0=9C=EC=96=B4=EB=A1=9C=EC=A7=81=20=EC=97=B0?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/routes/dataflow/node-flows.ts | 67 ++++++++++++++++ frontend/lib/api/nodeFlows.ts | 38 +++++++++ frontend/lib/utils/buttonActions.ts | 78 +++++++++++++++++++ 3 files changed, 183 insertions(+) diff --git a/backend-node/src/routes/dataflow/node-flows.ts b/backend-node/src/routes/dataflow/node-flows.ts index 6de84866..177b4304 100644 --- a/backend-node/src/routes/dataflow/node-flows.ts +++ b/backend-node/src/routes/dataflow/node-flows.ts @@ -214,6 +214,73 @@ router.delete("/:flowId", async (req: Request, res: Response) => { } }); +/** + * ํ”Œ๋กœ์šฐ ์†Œ์Šค ํ…Œ์ด๋ธ” ์กฐํšŒ + * GET /api/dataflow/node-flows/:flowId/source-table + * ํ”Œ๋กœ์šฐ์˜ ์ฒซ ๋ฒˆ์งธ ์†Œ์Šค ๋…ธ๋“œ(tableSource, externalDBSource)์—์„œ ํ…Œ์ด๋ธ”๋ช… ์ถ”์ถœ + */ +router.get("/:flowId/source-table", async (req: Request, res: Response) => { + try { + const { flowId } = req.params; + + const flow = await queryOne<{ flow_data: any }>( + `SELECT flow_data FROM node_flows WHERE flow_id = $1`, + [flowId] + ); + + if (!flow) { + return res.status(404).json({ + success: false, + message: "ํ”Œ๋กœ์šฐ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", + }); + } + + const flowData = + typeof flow.flow_data === "string" + ? JSON.parse(flow.flow_data) + : flow.flow_data; + + const nodes = flowData.nodes || []; + + // ์†Œ์Šค ๋…ธ๋“œ ์ฐพ๊ธฐ (tableSource, externalDBSource ํƒ€์ž…) + const sourceNode = nodes.find( + (node: any) => + node.type === "tableSource" || node.type === "externalDBSource" + ); + + if (!sourceNode || !sourceNode.data?.tableName) { + return res.json({ + success: true, + data: { + sourceTable: null, + sourceNodeType: null, + message: "์†Œ์Šค ๋…ธ๋“œ๊ฐ€ ์—†๊ฑฐ๋‚˜ ํ…Œ์ด๋ธ”๋ช…์ด ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.", + }, + }); + } + + logger.info( + `ํ”Œ๋กœ์šฐ ์†Œ์Šค ํ…Œ์ด๋ธ” ์กฐํšŒ: flowId=${flowId}, table=${sourceNode.data.tableName}` + ); + + return res.json({ + success: true, + data: { + sourceTable: sourceNode.data.tableName, + sourceNodeType: sourceNode.type, + sourceNodeId: sourceNode.id, + displayName: sourceNode.data.displayName, + }, + }); + } catch (error) { + logger.error("ํ”Œ๋กœ์šฐ ์†Œ์Šค ํ…Œ์ด๋ธ” ์กฐํšŒ ์‹คํŒจ:", error); + return res.status(500).json({ + success: false, + message: "ํ”Œ๋กœ์šฐ ์†Œ์Šค ํ…Œ์ด๋ธ”์„ ์กฐํšŒํ•˜์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.", + }); + } +}); + /** * ํ”Œ๋กœ์šฐ ์‹คํ–‰ * POST /api/dataflow/node-flows/:flowId/execute diff --git a/frontend/lib/api/nodeFlows.ts b/frontend/lib/api/nodeFlows.ts index b42340d7..27bb1b96 100644 --- a/frontend/lib/api/nodeFlows.ts +++ b/frontend/lib/api/nodeFlows.ts @@ -120,3 +120,41 @@ export interface NodeExecutionSummary { duration?: number; error?: string; } + +/** + * ํ”Œ๋กœ์šฐ ์†Œ์Šค ํ…Œ์ด๋ธ” ์ •๋ณด ์ธํ„ฐํŽ˜์ด์Šค + */ +export interface FlowSourceTableInfo { + sourceTable: string | null; + sourceNodeType: string | null; + sourceNodeId?: string; + displayName?: string; + message?: string; +} + +/** + * ํ”Œ๋กœ์šฐ ์†Œ์Šค ํ…Œ์ด๋ธ” ์กฐํšŒ + * ํ”Œ๋กœ์šฐ์˜ ์ฒซ ๋ฒˆ์งธ ์†Œ์Šค ๋…ธ๋“œ(tableSource, externalDBSource)์—์„œ ํ…Œ์ด๋ธ”๋ช… ์ถ”์ถœ + */ +export async function getFlowSourceTable(flowId: number): Promise { + try { + const response = await apiClient.get>( + `/dataflow/node-flows/${flowId}/source-table`, + ); + if (response.data.success && response.data.data) { + return response.data.data; + } + return { + sourceTable: null, + sourceNodeType: null, + message: response.data.message || "์†Œ์Šค ํ…Œ์ด๋ธ” ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", + }; + } catch (error) { + console.error("ํ”Œ๋กœ์šฐ ์†Œ์Šค ํ…Œ์ด๋ธ” ์กฐํšŒ ์‹คํŒจ:", error); + return { + sourceTable: null, + sourceNodeType: null, + message: "API ํ˜ธ์ถœ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.", + }; + } +} diff --git a/frontend/lib/utils/buttonActions.ts b/frontend/lib/utils/buttonActions.ts index 944f7126..327cb87f 100644 --- a/frontend/lib/utils/buttonActions.ts +++ b/frontend/lib/utils/buttonActions.ts @@ -1864,6 +1864,84 @@ export class ButtonActionExecutor { console.log(`โœ… [handleUniversalFormModalTableSectionSave] ์™„๋ฃŒ: ${resultMessage}`); toast.success(`์ €์žฅ ์™„๋ฃŒ: ${resultMessage}`); + // ๐Ÿ†• ์ €์žฅ ์„ฑ๊ณต ํ›„ ์ œ์–ด ๊ด€๋ฆฌ ์‹คํ–‰ (๋‹ค์ค‘ ํ…Œ์ด๋ธ” ์ €์žฅ ์‹œ ์†Œ์Šค ํ…Œ์ด๋ธ”๊ณผ ์ผ์น˜ํ•˜๋Š” ์„น์…˜๋งŒ ์‹คํ–‰) + if (config.enableDataflowControl && config.dataflowConfig?.flowConfig?.flowId) { + const flowId = config.dataflowConfig.flowConfig.flowId; + console.log("๐ŸŽฏ [handleUniversalFormModalTableSectionSave] ์ œ์–ด ๊ด€๋ฆฌ ์‹คํ–‰ ์‹œ์ž‘:", { flowId }); + + try { + // ํ”Œ๋กœ์šฐ ์†Œ์Šค ํ…Œ์ด๋ธ” ์กฐํšŒ + const { getFlowSourceTable } = await import("@/lib/api/nodeFlows"); + const flowSourceInfo = await getFlowSourceTable(flowId); + + console.log("๐Ÿ“Š [handleUniversalFormModalTableSectionSave] ํ”Œ๋กœ์šฐ ์†Œ์Šค ํ…Œ์ด๋ธ”:", flowSourceInfo); + + if (flowSourceInfo.sourceTable) { + // ๊ฐ ์„น์…˜ ํ™•์ธํ•˜์—ฌ ์†Œ์Šค ํ…Œ์ด๋ธ”๊ณผ ์ผ์น˜ํ•˜๋Š” ์„น์…˜ ์ฐพ๊ธฐ + let controlExecuted = false; + + for (const [sectionId, sectionItems] of Object.entries(tableSectionData)) { + const sectionConfig = sections.find((s: any) => s.id === sectionId); + const sectionTargetTable = sectionConfig?.tableConfig?.saveConfig?.targetTable || tableName; + + console.log(`๐Ÿ” [handleUniversalFormModalTableSectionSave] ์„น์…˜ ${sectionId} ํ…Œ์ด๋ธ” ๋น„๊ต:`, { + sectionTargetTable, + flowSourceTable: flowSourceInfo.sourceTable, + isMatch: sectionTargetTable === flowSourceInfo.sourceTable, + }); + + // ์†Œ์Šค ํ…Œ์ด๋ธ”๊ณผ ์ผ์น˜ํ•˜๋Š” ์„น์…˜๋งŒ ์ œ์–ด ์‹คํ–‰ + if (sectionTargetTable === flowSourceInfo.sourceTable && sectionItems.length > 0) { + console.log( + `โœ… [handleUniversalFormModalTableSectionSave] ์„น์…˜ ${sectionId} โ†’ ํ”Œ๋กœ์šฐ ์†Œ์Šค ํ…Œ์ด๋ธ” ์ผ์น˜! ์ œ์–ด ์‹คํ–‰`, + ); + + // ๊ณตํ†ต ํ•„๋“œ + ํ•ด๋‹น ์„น์…˜ ๋ฐ์ดํ„ฐ ๋ณ‘ํ•ฉํ•˜์—ฌ sourceData ์ƒ์„ฑ + const sourceData = sectionItems.map((item: any) => ({ + ...commonFieldsData, + ...item, + })); + + console.log( + `๐Ÿ“ฆ [handleUniversalFormModalTableSectionSave] ์ œ์–ด ์ „๋‹ฌ ๋ฐ์ดํ„ฐ: ${sourceData.length}๊ฑด`, + sourceData[0], + ); + + // ์ œ์–ด ๊ด€๋ฆฌ์šฉ ์ปจํ…์ŠคํŠธ ์ƒ์„ฑ + const controlContext: ButtonActionContext = { + ...context, + selectedRowsData: sourceData, + formData: commonFieldsData, + }; + + // ์ œ์–ด ๊ด€๋ฆฌ ์‹คํ–‰ + await this.executeAfterSaveControl(config, controlContext); + controlExecuted = true; + break; // ์ฒซ ๋ฒˆ์งธ ๋งค์นญ ์„น์…˜๋งŒ ์‹คํ–‰ + } + } + + // ๋งค์นญ๋˜๋Š” ์„น์…˜์ด ์—†์œผ๋ฉด ๋ฉ”์ธ ํ…Œ์ด๋ธ” ํ™•์ธ + if (!controlExecuted && tableName === flowSourceInfo.sourceTable) { + console.log("โœ… [handleUniversalFormModalTableSectionSave] ๋ฉ”์ธ ํ…Œ์ด๋ธ” ์ผ์น˜! ๊ณตํ†ต ํ•„๋“œ๋กœ ์ œ์–ด ์‹คํ–‰"); + + const controlContext: ButtonActionContext = { + ...context, + selectedRowsData: [commonFieldsData], + formData: commonFieldsData, + }; + + await this.executeAfterSaveControl(config, controlContext); + } + } else { + console.log("โš ๏ธ [handleUniversalFormModalTableSectionSave] ํ”Œ๋กœ์šฐ ์†Œ์Šค ํ…Œ์ด๋ธ” ์—†์Œ - ์ œ์–ด ์Šคํ‚ต"); + } + } catch (controlError) { + console.error("โŒ [handleUniversalFormModalTableSectionSave] ์ œ์–ด ๊ด€๋ฆฌ ์‹คํ–‰ ์˜ค๋ฅ˜:", controlError); + // ์ œ์–ด ๊ด€๋ฆฌ ์‹คํŒจ๋Š” ์ €์žฅ ์„ฑ๊ณต์— ์˜ํ–ฅ์ฃผ์ง€ ์•Š์Œ + } + } + // ์ €์žฅ ์„ฑ๊ณต ์ด๋ฒคํŠธ ๋ฐœ์ƒ window.dispatchEvent(new CustomEvent("saveSuccess")); window.dispatchEvent(new CustomEvent("refreshTable")); From 417e1d297b52176aa7060af6bb22194deef71a05 Mon Sep 17 00:00:00 2001 From: kjs Date: Wed, 31 Dec 2025 13:53:30 +0900 Subject: [PATCH 4/5] =?UTF-8?q?=ED=8F=BC=20=EC=A1=B0=EA=B1=B4=EB=B3=84=20?= =?UTF-8?q?=EA=B3=84=EC=82=B0=EC=8B=9D=20=EC=84=A4=EC=A0=95=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TableSectionRenderer.tsx | 58 ++- .../modals/TableSectionSettingsModal.tsx | 474 ++++++++++++++++-- .../components/universal-form-modal/types.ts | 24 + 3 files changed, 505 insertions(+), 51 deletions(-) diff --git a/frontend/lib/registry/components/universal-form-modal/TableSectionRenderer.tsx b/frontend/lib/registry/components/universal-form-modal/TableSectionRenderer.tsx index a1c0bd76..1242e1d2 100644 --- a/frontend/lib/registry/components/universal-form-modal/TableSectionRenderer.tsx +++ b/frontend/lib/registry/components/universal-form-modal/TableSectionRenderer.tsx @@ -16,7 +16,13 @@ import { ItemSelectionModal } from "../modal-repeater-table/ItemSelectionModal"; import { RepeaterColumnConfig, CalculationRule } from "../modal-repeater-table/types"; // ํƒ€์ž… ์ •์˜ -import { TableSectionConfig, TableColumnConfig, TableJoinCondition, FormDataState } from "./types"; +import { + TableSectionConfig, + TableColumnConfig, + TableJoinCondition, + FormDataState, + TableCalculationRule, +} from "./types"; interface TableSectionRendererProps { sectionId: string; @@ -811,39 +817,69 @@ export function TableSectionRenderer({ }); }, [tableConfig.columns, dynamicSelectOptionsMap]); - // ๊ณ„์‚ฐ ๊ทœ์น™ ๋ณ€ํ™˜ - const calculationRules: CalculationRule[] = (tableConfig.calculations || []).map(convertToCalculationRule); + // ์›๋ณธ ๊ณ„์‚ฐ ๊ทœ์น™ (์กฐ๊ฑด๋ถ€ ๊ณ„์‚ฐ ํฌํ•จ) + const originalCalculationRules: TableCalculationRule[] = useMemo( + () => tableConfig.calculations || [], + [tableConfig.calculations], + ); - // ๊ณ„์‚ฐ ๋กœ์ง + // ๊ธฐ๋ณธ ๊ณ„์‚ฐ ๊ทœ์น™ ๋ณ€ํ™˜ (RepeaterTable์šฉ - ์กฐ๊ฑด๋ถ€ ๊ณ„์‚ฐ์ด ์—†๋Š” ๊ฒฝ์šฐ์— ์‚ฌ์šฉ) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const calculationRules: CalculationRule[] = originalCalculationRules.map(convertToCalculationRule); + + // ์กฐ๊ฑด๋ถ€ ๊ณ„์‚ฐ ๋กœ์ง: ํ–‰์˜ ์กฐ๊ฑด ํ•„๋“œ ๊ฐ’์— ๋”ฐ๋ผ ์ ์ ˆํ•œ ๊ณ„์‚ฐ์‹ ์„ ํƒ + const getFormulaForRow = useCallback((rule: TableCalculationRule, row: Record): string => { + // ์กฐ๊ฑด๋ถ€ ๊ณ„์‚ฐ์ด ํ™œ์„ฑํ™”๋œ ๊ฒฝ์šฐ + if (rule.conditionalCalculation?.enabled && rule.conditionalCalculation.conditionField) { + const conditionValue = row[rule.conditionalCalculation.conditionField]; + // ์กฐ๊ฑด๊ฐ’๊ณผ ์ผ์น˜ํ•˜๋Š” ๊ทœ์น™ ์ฐพ๊ธฐ + const matchedRule = rule.conditionalCalculation.rules?.find((r) => r.conditionValue === conditionValue); + if (matchedRule) { + return matchedRule.formula; + } + // ์ผ์น˜ํ•˜๋Š” ๊ทœ์น™์ด ์—†์œผ๋ฉด ๊ธฐ๋ณธ ๊ณ„์‚ฐ์‹ ์‚ฌ์šฉ + if (rule.conditionalCalculation.defaultFormula) { + return rule.conditionalCalculation.defaultFormula; + } + } + // ์กฐ๊ฑด๋ถ€ ๊ณ„์‚ฐ์ด ๋น„ํ™œ์„ฑํ™”๋˜์—ˆ๊ฑฐ๋‚˜ ๊ธฐ๋ณธ๊ฐ’์ด ์—†์œผ๋ฉด ์›๋ž˜ ๊ณ„์‚ฐ์‹ ์‚ฌ์šฉ + return rule.formula; + }, []); + + // ๊ณ„์‚ฐ ๋กœ์ง (์กฐ๊ฑด๋ถ€ ๊ณ„์‚ฐ ์ง€์›) const calculateRow = useCallback( (row: any): any => { - if (calculationRules.length === 0) return row; + if (originalCalculationRules.length === 0) return row; const updatedRow = { ...row }; - for (const rule of calculationRules) { + for (const rule of originalCalculationRules) { try { - let formula = rule.formula; + // ์กฐ๊ฑด๋ถ€ ๊ณ„์‚ฐ์— ๋”ฐ๋ผ ์ ์ ˆํ•œ ๊ณ„์‚ฐ์‹ ์„ ํƒ + let formula = getFormulaForRow(rule, row); + + if (!formula) continue; + const fieldMatches = formula.match(/[a-zA-Z_][a-zA-Z0-9_]*/g) || []; const dependencies = rule.dependencies.length > 0 ? rule.dependencies : fieldMatches; for (const dep of dependencies) { - if (dep === rule.result) continue; + if (dep === rule.resultField) continue; const value = parseFloat(row[dep]) || 0; formula = formula.replace(new RegExp(`\\b${dep}\\b`, "g"), value.toString()); } const result = new Function(`return ${formula}`)(); - updatedRow[rule.result] = result; + updatedRow[rule.resultField] = result; } catch (error) { console.error(`๊ณ„์‚ฐ ์˜ค๋ฅ˜ (${rule.formula}):`, error); - updatedRow[rule.result] = 0; + updatedRow[rule.resultField] = 0; } } return updatedRow; }, - [calculationRules], + [originalCalculationRules, getFormulaForRow], ); const calculateAll = useCallback( diff --git a/frontend/lib/registry/components/universal-form-modal/modals/TableSectionSettingsModal.tsx b/frontend/lib/registry/components/universal-form-modal/modals/TableSectionSettingsModal.tsx index d82db59b..037707ca 100644 --- a/frontend/lib/registry/components/universal-form-modal/modals/TableSectionSettingsModal.tsx +++ b/frontend/lib/registry/components/universal-form-modal/modals/TableSectionSettingsModal.tsx @@ -24,6 +24,8 @@ import { TablePreFilter, TableModalFilter, TableCalculationRule, + ConditionalCalculationRule, + ConditionalCalculationConfig, LookupOption, LookupCondition, ConditionalTableOption, @@ -52,6 +54,429 @@ const HelpText = ({ children }: { children: React.ReactNode }) => (

{children}

); +// ๊ณ„์‚ฐ ๊ทœ์น™ ํŽธ์ง‘ ์ปดํฌ๋„ŒํŠธ (์กฐ๊ฑด๋ถ€ ๊ณ„์‚ฐ ์ง€์›) +interface CalculationRuleEditorProps { + calc: TableCalculationRule; + index: number; + columns: TableColumnConfig[]; + sourceTableName?: string; // ์†Œ์Šค ํ…Œ์ด๋ธ”๋ช… ์ถ”๊ฐ€ + onUpdate: (updates: Partial) => void; + onRemove: () => void; +} + +const CalculationRuleEditor: React.FC = ({ + calc, + index, + columns, + sourceTableName, + onUpdate, + onRemove, +}) => { + const [categoryOptions, setCategoryOptions] = useState<{ value: string; label: string }[]>([]); + const [loadingOptions, setLoadingOptions] = useState(false); + const [categoryColumns, setCategoryColumns] = useState>({}); + + // ์กฐ๊ฑด๋ถ€ ๊ณ„์‚ฐ ํ™œ์„ฑํ™” ์—ฌ๋ถ€ + const isConditionalEnabled = calc.conditionalCalculation?.enabled ?? false; + + // ์†Œ์Šค ํ…Œ์ด๋ธ”์˜ ์นดํ…Œ๊ณ ๋ฆฌ ์ปฌ๋Ÿผ ์ •๋ณด ๋กœ๋“œ + useEffect(() => { + const loadCategoryColumns = async () => { + console.log("[CalculationRuleEditor] sourceTableName:", sourceTableName); + + if (!sourceTableName) { + setCategoryColumns({}); + return; + } + + try { + const { getCategoryColumns } = await import("@/lib/api/tableCategoryValue"); + const result = await getCategoryColumns(sourceTableName); + console.log("[CalculationRuleEditor] getCategoryColumns ๊ฒฐ๊ณผ:", result); + + if (result && result.success && Array.isArray(result.data)) { + const categoryMap: Record = {}; + result.data.forEach((col: any) => { + // API ์‘๋‹ต์€ camelCase (columnName) + const colName = col.columnName || col.column_name; + if (colName) { + categoryMap[colName] = true; + } + }); + console.log("[CalculationRuleEditor] categoryMap:", categoryMap); + setCategoryColumns(categoryMap); + } + } catch (error) { + console.error("์นดํ…Œ๊ณ ๋ฆฌ ์ปฌ๋Ÿผ ์กฐํšŒ ์‹คํŒจ:", error); + } + }; + + loadCategoryColumns(); + }, [sourceTableName]); + + // ์กฐ๊ฑด ํ•„๋“œ๊ฐ€ ์„ ํƒ๋˜์—ˆ์„ ๋•Œ ์˜ต์…˜ ๋กœ๋“œ (ํ…Œ์ด๋ธ” ํƒ€์ž… ๊ด€๋ฆฌ์˜ ์นดํ…Œ๊ณ ๋ฆฌ ๊ธฐ์ค€) + useEffect(() => { + const loadConditionOptions = async () => { + if (!isConditionalEnabled || !calc.conditionalCalculation?.conditionField) { + setCategoryOptions([]); + return; + } + + const conditionField = calc.conditionalCalculation.conditionField; + + // ์†Œ์Šค ํ•„๋“œ(sourceField)๊ฐ€ ์žˆ์œผ๋ฉด ํ•ด๋‹น ํ•„๋“œ๋ช… ์‚ฌ์šฉ, ์—†์œผ๋ฉด field๋ช… ์‚ฌ์šฉ + const selectedColumn = columns.find((col) => col.field === conditionField); + const actualFieldName = selectedColumn?.sourceField || conditionField; + + console.log("[loadConditionOptions] ์กฐ๊ฑด ํ•„๋“œ:", { + conditionField, + actualFieldName, + sourceTableName, + categoryColumnsKeys: Object.keys(categoryColumns), + isCategoryColumn: categoryColumns[actualFieldName], + }); + + // ์†Œ์Šค ํ…Œ์ด๋ธ”์—์„œ ํ•ด๋‹น ์ปฌ๋Ÿผ์ด ์นดํ…Œ๊ณ ๋ฆฌ ํƒ€์ž…์ธ์ง€ ํ™•์ธ + if (sourceTableName && categoryColumns[actualFieldName]) { + try { + setLoadingOptions(true); + const { getCategoryValues } = await import("@/lib/api/tableCategoryValue"); + console.log("[loadConditionOptions] getCategoryValues ํ˜ธ์ถœ:", sourceTableName, actualFieldName); + const result = await getCategoryValues(sourceTableName, actualFieldName, false); + console.log("[loadConditionOptions] getCategoryValues ๊ฒฐ๊ณผ:", result); + if (result && result.success && Array.isArray(result.data)) { + const options = result.data.map((item: any) => ({ + // API ์‘๋‹ต์€ camelCase (valueCode, valueLabel) + value: item.valueCode || item.value_code || item.value, + label: item.valueLabel || item.displayLabel || item.display_label || item.label || item.valueCode || item.value_code || item.value, + })); + console.log("[loadConditionOptions] ๋งคํ•‘๋œ ์˜ต์…˜:", options); + setCategoryOptions(options); + } else { + setCategoryOptions([]); + } + } catch (error) { + console.error("์นดํ…Œ๊ณ ๋ฆฌ ๊ฐ’ ๋กœ๋“œ ์‹คํŒจ:", error); + setCategoryOptions([]); + } finally { + setLoadingOptions(false); + } + return; + } + + // ์นดํ…Œ๊ณ ๋ฆฌ ํ‚ค๊ฐ€ ์ง์ ‘ ์„ค์ •๋œ ๊ฒฝ์šฐ (์ €์žฅ๋œ ๊ฐ’) + const categoryKey = calc.conditionalCalculation?.conditionFieldCategoryKey; + if (categoryKey) { + try { + setLoadingOptions(true); + const [tableName, columnName] = categoryKey.split("."); + if (tableName && columnName) { + const { getCategoryValues } = await import("@/lib/api/tableCategoryValue"); + const result = await getCategoryValues(tableName, columnName, false); + if (result && result.success && Array.isArray(result.data)) { + setCategoryOptions( + result.data.map((item: any) => ({ + // API ์‘๋‹ต์€ camelCase (valueCode, valueLabel) + value: item.valueCode || item.value_code || item.value, + label: item.valueLabel || item.displayLabel || item.display_label || item.label || item.valueCode || item.value_code || item.value, + })) + ); + } + } + } catch (error) { + console.error("์นดํ…Œ๊ณ ๋ฆฌ ์˜ต์…˜ ๋กœ๋“œ ์‹คํŒจ:", error); + } finally { + setLoadingOptions(false); + } + return; + } + + // ๊ทธ ์™ธ ํƒ€์ž…์€ ์˜ต์…˜ ์—†์Œ (์ง์ ‘ ์ž…๋ ฅ) + setCategoryOptions([]); + }; + + loadConditionOptions(); + }, [isConditionalEnabled, calc.conditionalCalculation?.conditionField, calc.conditionalCalculation?.conditionFieldCategoryKey, columns, sourceTableName, categoryColumns]); + + // ์กฐ๊ฑด๋ถ€ ๊ณ„์‚ฐ ํ† ๊ธ€ + const toggleConditionalCalculation = (enabled: boolean) => { + onUpdate({ + conditionalCalculation: enabled + ? { + enabled: true, + conditionField: "", + conditionFieldType: "static", + rules: [], + defaultFormula: calc.formula || "", + } + : undefined, + }); + }; + + // ์กฐ๊ฑด ํ•„๋“œ ๋ณ€๊ฒฝ + const updateConditionField = (field: string) => { + const selectedColumn = columns.find((col) => col.field === field); + const actualFieldName = selectedColumn?.sourceField || field; + + // ์ปฌ๋Ÿผ์˜ ํƒ€์ž…๊ณผ ์˜ต์…˜ ํ™•์ธ (ํ…Œ์ด๋ธ” ํƒ€์ž… ๊ด€๋ฆฌ์˜ ์นดํ…Œ๊ณ ๋ฆฌ ๊ธฐ์ค€) + let conditionFieldType: "static" | "code" | "table" = "static"; + let conditionFieldCategoryKey = ""; + + // ์†Œ์Šค ํ…Œ์ด๋ธ”์—์„œ ํ•ด๋‹น ์ปฌ๋Ÿผ์ด ์นดํ…Œ๊ณ ๋ฆฌ ํƒ€์ž…์ธ์ง€ ํ™•์ธ + if (sourceTableName && categoryColumns[actualFieldName]) { + conditionFieldType = "code"; + conditionFieldCategoryKey = `${sourceTableName}.${actualFieldName}`; + } + + onUpdate({ + conditionalCalculation: { + ...calc.conditionalCalculation!, + conditionField: field, + conditionFieldType, + conditionFieldCategoryKey, + rules: [], // ํ•„๋“œ ๋ณ€๊ฒฝ ์‹œ ๊ทœ์น™ ์ดˆ๊ธฐํ™” + }, + }); + }; + + // ์กฐ๊ฑด ๊ทœ์น™ ์ถ”๊ฐ€ + const addConditionRule = () => { + const newRule: ConditionalCalculationRule = { + conditionValue: "", + formula: calc.formula || "", + }; + onUpdate({ + conditionalCalculation: { + ...calc.conditionalCalculation!, + rules: [...(calc.conditionalCalculation?.rules || []), newRule], + }, + }); + }; + + // ์กฐ๊ฑด ๊ทœ์น™ ์—…๋ฐ์ดํŠธ + const updateConditionRule = (ruleIndex: number, updates: Partial) => { + const newRules = [...(calc.conditionalCalculation?.rules || [])]; + newRules[ruleIndex] = { ...newRules[ruleIndex], ...updates }; + onUpdate({ + conditionalCalculation: { + ...calc.conditionalCalculation!, + rules: newRules, + }, + }); + }; + + // ์กฐ๊ฑด ๊ทœ์น™ ์‚ญ์ œ + const removeConditionRule = (ruleIndex: number) => { + onUpdate({ + conditionalCalculation: { + ...calc.conditionalCalculation!, + rules: (calc.conditionalCalculation?.rules || []).filter((_, i) => i !== ruleIndex), + }, + }); + }; + + // ๊ธฐ๋ณธ ๊ณ„์‚ฐ์‹ ์—…๋ฐ์ดํŠธ + const updateDefaultFormula = (formula: string) => { + onUpdate({ + conditionalCalculation: { + ...calc.conditionalCalculation!, + defaultFormula: formula, + }, + }); + }; + + // ์กฐ๊ฑด ํ•„๋“œ๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ปฌ๋Ÿผ (๋ชจ๋“  ์ปฌ๋Ÿผ) + const availableColumns = columns.filter((col) => col.field); + + return ( +
+ {/* ๊ธฐ๋ณธ ๊ณ„์‚ฐ ๊ทœ์น™ */} +
+ + = + onUpdate({ formula: e.target.value })} + placeholder="์ˆ˜์‹ (์˜ˆ: qty * unit_price)" + className="h-8 text-xs flex-1" + disabled={isConditionalEnabled} + /> + +
+ + {/* ์กฐ๊ฑด๋ถ€ ๊ณ„์‚ฐ ํ† ๊ธ€ */} +
+ + + {availableColumns.length === 0 && !isConditionalEnabled && ( + + (์ปฌ๋Ÿผ ์„ค์ •์—์„œ ๋จผ์ € ์ปฌ๋Ÿผ์„ ์ถ”๊ฐ€ํ•˜์„ธ์š”) + + )} +
+ + {/* ์กฐ๊ฑด๋ถ€ ๊ณ„์‚ฐ ์„ค์ • */} + {isConditionalEnabled && ( +
+ {/* ์กฐ๊ฑด ํ•„๋“œ ์„ ํƒ */} +
+ + +
+ + {/* ์กฐ๊ฑด๋ณ„ ๊ณ„์‚ฐ์‹ ๋ชฉ๋ก */} + {calc.conditionalCalculation?.conditionField && ( +
+
+ + +
+ + {(calc.conditionalCalculation?.rules || []).map((rule, ruleIndex) => ( +
+ {/* ์กฐ๊ฑด๊ฐ’ ์„ ํƒ */} + {categoryOptions.length > 0 ? ( + + ) : ( + + updateConditionRule(ruleIndex, { conditionValue: e.target.value }) + } + placeholder="์กฐ๊ฑด๊ฐ’" + className="h-7 text-xs w-[120px]" + /> + )} + โ†’ + + updateConditionRule(ruleIndex, { formula: e.target.value }) + } + placeholder="๊ณ„์‚ฐ์‹" + className="h-7 text-xs flex-1" + /> + +
+ ))} + + {/* ๊ธฐ๋ณธ ๊ณ„์‚ฐ์‹ */} +
+ + (๊ธฐ๋ณธ๊ฐ’) + + โ†’ + updateDefaultFormula(e.target.value)} + placeholder="๊ธฐ๋ณธ ๊ณ„์‚ฐ์‹ (์กฐ๊ฑด ๋ฏธํ•ด๋‹น ์‹œ)" + className="h-7 text-xs flex-1" + /> +
+
+ )} + + {loadingOptions && ( +

์˜ต์…˜ ๋กœ๋”ฉ ์ค‘...

+ )} +
+ )} +
+ ); +}; + // ์˜ต์…˜ ์†Œ์Šค ์„ค์ • ์ปดํฌ๋„ŒํŠธ (๊ฒ€์ƒ‰ ๊ฐ€๋Šฅํ•œ Combobox) interface OptionSourceConfigProps { optionSource: { @@ -3034,46 +3459,15 @@ export function TableSectionSettingsModal({
{(tableConfig.calculations || []).map((calc, index) => ( -
- - = - updateCalculation(index, { formula: e.target.value })} - placeholder="์ˆ˜์‹ (์˜ˆ: quantity * unit_price)" - className="h-8 text-xs flex-1" - /> - -
+ updateCalculation(index, updates)} + onRemove={() => removeCalculation(index)} + /> ))}
diff --git a/frontend/lib/registry/components/universal-form-modal/types.ts b/frontend/lib/registry/components/universal-form-modal/types.ts index a07feed6..3d8cac6c 100644 --- a/frontend/lib/registry/components/universal-form-modal/types.ts +++ b/frontend/lib/registry/components/universal-form-modal/types.ts @@ -604,6 +604,27 @@ export interface ColumnModeConfig { valueMapping: ValueMappingConfig; // ์ด ๋ชจ๋“œ์˜ ๊ฐ’ ๋งคํ•‘ } +/** + * ์กฐ๊ฑด๋ถ€ ๊ณ„์‚ฐ ๊ทœ์น™ + * ํŠน์ • ํ•„๋“œ ๊ฐ’์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ๊ณ„์‚ฐ์‹ ์ ์šฉ + */ +export interface ConditionalCalculationRule { + conditionValue: string; // ์กฐ๊ฑด ๊ฐ’ (์˜ˆ: "๊ตญ๋‚ด", "ํ•ด์™ธ") + formula: string; // ํ•ด๋‹น ์กฐ๊ฑด์ผ ๋•Œ ์‚ฌ์šฉํ•  ๊ณ„์‚ฐ์‹ +} + +/** + * ์กฐ๊ฑด๋ถ€ ๊ณ„์‚ฐ ์„ค์ • + */ +export interface ConditionalCalculationConfig { + enabled: boolean; // ์กฐ๊ฑด๋ถ€ ๊ณ„์‚ฐ ํ™œ์„ฑํ™” ์—ฌ๋ถ€ + conditionField: string; // ์กฐ๊ฑด ๊ธฐ์ค€ ํ•„๋“œ (์˜ˆ: "sales_type") + conditionFieldType?: "static" | "code" | "table"; // ์กฐ๊ฑด ํ•„๋“œ์˜ ์˜ต์…˜ ํƒ€์ž… + conditionFieldCategoryKey?: string; // ์นดํ…Œ๊ณ ๋ฆฌ ํ‚ค (์˜ˆ: "sales_order_mng.sales_type") + rules: ConditionalCalculationRule[]; // ์กฐ๊ฑด๋ณ„ ๊ณ„์‚ฐ ๊ทœ์น™ + defaultFormula?: string; // ์กฐ๊ฑด์— ํ•ด๋‹นํ•˜์ง€ ์•Š์„ ๋•Œ ๊ธฐ๋ณธ ๊ณ„์‚ฐ์‹ +} + /** * ํ…Œ์ด๋ธ” ๊ณ„์‚ฐ ๊ทœ์น™ * ๋‹ค๋ฅธ ์ปฌ๋Ÿผ ๊ฐ’์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์ž๋™ ๊ณ„์‚ฐ @@ -612,6 +633,9 @@ export interface TableCalculationRule { resultField: string; // ๊ฒฐ๊ณผ๋ฅผ ์ €์žฅํ•  ํ•„๋“œ formula: string; // ๊ณ„์‚ฐ ๊ณต์‹ (์˜ˆ: "quantity * unit_price") dependencies: string[]; // ์˜์กดํ•˜๋Š” ํ•„๋“œ๋“ค + + // ์กฐ๊ฑด๋ถ€ ๊ณ„์‚ฐ (์„ ํƒ์‚ฌํ•ญ) + conditionalCalculation?: ConditionalCalculationConfig; } // ๋‹ค์ค‘ ํ–‰ ์ €์žฅ ์„ค์ • From eb868965df4372656276edcf5dfdc7d18966b6b4 Mon Sep 17 00:00:00 2001 From: kjs Date: Wed, 31 Dec 2025 14:17:39 +0900 Subject: [PATCH 5/5] =?UTF-8?q?=EC=A1=B0=EA=B1=B4=EB=B6=80=20=EA=B3=84?= =?UTF-8?q?=EC=82=B0=EC=8B=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modal-repeater-table/RepeaterTable.tsx | 41 ++++++++++--------- .../components/modal-repeater-table/types.ts | 1 + .../TableSectionRenderer.tsx | 1 + .../modals/TableSectionSettingsModal.tsx | 23 ++++------- .../components/universal-form-modal/types.ts | 1 + 5 files changed, 33 insertions(+), 34 deletions(-) diff --git a/frontend/lib/registry/components/modal-repeater-table/RepeaterTable.tsx b/frontend/lib/registry/components/modal-repeater-table/RepeaterTable.tsx index 70b15e7d..995ebccb 100644 --- a/frontend/lib/registry/components/modal-repeater-table/RepeaterTable.tsx +++ b/frontend/lib/registry/components/modal-repeater-table/RepeaterTable.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useState, useEffect, useRef } from "react"; +import React, { useState, useEffect, useRef, useMemo } from "react"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; @@ -84,6 +84,9 @@ export function RepeaterTable({ onSelectionChange, equalizeWidthsTrigger, }: RepeaterTableProps) { + // ํžˆ๋“  ์ปฌ๋Ÿผ์„ ์ œ์™ธํ•œ ํ‘œ์‹œ ๊ฐ€๋Šฅํ•œ ์ปฌ๋Ÿผ๋งŒ ํ•„ํ„ฐ๋ง + const visibleColumns = useMemo(() => columns.filter((col) => !col.hidden), [columns]); + // ์ปจํ…Œ์ด๋„ˆ ref - ์‹ค์ œ ๋„ˆ๋น„ ์ธก์ •์šฉ const containerRef = useRef(null); @@ -145,7 +148,7 @@ export function RepeaterTable({ // ์ปฌ๋Ÿผ ๋„ˆ๋น„ ์ƒํƒœ ๊ด€๋ฆฌ const [columnWidths, setColumnWidths] = useState>(() => { const widths: Record = {}; - columns.forEach((col) => { + columns.filter((col) => !col.hidden).forEach((col) => { widths[col.field] = col.width ? parseInt(col.width) : 120; }); return widths; @@ -154,11 +157,11 @@ export function RepeaterTable({ // ๊ธฐ๋ณธ ๋„ˆ๋น„ ์ €์žฅ (๋ฆฌ์…‹์šฉ) const defaultWidths = React.useMemo(() => { const widths: Record = {}; - columns.forEach((col) => { + visibleColumns.forEach((col) => { widths[col.field] = col.width ? parseInt(col.width) : 120; }); return widths; - }, [columns]); + }, [visibleColumns]); // ๋ฆฌ์‚ฌ์ด์ฆˆ ์ƒํƒœ const [resizing, setResizing] = useState<{ field: string; startX: number; startWidth: number } | null>(null); @@ -206,7 +209,7 @@ export function RepeaterTable({ // ํ•ด๋‹น ์ปฌ๋Ÿผ์˜ ๊ฐ€์žฅ ๊ธด ๊ธ€์ž ๋„ˆ๋น„ ๊ณ„์‚ฐ // equalWidth: ๊ท ๋“ฑ ๋ถ„๋ฐฐ ์‹œ ๋„ˆ๋น„ (๊ฐ’์ด ์—†๋Š” ์ปฌ๋Ÿผ์˜ ์ตœ์†Œ๊ฐ’์œผ๋กœ ์‚ฌ์šฉ) const calculateColumnContentWidth = (field: string, equalWidth: number): number => { - const column = columns.find((col) => col.field === field); + const column = visibleColumns.find((col) => col.field === field); if (!column) return equalWidth; // ๋‚ ์งœ ํ•„๋“œ๋Š” 110px (yyyy-MM-dd) @@ -257,7 +260,7 @@ export function RepeaterTable({ // ํ—ค๋” ๋”๋ธ”ํด๋ฆญ: ํ•ด๋‹น ์ปฌ๋Ÿผ๋งŒ ๊ธ€์ž ๋„ˆ๋น„์— ๋งž์ถค const handleDoubleClick = (field: string) => { const availableWidth = getAvailableWidth(); - const equalWidth = Math.max(60, Math.floor(availableWidth / columns.length)); + const equalWidth = Math.max(60, Math.floor(availableWidth / visibleColumns.length)); const contentWidth = calculateColumnContentWidth(field, equalWidth); setColumnWidths((prev) => ({ ...prev, @@ -268,10 +271,10 @@ export function RepeaterTable({ // ๊ท ๋“ฑ ๋ถ„๋ฐฐ: ์ปฌ๋Ÿผ ์ˆ˜๋กœ ํ…Œ์ด๋ธ” ๋„ˆ๋น„๋ฅผ ๊ท ๋“ฑ ๋ถ„๋ฐฐ const applyEqualizeWidths = () => { const availableWidth = getAvailableWidth(); - const equalWidth = Math.max(60, Math.floor(availableWidth / columns.length)); + const equalWidth = Math.max(60, Math.floor(availableWidth / visibleColumns.length)); const newWidths: Record = {}; - columns.forEach((col) => { + visibleColumns.forEach((col) => { newWidths[col.field] = equalWidth; }); @@ -280,15 +283,15 @@ export function RepeaterTable({ // ์ž๋™ ๋งž์ถค: ๊ฐ ์ปฌ๋Ÿผ์„ ๊ธ€์ž ๋„ˆ๋น„์— ๋งž์ถ”๊ณ , ์ปจํ…Œ์ด๋„ˆ๋ณด๋‹ค ์ž‘์œผ๋ฉด ๋‚จ๋Š” ๊ณต๊ฐ„ ๋ถ„๋ฐฐ const applyAutoFitWidths = () => { - if (columns.length === 0) return; + if (visibleColumns.length === 0) return; // ๊ท ๋“ฑ ๋ถ„๋ฐฐ ๋„ˆ๋น„ ๊ณ„์‚ฐ (๊ฐ’์ด ์—†๋Š” ์ปฌ๋Ÿผ์˜ ์ตœ์†Œ๊ฐ’) const availableWidth = getAvailableWidth(); - const equalWidth = Math.max(60, Math.floor(availableWidth / columns.length)); + const equalWidth = Math.max(60, Math.floor(availableWidth / visibleColumns.length)); // 1. ๊ฐ ์ปฌ๋Ÿผ์˜ ๊ธ€์ž ๋„ˆ๋น„ ๊ณ„์‚ฐ (๊ฐ’์ด ์—†์œผ๋ฉด ๊ท ๋“ฑ ๋ถ„๋ฐฐ ๋„ˆ๋น„ ์‚ฌ์šฉ) const newWidths: Record = {}; - columns.forEach((col) => { + visibleColumns.forEach((col) => { newWidths[col.field] = calculateColumnContentWidth(col.field, equalWidth); }); @@ -298,8 +301,8 @@ export function RepeaterTable({ // 3. ์ปจํ…Œ์ด๋„ˆ๋ณด๋‹ค ์ž‘์œผ๋ฉด ๋‚จ๋Š” ๊ณต๊ฐ„์„ ๊ท ๋“ฑ ๋ถ„๋ฐฐ (ํ…Œ์ด๋ธ” ๊ฝ‰ ์ฐธ ์œ ์ง€) if (totalContentWidth < availableWidth) { const extraSpace = availableWidth - totalContentWidth; - const extraPerColumn = Math.floor(extraSpace / columns.length); - columns.forEach((col) => { + const extraPerColumn = Math.floor(extraSpace / visibleColumns.length); + visibleColumns.forEach((col) => { newWidths[col.field] += extraPerColumn; }); } @@ -311,7 +314,7 @@ export function RepeaterTable({ // ์ดˆ๊ธฐ ๋งˆ์šดํŠธ ์‹œ ๊ท ๋“ฑ ๋ถ„๋ฐฐ ์ ์šฉ useEffect(() => { if (initializedRef.current) return; - if (!containerRef.current || columns.length === 0) return; + if (!containerRef.current || visibleColumns.length === 0) return; const timer = setTimeout(() => { applyEqualizeWidths(); @@ -319,7 +322,7 @@ export function RepeaterTable({ }, 100); return () => clearTimeout(timer); - }, [columns]); + }, [visibleColumns]); // ํŠธ๋ฆฌ๊ฑฐ ๊ฐ์ง€: 1=๊ท ๋“ฑ๋ถ„๋ฐฐ, 2=์ž๋™๋งž์ถค useEffect(() => { @@ -357,7 +360,7 @@ export function RepeaterTable({ document.removeEventListener("mousemove", handleMouseMove); document.removeEventListener("mouseup", handleMouseUp); }; - }, [resizing, columns, data]); + }, [resizing, visibleColumns, data]); // ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ ๊ฐ์ง€ (ํ•„์š”์‹œ ํ™œ์„ฑํ™”) // useEffect(() => { @@ -531,7 +534,7 @@ export function RepeaterTable({ className={cn("border-gray-400", isIndeterminate && "data-[state=checked]:bg-primary")} /> - {columns.map((col) => { + {visibleColumns.map((col) => { const hasDynamicSource = col.dynamicDataSource?.enabled && col.dynamicDataSource.options.length > 0; const activeOptionId = activeDataSources[col.field] || col.dynamicDataSource?.defaultOptionId; const activeOption = hasDynamicSource @@ -631,7 +634,7 @@ export function RepeaterTable({ {data.length === 0 ? ( ์ถ”๊ฐ€๋œ ํ•ญ๋ชฉ์ด ์—†์Šต๋‹ˆ๋‹ค @@ -672,7 +675,7 @@ export function RepeaterTable({ /> {/* ๋ฐ์ดํ„ฐ ์ปฌ๋Ÿผ๋“ค */} - {columns.map((col) => ( + {visibleColumns.map((col) => ( = ({ // ์†Œ์Šค ํ…Œ์ด๋ธ”์˜ ์นดํ…Œ๊ณ ๋ฆฌ ์ปฌ๋Ÿผ ์ •๋ณด ๋กœ๋“œ useEffect(() => { const loadCategoryColumns = async () => { - console.log("[CalculationRuleEditor] sourceTableName:", sourceTableName); - if (!sourceTableName) { setCategoryColumns({}); return; @@ -92,7 +90,6 @@ const CalculationRuleEditor: React.FC = ({ try { const { getCategoryColumns } = await import("@/lib/api/tableCategoryValue"); const result = await getCategoryColumns(sourceTableName); - console.log("[CalculationRuleEditor] getCategoryColumns ๊ฒฐ๊ณผ:", result); if (result && result.success && Array.isArray(result.data)) { const categoryMap: Record = {}; @@ -103,7 +100,6 @@ const CalculationRuleEditor: React.FC = ({ categoryMap[colName] = true; } }); - console.log("[CalculationRuleEditor] categoryMap:", categoryMap); setCategoryColumns(categoryMap); } } catch (error) { @@ -128,29 +124,18 @@ const CalculationRuleEditor: React.FC = ({ const selectedColumn = columns.find((col) => col.field === conditionField); const actualFieldName = selectedColumn?.sourceField || conditionField; - console.log("[loadConditionOptions] ์กฐ๊ฑด ํ•„๋“œ:", { - conditionField, - actualFieldName, - sourceTableName, - categoryColumnsKeys: Object.keys(categoryColumns), - isCategoryColumn: categoryColumns[actualFieldName], - }); - // ์†Œ์Šค ํ…Œ์ด๋ธ”์—์„œ ํ•ด๋‹น ์ปฌ๋Ÿผ์ด ์นดํ…Œ๊ณ ๋ฆฌ ํƒ€์ž…์ธ์ง€ ํ™•์ธ if (sourceTableName && categoryColumns[actualFieldName]) { try { setLoadingOptions(true); const { getCategoryValues } = await import("@/lib/api/tableCategoryValue"); - console.log("[loadConditionOptions] getCategoryValues ํ˜ธ์ถœ:", sourceTableName, actualFieldName); const result = await getCategoryValues(sourceTableName, actualFieldName, false); - console.log("[loadConditionOptions] getCategoryValues ๊ฒฐ๊ณผ:", result); if (result && result.success && Array.isArray(result.data)) { const options = result.data.map((item: any) => ({ // API ์‘๋‹ต์€ camelCase (valueCode, valueLabel) value: item.valueCode || item.value_code || item.value, label: item.valueLabel || item.displayLabel || item.display_label || item.label || item.valueCode || item.value_code || item.value, })); - console.log("[loadConditionOptions] ๋งคํ•‘๋œ ์˜ต์…˜:", options); setCategoryOptions(options); } else { setCategoryOptions([]); @@ -1094,6 +1079,14 @@ function ColumnSettingItem({ /> ํ•„์ˆ˜ +