From f8fb7d687e3b660a9dc67757599f9dcab836dcd9 Mon Sep 17 00:00:00 2001 From: SeongHyun Kim Date: Fri, 9 Jan 2026 17:42:33 +0900 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20SelectedItemsDetailInput=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=AA=A8=EB=93=9C=EC=97=90=EC=84=9C=20=EB=8B=A4?= =?UTF-8?q?=EC=A4=91=20=EB=A0=88=EC=BD=94=EB=93=9C=20=EB=A1=9C=EB=93=9C=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95=20DynamicComponentRende?= =?UTF-8?q?rer=EC=97=90=20selected-items-detail-input=20groupedData=20?= =?UTF-8?q?=EC=A0=84=EB=8B=AC=20=EC=B6=94=EA=B0=80=20SelectedItemsDetailIn?= =?UTF-8?q?put=EC=97=90=EC=84=9C=20groupedData=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=20ScreenModal=20editData=20=EB=B0=B0=EC=97=B4=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EC=8B=9C=20formData/selectedData=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC=20=EC=A0=80=EC=9E=A5=20TextInput=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=AA=A8=EB=93=9C=EC=97=90=EC=84=9C=20=EC=B1=84?= =?UTF-8?q?=EB=B2=88=20=EA=B7=9C=EC=B9=99=20=EC=8A=A4=ED=82=B5=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/common/ScreenModal.tsx | 16 +++++++++---- .../lib/registry/DynamicComponentRenderer.tsx | 8 ++++--- .../SelectedItemsDetailInputComponent.tsx | 24 +++++++++++++------ .../text-input/TextInputComponent.tsx | 16 ++++++++++++- 4 files changed, 49 insertions(+), 15 deletions(-) diff --git a/frontend/components/common/ScreenModal.tsx b/frontend/components/common/ScreenModal.tsx index f7926f43..44685dc0 100644 --- a/frontend/components/common/ScreenModal.tsx +++ b/frontend/components/common/ScreenModal.tsx @@ -175,13 +175,21 @@ export const ScreenModal: React.FC = ({ className }) => { if (editData) { console.log("๐Ÿ“ [ScreenModal] ์ˆ˜์ • ๋ฐ์ดํ„ฐ ์„ค์ •:", editData); - // ๐Ÿ†• ๋ฐฐ์—ด์ธ ๊ฒฝ์šฐ (๊ทธ๋ฃน ๋ ˆ์ฝ”๋“œ) vs ๋‹จ์ผ ๊ฐ์ฒด ์ฒ˜๋ฆฌ + // ๐Ÿ†• ๋ฐฐ์—ด์ธ ๊ฒฝ์šฐ ๋‘ ๊ฐ€์ง€ ๋ฐ์ดํ„ฐ๋ฅผ ์„ค์ •: + // 1. formData: ์ฒซ ๋ฒˆ์งธ ์š”์†Œ(๊ฐ์ฒด) - ์ผ๋ฐ˜ ์ž…๋ ฅ ํ•„๋“œ์šฉ (TextInput ๋“ฑ) + // 2. selectedData: ์ „์ฒด ๋ฐฐ์—ด - ๋‹ค์ค‘ ํ•ญ๋ชฉ ์ปดํฌ๋„ŒํŠธ์šฉ (SelectedItemsDetailInput ๋“ฑ) if (Array.isArray(editData)) { - console.log(`๐Ÿ“ [ScreenModal] ๊ทธ๋ฃน ๋ ˆ์ฝ”๋“œ ${editData.length}๊ฐœ ์„ค์ •`); - setFormData(editData as any); // ๋ฐฐ์—ด ๊ทธ๋Œ€๋กœ ์ „๋‹ฌ (SelectedItemsDetailInput์—์„œ ์ฒ˜๋ฆฌ) - setOriginalData(editData[0] || null); // ์ฒซ ๋ฒˆ์งธ ๋ ˆ์ฝ”๋“œ๋ฅผ ์›๋ณธ์œผ๋กœ ์ €์žฅ + const firstRecord = editData[0] || {}; + console.log(`๐Ÿ“ [ScreenModal] ๊ทธ๋ฃน ๋ ˆ์ฝ”๋“œ ${editData.length}๊ฐœ ์„ค์ •:`, { + formData: "์ฒซ ๋ฒˆ์งธ ๋ ˆ์ฝ”๋“œ (์ผ๋ฐ˜ ์ž…๋ ฅ ํ•„๋“œ์šฉ)", + selectedData: "์ „์ฒด ๋ฐฐ์—ด (๋‹ค์ค‘ ํ•ญ๋ชฉ ์ปดํฌ๋„ŒํŠธ์šฉ)", + }); + setFormData(firstRecord); // ๐Ÿ”ง ์ผ๋ฐ˜ ์ž…๋ ฅ ํ•„๋“œ์šฉ (๊ฐ์ฒด) + setSelectedData(editData); // ๐Ÿ”ง ๋‹ค์ค‘ ํ•ญ๋ชฉ ์ปดํฌ๋„ŒํŠธ์šฉ (๋ฐฐ์—ด) - groupedData๋กœ ์ „๋‹ฌ๋จ + setOriginalData(firstRecord); // ์ฒซ ๋ฒˆ์งธ ๋ ˆ์ฝ”๋“œ๋ฅผ ์›๋ณธ์œผ๋กœ ์ €์žฅ } else { setFormData(editData); + setSelectedData([editData]); // ๐Ÿ”ง ๋‹จ์ผ ๊ฐ์ฒด๋„ ๋ฐฐ์—ด๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ์ €์žฅ setOriginalData(editData); // ๐Ÿ†• ์›๋ณธ ๋ฐ์ดํ„ฐ ์ €์žฅ (UPDATE ํŒ๋‹จ์šฉ) } } else { diff --git a/frontend/lib/registry/DynamicComponentRenderer.tsx b/frontend/lib/registry/DynamicComponentRenderer.tsx index 6166317f..3d8b2d4e 100644 --- a/frontend/lib/registry/DynamicComponentRenderer.tsx +++ b/frontend/lib/registry/DynamicComponentRenderer.tsx @@ -281,10 +281,12 @@ export const DynamicComponentRenderer: React.FC = // ์ปดํฌ๋„ŒํŠธ์˜ columnName์— ํ•ด๋‹นํ•˜๋Š” formData ๊ฐ’ ์ถ”์ถœ const fieldName = (component as any).columnName || component.id; - // modal-repeater-table์€ ๋ฐฐ์—ด ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค๋ฃจ๋ฏ€๋กœ ๋นˆ ๋ฐฐ์—ด๋กœ ์ดˆ๊ธฐํ™” + // ๋‹ค์ค‘ ๋ ˆ์ฝ”๋“œ๋ฅผ ๋‹ค๋ฃจ๋Š” ์ปดํฌ๋„ŒํŠธ๋Š” ๋ฐฐ์—ด ๋ฐ์ดํ„ฐ๋กœ ์ดˆ๊ธฐํ™” let currentValue; - if (componentType === "modal-repeater-table" || componentType === "repeat-screen-modal") { - // EditModal์—์„œ ์ „๋‹ฌ๋œ groupedData๊ฐ€ ์žˆ์œผ๋ฉด ์šฐ์„  ์‚ฌ์šฉ + if (componentType === "modal-repeater-table" || + componentType === "repeat-screen-modal" || + componentType === "selected-items-detail-input") { + // EditModal/ScreenModal์—์„œ ์ „๋‹ฌ๋œ groupedData๊ฐ€ ์žˆ์œผ๋ฉด ์šฐ์„  ์‚ฌ์šฉ currentValue = props.groupedData || formData?.[fieldName] || []; } else { currentValue = formData?.[fieldName] || ""; diff --git a/frontend/lib/registry/components/selected-items-detail-input/SelectedItemsDetailInputComponent.tsx b/frontend/lib/registry/components/selected-items-detail-input/SelectedItemsDetailInputComponent.tsx index 19073e39..285c655d 100644 --- a/frontend/lib/registry/components/selected-items-detail-input/SelectedItemsDetailInputComponent.tsx +++ b/frontend/lib/registry/components/selected-items-detail-input/SelectedItemsDetailInputComponent.tsx @@ -42,6 +42,8 @@ export const SelectedItemsDetailInputComponent: React.FC { + // ๐Ÿ†• groupedData ์ถ”์ถœ (DynamicComponentRenderer์—์„œ ์ „๋‹ฌ) + const groupedData = (props as any).groupedData || (props as any)._groupedData; // ๐Ÿ†• URL ํŒŒ๋ผ๋ฏธํ„ฐ์—์„œ dataSourceId ์ฝ๊ธฐ const searchParams = useSearchParams(); const urlDataSourceId = searchParams?.get("dataSourceId") || undefined; @@ -225,24 +227,32 @@ export const SelectedItemsDetailInputComponent: React.FC { - // ๐Ÿ†• ์ˆ˜์ • ๋ชจ๋“œ: formData์—์„œ ๋ฐ์ดํ„ฐ ๋กœ๋“œ (URL์— mode=edit์ด ์žˆ์œผ๋ฉด) + // ๐Ÿ†• ์ˆ˜์ • ๋ชจ๋“œ: groupedData ๋˜๋Š” formData์—์„œ ๋ฐ์ดํ„ฐ ๋กœ๋“œ (URL์— mode=edit์ด ์žˆ์œผ๋ฉด) const urlParams = new URLSearchParams(window.location.search); const mode = urlParams.get("mode"); - if (mode === "edit" && formData) { + // ๐Ÿ”ง ๋ฐ์ดํ„ฐ ์†Œ์Šค ์šฐ์„ ์ˆœ์œ„: groupedData > formData (๋ฐฐ์—ด) > formData (๊ฐ์ฒด) + const sourceData = groupedData && Array.isArray(groupedData) && groupedData.length > 0 + ? groupedData + : formData; + + if (mode === "edit" && sourceData) { // ๋ฐฐ์—ด์ธ์ง€ ๋‹จ์ผ ๊ฐ์ฒด์ธ์ง€ ํ™•์ธ - const isArray = Array.isArray(formData); - const dataArray = isArray ? formData : [formData]; + const isArray = Array.isArray(sourceData); + const dataArray = isArray ? sourceData : [sourceData]; if (dataArray.length === 0 || (dataArray.length === 1 && Object.keys(dataArray[0]).length === 0)) { - console.warn("โš ๏ธ [SelectedItemsDetailInput] formData๊ฐ€ ๋น„์–ด์žˆ์Œ"); + console.warn("โš ๏ธ [SelectedItemsDetailInput] ๋ฐ์ดํ„ฐ๊ฐ€ ๋น„์–ด์žˆ์Œ"); return; } console.log( `๐Ÿ“ [SelectedItemsDetailInput] ์ˆ˜์ • ๋ชจ๋“œ - ${isArray ? "๊ทธ๋ฃน ๋ ˆ์ฝ”๋“œ" : "๋‹จ์ผ ๋ ˆ์ฝ”๋“œ"} (${dataArray.length}๊ฐœ)`, ); - console.log("๐Ÿ“ [SelectedItemsDetailInput] formData (JSON):", JSON.stringify(dataArray, null, 2)); + console.log("๐Ÿ“ [SelectedItemsDetailInput] ๋ฐ์ดํ„ฐ ์†Œ์Šค:", { + fromGroupedData: groupedData && Array.isArray(groupedData) && groupedData.length > 0, + dataArray: JSON.stringify(dataArray, null, 2), + }); const groups = componentConfig.fieldGroups || []; const additionalFields = componentConfig.additionalFields || []; @@ -423,7 +433,7 @@ export const SelectedItemsDetailInputComponent: React.FC = ({ // ์ˆจ๊น€ ์ƒํƒœ (props์—์„œ ์ „๋‹ฌ๋ฐ›์€ ๊ฐ’ ์šฐ์„  ์‚ฌ์šฉ) const isHidden = props.hidden !== undefined ? props.hidden : component.hidden || componentConfig.hidden || false; + // ์ˆ˜์ • ๋ชจ๋“œ ์—ฌ๋ถ€ ํ™•์ธ (originalData๊ฐ€ ์žˆ์œผ๋ฉด ์ˆ˜์ • ๋ชจ๋“œ) + const originalData = props.originalData || (props as any)._originalData; + const isEditMode = originalData && Object.keys(originalData).length > 0; + // ์ž๋™์ƒ์„ฑ๋œ ๊ฐ’ ์ƒํƒœ const [autoGeneratedValue, setAutoGeneratedValue] = useState(""); @@ -99,6 +103,16 @@ export const TextInputComponent: React.FC = ({ return; } + // ๐Ÿ†• ์ˆ˜์ • ๋ชจ๋“œ์ผ ๋•Œ๋Š” ์ฑ„๋ฒˆ ๊ทœ์น™ ์Šคํ‚ต (๊ธฐ์กด ๊ฐ’ ์œ ์ง€) + if (isEditMode) { + console.log("โญ๏ธ ์ˆ˜์ • ๋ชจ๋“œ - ์ฑ„๋ฒˆ ๊ทœ์น™ ์Šคํ‚ต:", { + columnName: component.columnName, + originalValue: originalData?.[component.columnName], + }); + hasGeneratedRef.current = true; // ์ƒ์„ฑ ์™„๋ฃŒ๋กœ ํ‘œ์‹œํ•˜์—ฌ ์žฌ์‹คํ–‰ ๋ฐฉ์ง€ + return; + } + if (testAutoGeneration.enabled && testAutoGeneration.type !== "none") { // ํผ ๋ฐ์ดํ„ฐ์— ์ด๋ฏธ ๊ฐ’์ด ์žˆ์œผ๋ฉด ์ž๋™์ƒ์„ฑํ•˜์ง€ ์•Š์Œ const currentFormValue = formData?.[component.columnName]; @@ -171,7 +185,7 @@ export const TextInputComponent: React.FC = ({ }; generateAutoValue(); - }, [testAutoGeneration.enabled, testAutoGeneration.type, component.columnName, isInteractive]); + }, [testAutoGeneration.enabled, testAutoGeneration.type, component.columnName, isInteractive, isEditMode]); // ์‹ค์ œ ํ™”๋ฉด์—์„œ ์ˆจ๊น€ ์ฒ˜๋ฆฌ๋œ ์ปดํฌ๋„ŒํŠธ๋Š” ๋ Œ๋”๋งํ•˜์ง€ ์•Š์Œ if (isHidden && !isDesignMode) { From 3677c77da01224b531289df20d1e7414f9906b34 Mon Sep 17 00:00:00 2001 From: kjs Date: Fri, 9 Jan 2026 17:56:48 +0900 Subject: [PATCH 2/2] =?UTF-8?q?=EB=8B=A8=EC=9D=BC=ED=85=8C=EC=9D=B4?= =?UTF-8?q?=EB=B8=94=20=EC=97=91=EC=85=80=20=EC=97=85=EB=A1=9C=EB=93=9C=20?= =?UTF-8?q?=EC=B1=84=EB=B2=88=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/common/ExcelUploadModal.tsx | 65 ++- .../config-panels/ButtonConfigPanel.tsx | 468 ++++++++++++------ frontend/lib/utils/buttonActions.ts | 14 +- 3 files changed, 402 insertions(+), 145 deletions(-) diff --git a/frontend/components/common/ExcelUploadModal.tsx b/frontend/components/common/ExcelUploadModal.tsx index 6785eac8..6eda1594 100644 --- a/frontend/components/common/ExcelUploadModal.tsx +++ b/frontend/components/common/ExcelUploadModal.tsx @@ -84,6 +84,11 @@ export interface ExcelUploadModalProps { }; // ๐Ÿ†• ๋งˆ์Šคํ„ฐ-๋””ํ…Œ์ผ ์—‘์…€ ์—…๋กœ๋“œ ์„ค์ • masterDetailExcelConfig?: MasterDetailExcelConfig; + // ๐Ÿ†• ๋‹จ์ผ ํ…Œ์ด๋ธ” ์ฑ„๋ฒˆ ์„ค์ • + numberingRuleId?: string; + numberingTargetColumn?: string; + // ๐Ÿ†• ์—…๋กœ๋“œ ํ›„ ์ œ์–ด ์‹คํ–‰ ์„ค์ • + afterUploadFlows?: Array<{ flowId: string; order: number }>; } interface ColumnMapping { @@ -103,6 +108,11 @@ export const ExcelUploadModal: React.FC = ({ isMasterDetail = false, masterDetailRelation, masterDetailExcelConfig, + // ๋‹จ์ผ ํ…Œ์ด๋ธ” ์ฑ„๋ฒˆ ์„ค์ • + numberingRuleId, + numberingTargetColumn, + // ์—…๋กœ๋“œ ํ›„ ์ œ์–ด ์‹คํ–‰ ์„ค์ • + afterUploadFlows, }) => { const [currentStep, setCurrentStep] = useState(1); @@ -695,13 +705,48 @@ export const ExcelUploadModal: React.FC = ({ } } else { // ๊ธฐ์กด ๋‹จ์ผ ํ…Œ์ด๋ธ” ์—…๋กœ๋“œ ๋กœ์ง + console.log("๐Ÿ“Š ๋‹จ์ผ ํ…Œ์ด๋ธ” ์—…๋กœ๋“œ ์‹œ์ž‘:", { + tableName, + uploadMode, + numberingRuleId, + numberingTargetColumn, + dataCount: filteredData.length, + }); + let successCount = 0; let failCount = 0; + // ๐Ÿ†• ๋‹จ์ผ ํ…Œ์ด๋ธ” ์ฑ„๋ฒˆ ์„ค์ • ํ™•์ธ + const hasNumbering = numberingRuleId && numberingTargetColumn; + console.log("๐Ÿ“Š ์ฑ„๋ฒˆ ์„ค์ •:", { hasNumbering, numberingRuleId, numberingTargetColumn }); + for (const row of filteredData) { try { + let dataToSave = { ...row }; + + // ๐Ÿ†• ์ฑ„๋ฒˆ ์ ์šฉ: ๊ฐ ํ–‰๋งˆ๋‹ค ์ฑ„๋ฒˆ API ํ˜ธ์ถœ + if (hasNumbering && uploadMode === "insert") { + try { + const { apiClient } = await import("@/lib/api/client"); + console.log(`๐Ÿ“Š ์ฑ„๋ฒˆ API ํ˜ธ์ถœ: /numbering-rules/${numberingRuleId}/allocate`); + const numberingResponse = await apiClient.post(`/numbering-rules/${numberingRuleId}/allocate`); + console.log(`๐Ÿ“Š ์ฑ„๋ฒˆ API ์‘๋‹ต:`, numberingResponse.data); + // ์‘๋‹ต ๊ตฌ์กฐ: { success: true, data: { generatedCode: "..." } } + const generatedCode = numberingResponse.data?.data?.generatedCode || numberingResponse.data?.data?.code; + if (numberingResponse.data?.success && generatedCode) { + dataToSave[numberingTargetColumn] = generatedCode; + console.log(`โœ… ์ฑ„๋ฒˆ ์ ์šฉ: ${numberingTargetColumn} = ${generatedCode}`); + } else { + console.warn(`โš ๏ธ ์ฑ„๋ฒˆ ์‹คํŒจ: ์‘๋‹ต์— ์ฝ”๋“œ ์—†์Œ`, numberingResponse.data); + } + } catch (numError) { + console.error("์ฑ„๋ฒˆ ์˜ค๋ฅ˜:", numError); + // ์ฑ„๋ฒˆ ์‹คํŒจ ์‹œ์—๋„ ๊ณ„์† ์ง„ํ–‰ (์ฑ„๋ฒˆ ์ปฌ๋Ÿผ๋งŒ ๋น„์›Œ๋‘ ) + } + } + if (uploadMode === "insert") { - const formData = { screenId: 0, tableName, data: row }; + const formData = { screenId: 0, tableName, data: dataToSave }; const result = await DynamicFormApi.saveFormData(formData); if (result.success) { successCount++; @@ -714,6 +759,24 @@ export const ExcelUploadModal: React.FC = ({ } } + // ๐Ÿ†• ์—…๋กœ๋“œ ํ›„ ์ œ์–ด ์‹คํ–‰ + if (afterUploadFlows && afterUploadFlows.length > 0 && successCount > 0) { + console.log("๐Ÿ”„ ์—…๋กœ๋“œ ํ›„ ์ œ์–ด ์‹คํ–‰:", afterUploadFlows); + try { + const { apiClient } = await import("@/lib/api/client"); + // ์ˆœ์„œ๋Œ€๋กœ ์‹คํ–‰ + const sortedFlows = [...afterUploadFlows].sort((a, b) => a.order - b.order); + for (const flow of sortedFlows) { + await apiClient.post(`/dataflow/node-flows/${flow.flowId}/execute`, { + sourceData: { tableName, uploadedCount: successCount }, + }); + console.log(`โœ… ์ œ์–ด ์‹คํ–‰ ์™„๋ฃŒ: flowId=${flow.flowId}`); + } + } catch (controlError) { + console.error("์ œ์–ด ์‹คํ–‰ ์˜ค๋ฅ˜:", controlError); + } + } + if (successCount > 0) { toast.success( `${successCount}๊ฐœ ํ–‰์ด ์—…๋กœ๋“œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.${failCount > 0 ? ` (์‹คํŒจ: ${failCount}๊ฐœ)` : ""}` diff --git a/frontend/components/screen/config-panels/ButtonConfigPanel.tsx b/frontend/components/screen/config-panels/ButtonConfigPanel.tsx index 8d8c4df9..eabeb59f 100644 --- a/frontend/components/screen/config-panels/ButtonConfigPanel.tsx +++ b/frontend/components/screen/config-panels/ButtonConfigPanel.tsx @@ -5,7 +5,6 @@ import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; -import { Checkbox } from "@/components/ui/checkbox"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"; import { Button } from "@/components/ui/button"; @@ -2026,7 +2025,12 @@ export const ButtonConfigPanel: React.FC = ({ {/* ์—‘์…€ ์—…๋กœ๋“œ ์•ก์…˜ ์„ค์ • */} {(component.componentConfig?.action?.type || "save") === "excel_upload" && ( - + )} {/* ๋ฐ”์ฝ”๋“œ ์Šค์บ” ์•ก์…˜ ์„ค์ • */} @@ -3311,7 +3315,6 @@ const MasterDetailExcelUploadConfig: React.FC<{ onUpdateProperty: (path: string, value: any) => void; allComponents: ComponentData[]; }> = ({ config, onUpdateProperty, allComponents }) => { - const [numberingRules, setNumberingRules] = useState([]); const [relationInfo, setRelationInfo] = useState<{ masterTable: string; detailTable: string; @@ -3319,7 +3322,6 @@ const MasterDetailExcelUploadConfig: React.FC<{ detailFkColumn: string; } | null>(null); const [loading, setLoading] = useState(false); - const [numberingRuleOpen, setNumberingRuleOpen] = useState(false); const [masterColumns, setMasterColumns] = useState< Array<{ columnName: string; @@ -3357,22 +3359,6 @@ const MasterDetailExcelUploadConfig: React.FC<{ const masterTable = splitPanelInfo?.leftPanel?.tableName || ""; const detailTable = splitPanelInfo?.rightPanel?.tableName || ""; - // ์ฑ„๋ฒˆ ๊ทœ์น™ ๋กœ๋“œ - useEffect(() => { - const loadNumberingRules = async () => { - try { - const { apiClient } = await import("@/lib/api/client"); - const response = await apiClient.get("/numbering-rules"); - if (response.data?.success && response.data?.data) { - setNumberingRules(response.data.data); - } - } catch (error) { - console.error("์ฑ„๋ฒˆ ๊ทœ์น™ ๋กœ๋“œ ์‹คํŒจ:", error); - } - }; - loadNumberingRules(); - }, []); - // ๋งˆ์Šคํ„ฐ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ๋กœ๋“œ useEffect(() => { if (!masterTable) { @@ -3547,86 +3533,12 @@ const MasterDetailExcelUploadConfig: React.FC<{ )} - {/* ์ฑ„๋ฒˆ ๊ทœ์น™ ์„ ํƒ - ์œ ์ผํ•˜๊ฒŒ ์‚ฌ์šฉ์ž๊ฐ€ ์„ค์ •ํ•˜๋Š” ํ•ญ๋ชฉ */} + {/* ๋งˆ์Šคํ„ฐ ํ‚ค ์ž๋™ ์ƒ์„ฑ ์•ˆ๋‚ด */} {relationInfo && ( -
- - - - - - - - - - ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์—†์Œ - - { - updateMasterDetailConfig({ numberingRuleId: undefined }); - setNumberingRuleOpen(false); - }} - className="text-xs" - > - - ์ฑ„๋ฒˆ ์—†์Œ (์ˆ˜๋™ ์ž…๋ ฅ) - - {numberingRules - .filter((rule) => rule.table_name === masterTable || !rule.table_name) - .map((rule, idx) => { - const ruleId = String(rule.rule_id || rule.ruleId || `rule-${idx}`); - const ruleName = rule.rule_name || rule.ruleName || "(์ด๋ฆ„ ์—†์Œ)"; - return ( - { - updateMasterDetailConfig({ numberingRuleId: ruleId }); - setNumberingRuleOpen(false); - }} - className="text-xs" - > - - {ruleName} - - ); - })} - - - - - -

- ๋งˆ์Šคํ„ฐ ํ…Œ์ด๋ธ”์˜ {relationInfo.masterKeyColumn} ๊ฐ’์„ ์ž๋™ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค -

-
+

+ ๋งˆ์Šคํ„ฐ ํ…Œ์ด๋ธ”์˜ {relationInfo.masterKeyColumn} ๊ฐ’์€ ์œ„์—์„œ ์„ค์ •ํ•œ ์ฑ„๋ฒˆ ๊ทœ์น™์œผ๋กœ ์ž๋™ + ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. +

)} {/* ๋งˆ์Šคํ„ฐ ํ•„๋“œ ์„ ํƒ - ์‚ฌ์šฉ์ž๊ฐ€ ์—‘์…€ ์—…๋กœ๋“œ ์‹œ ์ž…๋ ฅํ•  ํ•„๋“œ */} @@ -3726,14 +3638,6 @@ const MasterDetailExcelUploadConfig: React.FC<{

์ฐธ์กฐ ํ…Œ์ด๋ธ”์—์„œ ์‚ฌ์šฉ์ž์—๊ฒŒ ํ‘œ์‹œํ•  ์ปฌ๋Ÿผ์„ ์„ ํƒํ•˜์„ธ์š”.

)} - - {/* ์—…๋กœ๋“œ ํ›„ ์ œ์–ด ์‹คํ–‰ ์„ค์ • */} - )} @@ -3741,23 +3645,181 @@ const MasterDetailExcelUploadConfig: React.FC<{ }; /** - * ์—…๋กœ๋“œ ํ›„ ์ œ์–ด ์‹คํ–‰ ์„ค์ • ์ปดํฌ๋„ŒํŠธ - * ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์ œ์–ด๋ฅผ ์ˆœ์„œ๋Œ€๋กœ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ง€์› + * ์—‘์…€ ์—…๋กœ๋“œ ์ฑ„๋ฒˆ ๊ทœ์น™ ์„ค์ • (๋‹จ์ผ ํ…Œ์ด๋ธ”/๋งˆ์Šคํ„ฐ-๋””ํ…Œ์ผ ๋ชจ๋‘ ์‚ฌ์šฉ ๊ฐ€๋Šฅ) */ -const AfterUploadControlConfig: React.FC<{ - config: any; - onUpdateProperty: (path: string, value: any) => void; - masterDetailConfig: any; - updateMasterDetailConfig: (updates: any) => void; -}> = ({ masterDetailConfig, updateMasterDetailConfig }) => { - const [nodeFlows, setNodeFlows] = useState< - Array<{ flowId: number; flowName: string; flowDescription?: string }> - >([]); +const ExcelNumberingRuleConfig: React.FC<{ + config: { numberingRuleId?: string; numberingTargetColumn?: string }; + updateConfig: (updates: { numberingRuleId?: string; numberingTargetColumn?: string }) => void; + tableName?: string; // ๋‹จ์ผ ํ…Œ์ด๋ธ”์ธ ๊ฒฝ์šฐ ํ…Œ์ด๋ธ”๋ช… + hasSplitPanel?: boolean; // ๋ถ„ํ•  ํŒจ๋„ ์—ฌ๋ถ€ (๋งˆ์Šคํ„ฐ-๋””ํ…Œ์ผ) +}> = ({ config, updateConfig, tableName, hasSplitPanel }) => { + const [numberingRules, setNumberingRules] = useState([]); + const [ruleSelectOpen, setRuleSelectOpen] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [tableColumns, setTableColumns] = useState>([]); + const [columnsLoading, setColumnsLoading] = useState(false); + + // ์ฑ„๋ฒˆ ๊ทœ์น™ ๋ชฉ๋ก ๋กœ๋“œ + useEffect(() => { + const loadNumberingRules = async () => { + setIsLoading(true); + try { + const { apiClient } = await import("@/lib/api/client"); + const response = await apiClient.get("/numbering-rules"); + if (response.data?.success && response.data?.data) { + setNumberingRules(response.data.data); + } + } catch (error) { + console.error("์ฑ„๋ฒˆ ๊ทœ์น™ ๋ชฉ๋ก ๋กœ๋“œ ์‹คํŒจ:", error); + } finally { + setIsLoading(false); + } + }; + + loadNumberingRules(); + }, []); + + // ๋‹จ์ผ ํ…Œ์ด๋ธ”์ธ ๊ฒฝ์šฐ ์ปฌ๋Ÿผ ๋ชฉ๋ก ๋กœ๋“œ + useEffect(() => { + if (!tableName || hasSplitPanel) { + setTableColumns([]); + return; + } + + const loadColumns = async () => { + setColumnsLoading(true); + try { + const { apiClient } = await import("@/lib/api/client"); + const response = await apiClient.get(`/table-management/tables/${tableName}/columns`); + if (response.data?.success && response.data?.data?.columns) { + const cols = response.data.data.columns.map((col: any) => ({ + columnName: col.columnName || col.column_name, + columnLabel: col.displayName || col.columnLabel || col.column_label || col.columnName || col.column_name, + })); + setTableColumns(cols); + } + } catch (error) { + console.error("์ปฌ๋Ÿผ ๋ชฉ๋ก ๋กœ๋“œ ์‹คํŒจ:", error); + } finally { + setColumnsLoading(false); + } + }; + + loadColumns(); + }, [tableName, hasSplitPanel]); + + const selectedRule = numberingRules.find((r) => String(r.rule_id || r.ruleId) === String(config.numberingRuleId)); + + return ( +
+ +

+ ์—…๋กœ๋“œ ์‹œ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•  ์ฝ”๋“œ/๋ฒˆํ˜ธ์˜ ์ฑ„๋ฒˆ ๊ทœ์น™์„ ์„ ํƒํ•˜์„ธ์š”. +

+ + + + + + + + + + ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์—†์Œ + + { + updateConfig({ numberingRuleId: undefined, numberingTargetColumn: undefined }); + setRuleSelectOpen(false); + }} + className="text-xs" + > + + ์ฑ„๋ฒˆ ์—†์Œ + + {numberingRules.map((rule, idx) => { + const ruleId = String(rule.rule_id || rule.ruleId || `rule-${idx}`); + const ruleName = rule.rule_name || rule.ruleName || "(์ด๋ฆ„ ์—†์Œ)"; + return ( + { + updateConfig({ numberingRuleId: ruleId }); + setRuleSelectOpen(false); + }} + className="text-xs" + > + + {ruleName} + + ); + })} + + + + + + + {/* ๋‹จ์ผ ํ…Œ์ด๋ธ”์ด๊ณ  ์ฑ„๋ฒˆ ๊ทœ์น™์ด ์„ ํƒ๋œ ๊ฒฝ์šฐ, ์ ์šฉํ•  ์ปฌ๋Ÿผ ์„ ํƒ */} + {config.numberingRuleId && !hasSplitPanel && tableName && ( +
+ + +

์ฑ„๋ฒˆ ๊ฐ’์ด ์ž…๋ ฅ๋  ์ปฌ๋Ÿผ์„ ์„ ํƒํ•˜์„ธ์š”.

+
+ )} + + {/* ๋ถ„ํ•  ํŒจ๋„์ธ ๊ฒฝ์šฐ ์•ˆ๋‚ด ๋ฉ”์‹œ์ง€ */} + {config.numberingRuleId && hasSplitPanel && ( +

๋งˆ์Šคํ„ฐ-๋””ํ…Œ์ผ ๊ตฌ์กฐ์—์„œ๋Š” ๋งˆ์Šคํ„ฐ ํ‚ค ์ปฌ๋Ÿผ์— ์ž๋™ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.

+ )} +
+ ); +}; + +/** + * ์—‘์…€ ์—…๋กœ๋“œ ํ›„ ์ œ์–ด ์‹คํ–‰ ์„ค์ • (๋‹จ์ผ ํ…Œ์ด๋ธ”/๋งˆ์Šคํ„ฐ-๋””ํ…Œ์ผ ๋ชจ๋‘ ์‚ฌ์šฉ ๊ฐ€๋Šฅ) + */ +const ExcelAfterUploadControlConfig: React.FC<{ + config: { afterUploadFlows?: Array<{ flowId: string; order: number }> }; + updateConfig: (updates: { afterUploadFlows?: Array<{ flowId: string; order: number }> }) => void; +}> = ({ config, updateConfig }) => { + const [nodeFlows, setNodeFlows] = useState>([]); const [flowSelectOpen, setFlowSelectOpen] = useState(false); const [isLoading, setIsLoading] = useState(false); - // ์„ ํƒ๋œ ์ œ์–ด ๋ชฉ๋ก (๋ฐฐ์—ด๋กœ ๊ด€๋ฆฌ) - const selectedFlows: Array<{ flowId: string; order: number }> = masterDetailConfig.afterUploadFlows || []; + const selectedFlows = config.afterUploadFlows || []; // ๋…ธ๋“œ ํ”Œ๋กœ์šฐ ๋ชฉ๋ก ๋กœ๋“œ useEffect(() => { @@ -3779,53 +3841,39 @@ const AfterUploadControlConfig: React.FC<{ loadNodeFlows(); }, []); - // ์ œ์–ด ์ถ”๊ฐ€ const addFlow = (flowId: string) => { if (selectedFlows.some((f) => f.flowId === flowId)) return; const newFlows = [...selectedFlows, { flowId, order: selectedFlows.length + 1 }]; - updateMasterDetailConfig({ afterUploadFlows: newFlows }); + updateConfig({ afterUploadFlows: newFlows }); setFlowSelectOpen(false); }; - // ์ œ์–ด ์ œ๊ฑฐ const removeFlow = (flowId: string) => { - const newFlows = selectedFlows - .filter((f) => f.flowId !== flowId) - .map((f, idx) => ({ ...f, order: idx + 1 })); - updateMasterDetailConfig({ afterUploadFlows: newFlows }); + const newFlows = selectedFlows.filter((f) => f.flowId !== flowId).map((f, idx) => ({ ...f, order: idx + 1 })); + updateConfig({ afterUploadFlows: newFlows }); }; - // ์ˆœ์„œ ๋ณ€๊ฒฝ (์œ„๋กœ) const moveUp = (index: number) => { if (index === 0) return; const newFlows = [...selectedFlows]; [newFlows[index - 1], newFlows[index]] = [newFlows[index], newFlows[index - 1]]; - updateMasterDetailConfig({ - afterUploadFlows: newFlows.map((f, idx) => ({ ...f, order: idx + 1 })), - }); + updateConfig({ afterUploadFlows: newFlows.map((f, idx) => ({ ...f, order: idx + 1 })) }); }; - // ์ˆœ์„œ ๋ณ€๊ฒฝ (์•„๋ž˜๋กœ) const moveDown = (index: number) => { if (index === selectedFlows.length - 1) return; const newFlows = [...selectedFlows]; [newFlows[index], newFlows[index + 1]] = [newFlows[index + 1], newFlows[index]]; - updateMasterDetailConfig({ - afterUploadFlows: newFlows.map((f, idx) => ({ ...f, order: idx + 1 })), - }); + updateConfig({ afterUploadFlows: newFlows.map((f, idx) => ({ ...f, order: idx + 1 })) }); }; - // ์„ ํƒ๋˜์ง€ ์•Š์€ ํ”Œ๋กœ์šฐ๋งŒ ํ•„ํ„ฐ๋ง const availableFlows = nodeFlows.filter((f) => !selectedFlows.some((s) => s.flowId === String(f.flowId))); return (
-

- ์—‘์…€ ์—…๋กœ๋“œ ์™„๋ฃŒ ํ›„ ์ˆœ์„œ๋Œ€๋กœ ์‹คํ–‰ํ•  ์ œ์–ด๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”. -

+

์—‘์…€ ์—…๋กœ๋“œ ์™„๋ฃŒ ํ›„ ์ˆœ์„œ๋Œ€๋กœ ์‹คํ–‰ํ•  ์ œ์–ด๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”.

- {/* ์„ ํƒ๋œ ์ œ์–ด ๋ชฉ๋ก */} {selectedFlows.length > 0 && (
{selectedFlows.map((selected, index) => { @@ -3852,7 +3900,12 @@ const AfterUploadControlConfig: React.FC<{ > -
@@ -3861,7 +3914,6 @@ const AfterUploadControlConfig: React.FC<{
)} - {/* ์ œ์–ด ์ถ”๊ฐ€ ๋ฒ„ํŠผ */}