diff --git a/backend-node/src/services/dynamicFormService.ts b/backend-node/src/services/dynamicFormService.ts index 89d96859..9e0915ee 100644 --- a/backend-node/src/services/dynamicFormService.ts +++ b/backend-node/src/services/dynamicFormService.ts @@ -937,11 +937,17 @@ export class DynamicFormService { }) .join(", "); - // ๐Ÿ†• JSONB ํƒ€์ž… ๊ฐ’์€ JSON ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ + // ๐Ÿ†• JSONB ํƒ€์ž… ๊ฐ’์€ JSON ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜, ๋นˆ ๋ฌธ์ž์—ด์€ null๋กœ ๋ณ€ํ™˜ const values: any[] = Object.keys(changedFields).map((key) => { const value = changedFields[key]; const dataType = columnTypes[key]; + // ๐Ÿ”ง ๋นˆ ๋ฌธ์ž์—ด์€ null๋กœ ๋ณ€ํ™˜ (๋‚ ์งœ ํ•„๋“œ ๋“ฑ์—์„œ ๊ฐ’์„ ์ง€์šธ ๋•Œ ํ•„์š”) + if (value === "" || value === undefined) { + console.log(`๐Ÿ”„ ๋นˆ ๊ฐ’ โ†’ null ๋ณ€ํ™˜: ${key}`); + return null; + } + // JSONB/JSON ํƒ€์ž…์ด๊ณ  ๋ฐฐ์—ด/๊ฐ์ฒด์ธ ๊ฒฝ์šฐ JSON ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ if ( (dataType === "jsonb" || dataType === "json") && diff --git a/frontend/components/common/ExcelUploadModal.tsx b/frontend/components/common/ExcelUploadModal.tsx index 64fe38b8..f6429a09 100644 --- a/frontend/components/common/ExcelUploadModal.tsx +++ b/frontend/components/common/ExcelUploadModal.tsx @@ -26,7 +26,9 @@ import { CheckCircle2, ArrowRight, Zap, + Copy, } from "lucide-react"; +import { Checkbox } from "@/components/ui/checkbox"; import { importFromExcel, getExcelSheetNames } from "@/lib/utils/excelExport"; import { DynamicFormApi } from "@/lib/api/dynamicForm"; import { getTableSchema, TableColumn } from "@/lib/api/tableSchema"; @@ -94,6 +96,8 @@ export interface ExcelUploadModalProps { interface ColumnMapping { excelColumn: string; systemColumn: string | null; + // ์ค‘๋ณต ์ฒดํฌ ์„ค์ • (ํ•ด๋‹น ์ปฌ๋Ÿผ์„ ์ค‘๋ณต ์ฒดํฌ ํ‚ค๋กœ ์‚ฌ์šฉํ• ์ง€) + checkDuplicate?: boolean; } export const ExcelUploadModal: React.FC = ({ @@ -131,6 +135,9 @@ export const ExcelUploadModal: React.FC = ({ const [excelColumns, setExcelColumns] = useState([]); const [systemColumns, setSystemColumns] = useState([]); const [columnMappings, setColumnMappings] = useState([]); + + // ์ค‘๋ณต ์ฒ˜๋ฆฌ ๋ฐฉ๋ฒ• (์ „์—ญ ์„ค์ •) + const [duplicateAction, setDuplicateAction] = useState<"overwrite" | "skip">("skip"); // 3๋‹จ๊ณ„: ํ™•์ธ const [isUploading, setIsUploading] = useState(false); @@ -544,6 +551,20 @@ export const ExcelUploadModal: React.FC = ({ ); }; + // ์ค‘๋ณต ์ฒดํฌ ์„ค์ • ๋ณ€๊ฒฝ + const handleDuplicateCheckChange = (excelColumn: string, checkDuplicate: boolean) => { + setColumnMappings((prev) => + prev.map((mapping) => + mapping.excelColumn === excelColumn + ? { ...mapping, checkDuplicate } + : mapping + ) + ); + }; + + // ์ค‘๋ณต ์ฒดํฌ ์„ค์ •๋œ ์ปฌ๋Ÿผ ์ˆ˜ + const duplicateCheckCount = columnMappings.filter((m) => m.checkDuplicate && m.systemColumn).length; + // ๋‹ค์Œ ๋‹จ๊ณ„ const handleNext = () => { if (currentStep === 1 && !file) { @@ -707,16 +728,96 @@ export const ExcelUploadModal: React.FC = ({ // ๊ธฐ์กด ๋‹จ์ผ ํ…Œ์ด๋ธ” ์—…๋กœ๋“œ ๋กœ์ง let successCount = 0; let failCount = 0; + let skipCount = 0; + let overwriteCount = 0; // ๋‹จ์ผ ํ…Œ์ด๋ธ” ์ฑ„๋ฒˆ ์„ค์ • ํ™•์ธ const hasNumbering = numberingRuleId && numberingTargetColumn; + // ์ค‘๋ณต ์ฒดํฌ ์„ค์ • ํ™•์ธ + const duplicateCheckMappings = columnMappings.filter( + (m) => m.checkDuplicate && m.systemColumn + ); + const hasDuplicateCheck = duplicateCheckMappings.length > 0; + + // ์ค‘๋ณต ์ฒดํฌ๋ฅผ ์œ„ํ•œ ๊ธฐ์กด ๋ฐ์ดํ„ฐ ์กฐํšŒ (์ค‘๋ณต ์ฒดํฌ๊ฐ€ ์„ค์ •๋œ ๊ฒฝ์šฐ์—๋งŒ) + let existingDataMap: Map = new Map(); + if (hasDuplicateCheck) { + try { + // ์ค‘๋ณต ์ฒดํฌํ•  ์ปฌ๋Ÿผ๋“ค์˜ ๊ฐ’ ์กฐํšŒ + const checkColumns = duplicateCheckMappings.map((m) => { + let colName = m.systemColumn!; + if (isMasterDetail && colName.includes(".")) { + colName = colName.split(".")[1]; + } + return colName; + }); + + // DynamicFormApi.getTableData ์‚ฌ์šฉ + const existingResponse = await DynamicFormApi.getTableData(tableName, { + page: 1, + pageSize: 10000, + }); + + console.log("๐Ÿ“Š ์ค‘๋ณต ์ฒดํฌ์šฉ ๊ธฐ์กด ๋ฐ์ดํ„ฐ ์กฐํšŒ ๊ฒฐ๊ณผ:", existingResponse); + + // getTableData๋Š” { success, data: [...] } ๋˜๋Š” { success, data: { rows: [...] } } ํ˜•์‹ + const rows = existingResponse.data?.rows || existingResponse.data; + if (existingResponse.success && rows && Array.isArray(rows)) { + // ์ค‘๋ณต ์ฒดํฌ ์ปฌ๋Ÿผ ๊ฐ’์„ ํ‚ค๋กœ ํ•˜๋Š” ๋งต ์ƒ์„ฑ + rows.forEach((row: any) => { + const keyParts = checkColumns.map((col) => String(row[col] || "").trim()); + const key = keyParts.join("|||"); + existingDataMap.set(key, row); + }); + console.log(`๐Ÿ“Š ์ค‘๋ณต ์ฒดํฌ์šฉ ๊ธฐ์กด ๋ฐ์ดํ„ฐ ๋กœ๋“œ: ${existingDataMap.size}๊ฑด`); + } + } catch (error) { + console.error("์ค‘๋ณต ์ฒดํฌ ๋ฐ์ดํ„ฐ ์กฐํšŒ ์˜ค๋ฅ˜:", error); + } + } + for (const row of filteredData) { try { let dataToSave = { ...row }; + let shouldSkip = false; + let shouldUpdate = false; + let existingRow: any = null; - // ์ฑ„๋ฒˆ ์ ์šฉ: ๊ฐ ํ–‰๋งˆ๋‹ค ์ฑ„๋ฒˆ API ํ˜ธ์ถœ - if (hasNumbering && uploadMode === "insert") { + // ์ค‘๋ณต ์ฒดํฌ + if (hasDuplicateCheck) { + const checkColumns = duplicateCheckMappings.map((m) => { + let colName = m.systemColumn!; + if (isMasterDetail && colName.includes(".")) { + colName = colName.split(".")[1]; + } + return colName; + }); + + const keyParts = checkColumns.map((col) => String(dataToSave[col] || "").trim()); + const key = keyParts.join("|||"); + + if (existingDataMap.has(key)) { + existingRow = existingDataMap.get(key); + // ์ค‘๋ณต ๋ฐœ๊ฒฌ - ์ „์—ญ ์„ค์ •์— ๋”ฐ๋ผ ์ฒ˜๋ฆฌ + if (duplicateAction === "skip") { + shouldSkip = true; + skipCount++; + console.log(`โญ๏ธ ์ค‘๋ณต์œผ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ: ${key}`); + } else { + shouldUpdate = true; + console.log(`๐Ÿ”„ ์ค‘๋ณต์œผ๋กœ ๋ฎ์–ด์“ฐ๊ธฐ: ${key}`); + } + } + } + + // ๊ฑด๋„ˆ๋›ฐ๊ธฐ ์ฒ˜๋ฆฌ + if (shouldSkip) { + continue; + } + + // ์ฑ„๋ฒˆ ์ ์šฉ: ๊ฐ ํ–‰๋งˆ๋‹ค ์ฑ„๋ฒˆ API ํ˜ธ์ถœ (์‹ ๊ทœ ๋“ฑ๋ก ์‹œ์—๋งŒ) + if (hasNumbering && uploadMode === "insert" && !shouldUpdate) { try { const { apiClient } = await import("@/lib/api/client"); const numberingResponse = await apiClient.post(`/numbering-rules/${numberingRuleId}/allocate`); @@ -729,7 +830,22 @@ export const ExcelUploadModal: React.FC = ({ } } - if (uploadMode === "insert") { + if (shouldUpdate && existingRow) { + // ๋ฎ์–ด์“ฐ๊ธฐ: ๊ธฐ์กด ๋ฐ์ดํ„ฐ ์—…๋ฐ์ดํŠธ + const formData = { + screenId: 0, + tableName, + data: dataToSave, + }; + const result = await DynamicFormApi.updateFormData(existingRow.id, formData); + if (result.success) { + overwriteCount++; + successCount++; + } else { + failCount++; + } + } else if (uploadMode === "insert") { + // ์‹ ๊ทœ ๋“ฑ๋ก const formData = { screenId: 0, tableName, data: dataToSave }; const result = await DynamicFormApi.saveFormData(formData); if (result.success) { @@ -743,7 +859,7 @@ export const ExcelUploadModal: React.FC = ({ } } - // ๐Ÿ†• ์—…๋กœ๋“œ ํ›„ ์ œ์–ด ์‹คํ–‰ + // ์—…๋กœ๋“œ ํ›„ ์ œ์–ด ์‹คํ–‰ if (afterUploadFlows && afterUploadFlows.length > 0 && successCount > 0) { console.log("๐Ÿ”„ ์—…๋กœ๋“œ ํ›„ ์ œ์–ด ์‹คํ–‰:", afterUploadFlows); try { @@ -761,10 +877,24 @@ export const ExcelUploadModal: React.FC = ({ } } - if (successCount > 0) { - toast.success( - `${successCount}๊ฐœ ํ–‰์ด ์—…๋กœ๋“œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.${failCount > 0 ? ` (์‹คํŒจ: ${failCount}๊ฐœ)` : ""}` - ); + if (successCount > 0 || skipCount > 0) { + // ์ƒ์„ธ ๊ฒฐ๊ณผ ๋ฉ”์‹œ์ง€ ์ƒ์„ฑ + let message = ""; + if (successCount > 0) { + message += `${successCount}๊ฐœ ํ–‰ ์—…๋กœ๋“œ`; + if (overwriteCount > 0) { + message += ` (๋ฎ์–ด์“ฐ๊ธฐ ${overwriteCount}๊ฑด)`; + } + } + if (skipCount > 0) { + message += message ? `, ` : ""; + message += `์ค‘๋ณต ๊ฑด๋„ˆ๋›ฐ๊ธฐ ${skipCount}๊ฐœ`; + } + if (failCount > 0) { + message += ` (์‹คํŒจ: ${failCount}๊ฐœ)`; + } + + toast.success(message); // ๋งคํ•‘ ํ…œํ”Œ๋ฆฟ ์ €์žฅ await saveMappingTemplateInternal(); @@ -825,6 +955,7 @@ export const ExcelUploadModal: React.FC = ({ setExcelColumns([]); setSystemColumns([]); setColumnMappings([]); + setDuplicateAction("skip"); // ๐Ÿ†• ๋งˆ์Šคํ„ฐ-๋””ํ…Œ์ผ ๋ชจ๋“œ ์ดˆ๊ธฐํ™” setMasterFieldValues({}); } @@ -928,12 +1059,39 @@ export const ExcelUploadModal: React.FC = ({ {field.inputType === "entity" ? ( + {/* ์ค‘๋ณต ์ฒดํฌ ์ฒดํฌ๋ฐ•์Šค */} +
+ {mapping.systemColumn ? ( + + handleDuplicateCheckChange(mapping.excelColumn, checked as boolean) + } + className="h-4 w-4" + /> + ) : ( + - + )} +
))} + {/* ์ค‘๋ณต ์ฒดํฌ ์•ˆ๋‚ด */} + {duplicateCheckCount > 0 ? ( +
+
+
+ +
+

+ ์ค‘๋ณต ํ‚ค: {columnMappings + .filter((m) => m.checkDuplicate && m.systemColumn) + .map((m) => { + const col = systemColumns.find((c) => c.name === m.systemColumn); + return col?.label || m.systemColumn; + }) + .join(" + ")} +

+

+ ์œ„ ์ปฌ๋Ÿผ ๊ฐ’์ด ๋ชจ๋‘ ์ผ์น˜ํ•˜๋Š” ๊ธฐ์กด ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์œผ๋ฉด ์ค‘๋ณต์œผ๋กœ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. +

+
+
+
+ ์ค‘๋ณต ์‹œ: + +
+
+
+ ) : ( +
+
+ +
+

์ค‘๋ณต ์ฒดํฌ (์„ ํƒ์‚ฌํ•ญ)

+

+ "์ค‘๋ณต ํ‚ค" ์ฒดํฌ๋ฐ•์Šค๋ฅผ ์„ ํƒํ•˜๋ฉด ํ•ด๋‹น ์ปฌ๋Ÿผ ๊ฐ’์œผ๋กœ ๊ธฐ์กด ๋ฐ์ดํ„ฐ์™€ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค. + ์—ฌ๋Ÿฌ ์ปฌ๋Ÿผ์„ ์„ ํƒํ•˜๋ฉด ๋ณตํ•ฉ ํ‚ค๋กœ ์ค‘๋ณต์„ ํŒ๋‹จํ•ฉ๋‹ˆ๋‹ค. +

+
+
+
+ )} + {/* ๋งคํ•‘ ์ž๋™ ์ €์žฅ ์•ˆ๋‚ด */} {isAutoMappingLoaded ? (
@@ -1271,6 +1497,11 @@ export const ExcelUploadModal: React.FC = ({

{mapping.excelColumn} โ†’{" "} {col?.label || mapping.systemColumn} + {mapping.checkDuplicate && ( + + (์ค‘๋ณต ์ฒดํฌ: {mapping.duplicateAction === "overwrite" ? "๋ฎ์–ด์“ฐ๊ธฐ" : "๊ฑด๋„ˆ๋›ฐ๊ธฐ"}) + + )}

); })} @@ -1280,6 +1511,29 @@ export const ExcelUploadModal: React.FC = ({
+ {/* ์ค‘๋ณต ์ฒดํฌ ์š”์•ฝ */} + {duplicateCheckCount > 0 && ( +
+

์ค‘๋ณต ์ฒดํฌ ์„ค์ •

+
+

+ ์ค‘๋ณต ํ‚ค:{" "} + {columnMappings + .filter((m) => m.checkDuplicate && m.systemColumn) + .map((m) => { + const col = systemColumns.find((c) => c.name === m.systemColumn); + return col?.label || m.systemColumn; + }) + .join(" + ")} +

+

+ ์ค‘๋ณต ์‹œ ์ฒ˜๋ฆฌ:{" "} + {duplicateAction === "overwrite" ? "๋ฎ์–ด์“ฐ๊ธฐ (๊ธฐ์กด ๋ฐ์ดํ„ฐ ์—…๋ฐ์ดํŠธ)" : "๊ฑด๋„ˆ๋›ฐ๊ธฐ (ํ•ด๋‹น ํ–‰ ๋ฌด์‹œ)"} +

+
+
+ )} +
diff --git a/frontend/components/screen/EditModal.tsx b/frontend/components/screen/EditModal.tsx index 16c99629..3c04bfb7 100644 --- a/frontend/components/screen/EditModal.tsx +++ b/frontend/components/screen/EditModal.tsx @@ -1064,8 +1064,15 @@ export const EditModal: React.FC = ({ className }) => { } ); + // ๐Ÿ†• _tableSection_ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธ (TableSectionRenderer ์‚ฌ์šฉ ์‹œ) + // _tableSection_ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์œผ๋ฉด buttonActions.ts์˜ handleUniversalFormModalTableSectionSave๊ฐ€ ์ฒ˜๋ฆฌ + const hasTableSectionData = Object.keys(formData).some(k => + k.startsWith("_tableSection_") || k.startsWith("__tableSection_") + ); + // ๐Ÿ†• ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์œผ๋ฉด EditModal.handleSave ์‚ฌ์šฉ (์ผ๊ด„ ์ €์žฅ) - const shouldUseEditModalSave = groupData.length > 0 || !hasUniversalFormModal; + // ๋‹จ, _tableSection_ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์œผ๋ฉด EditModal.handleSave ์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ (buttonActions.ts๊ฐ€ ์ฒ˜๋ฆฌ) + const shouldUseEditModalSave = !hasTableSectionData && (groupData.length > 0 || !hasUniversalFormModal); // ๐Ÿ”‘ ์ฒจ๋ถ€ํŒŒ์ผ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ–‰(๋ ˆ์ฝ”๋“œ) ๋‹จ์œ„๋กœ ํŒŒ์ผ์„ ์ €์žฅํ•  ์ˆ˜ ์žˆ๋„๋ก tableName ์ถ”๊ฐ€ const enrichedFormData = { diff --git a/frontend/components/screen/filters/ModernDatePicker.tsx b/frontend/components/screen/filters/ModernDatePicker.tsx index 0a134927..54fdcfed 100644 --- a/frontend/components/screen/filters/ModernDatePicker.tsx +++ b/frontend/components/screen/filters/ModernDatePicker.tsx @@ -84,8 +84,20 @@ export const ModernDatePicker: React.FC = ({ label, value }; const handleConfirm = () => { + // ๋‚ ์งœ ์ˆœ์„œ ์ž๋™ ์ •๋ ฌ + let finalValue = { ...tempValue }; + + if (finalValue.from && finalValue.to) { + // from์ด to๋ณด๋‹ค ๋‚˜์ค‘์ด๋ฉด swap + if (finalValue.from > finalValue.to) { + const temp = finalValue.from; + finalValue.from = finalValue.to; + finalValue.to = temp; + } + } + // ํ™•์ธ ๋ฒ„ํŠผ์„ ๋ˆŒ๋ €์„ ๋•Œ๋งŒ onChange ํ˜ธ์ถœ - onChange(tempValue); + onChange(finalValue); setIsOpen(false); setSelectingType("from"); }; diff --git a/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx b/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx index 60262401..6264a757 100644 --- a/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx +++ b/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx @@ -951,23 +951,43 @@ export const SplitPanelLayoutComponent: React.FC // ์ถ”๊ฐ€ dataFilter ์ ์šฉ let filteredData = result.data || []; const dataFilter = componentConfig.rightPanel?.dataFilter; - if (dataFilter?.enabled && dataFilter.conditions?.length > 0) { + // ๐Ÿ”ง filters ๋˜๋Š” conditions ๋ฐฐ์—ด ๋ชจ๋‘ ์ง€์› + const filterConditions = dataFilter?.filters || dataFilter?.conditions || []; + if (dataFilter?.enabled && filterConditions.length > 0) { + console.log(`๐Ÿ” [๊ธฐ๋ณธํƒญ] dataFilter ์„ค์ •:`, JSON.stringify(dataFilter, null, 2)); + console.log(`๐Ÿ” [๊ธฐ๋ณธํƒญ] ํ•„ํ„ฐ ์ „ ๋ฐ์ดํ„ฐ ์ˆ˜:`, filteredData.length); filteredData = filteredData.filter((item: any) => { - return dataFilter.conditions.every((cond: any) => { - const value = item[cond.column]; + return filterConditions.every((cond: any) => { + // ๐Ÿ”ง columnName ๋˜๋Š” column ํ•„๋“œ ๋ชจ๋‘ ์ง€์› + const columnName = cond.columnName || cond.column; + const value = item[columnName]; const condValue = cond.value; + let result = true; switch (cond.operator) { case "equals": - return value === condValue; + result = value === condValue; + break; case "notEquals": - return value !== condValue; + result = value !== condValue; + break; case "contains": - return String(value).includes(String(condValue)); + result = String(value).includes(String(condValue)); + break; + case "is_null": + case "NULL": + result = value === null || value === undefined || value === ""; + break; + case "is_not_null": + case "NOT NULL": + result = value !== null && value !== undefined && value !== ""; + break; default: - return true; + result = true; } + return result; }); }); + console.log(`๐Ÿ” [๊ธฐ๋ณธํƒญ] ํ•„ํ„ฐ ํ›„ ๋ฐ์ดํ„ฐ ์ˆ˜:`, filteredData.length); } setRightData(filteredData); @@ -1080,23 +1100,48 @@ export const SplitPanelLayoutComponent: React.FC // ๋ฐ์ดํ„ฐ ํ•„ํ„ฐ ์ ์šฉ const dataFilter = tabConfig.dataFilter; - if (dataFilter?.enabled && dataFilter.conditions?.length > 0) { + console.log(`๐Ÿ” [์ถ”๊ฐ€ํƒญ ${tabIndex}] dataFilter ์„ค์ •:`, JSON.stringify(dataFilter, null, 2)); + // ๐Ÿ”ง filters ๋˜๋Š” conditions ๋ฐฐ์—ด ๋ชจ๋‘ ์ง€์› (DataFilterConfigPanel์€ filters ์‚ฌ์šฉ) + const filterConditions = dataFilter?.filters || dataFilter?.conditions || []; + console.log(`๐Ÿ” [์ถ”๊ฐ€ํƒญ ${tabIndex}] filterConditions:`, filterConditions); + console.log(`๐Ÿ” [์ถ”๊ฐ€ํƒญ ${tabIndex}] ํ•„ํ„ฐ ์ „ ๋ฐ์ดํ„ฐ ์ˆ˜:`, resultData.length); + if (dataFilter?.enabled && filterConditions.length > 0) { + const beforeCount = resultData.length; resultData = resultData.filter((item: any) => { - return dataFilter.conditions.every((cond: any) => { - const value = item[cond.column]; + return filterConditions.every((cond: any) => { + // ๐Ÿ”ง columnName ๋˜๋Š” column ํ•„๋“œ ๋ชจ๋‘ ์ง€์› + const columnName = cond.columnName || cond.column; + const value = item[columnName]; const condValue = cond.value; + let result = true; switch (cond.operator) { case "equals": - return value === condValue; + result = value === condValue; + break; case "notEquals": - return value !== condValue; + result = value !== condValue; + break; case "contains": - return String(value).includes(String(condValue)); + result = String(value).includes(String(condValue)); + break; + case "is_null": + case "NULL": + result = value === null || value === undefined || value === ""; + break; + case "is_not_null": + case "NOT NULL": + result = value !== null && value !== undefined && value !== ""; + break; default: - return true; + result = true; } + console.log(`๐Ÿ” [ํ•„ํ„ฐ ์ฒดํฌ] ${columnName}=${JSON.stringify(value)}, operator=${cond.operator}, result=${result}`); + return result; }); }); + console.log(`๐Ÿ” [์ถ”๊ฐ€ํƒญ ${tabIndex}] ํ•„ํ„ฐ ํ›„ ๋ฐ์ดํ„ฐ ์ˆ˜: ${beforeCount} โ†’ ${resultData.length}`); + } else { + console.log(`๐Ÿ” [์ถ”๊ฐ€ํƒญ ${tabIndex}] ํ•„ํ„ฐ ๋น„ํ™œ์„ฑํ™” ๋˜๋Š” ์กฐ๊ฑด ์—†์Œ (enabled=${dataFilter?.enabled}, conditions=${filterConditions.length})`); } // ์ค‘๋ณต ์ œ๊ฑฐ ์ ์šฉ @@ -1557,6 +1602,7 @@ export const SplitPanelLayoutComponent: React.FC // ์ถ”๊ฐ€ ๋ฒ„ํŠผ ํ•ธ๋“ค๋Ÿฌ const handleAddClick = useCallback( (panel: "left" | "right") => { + console.log("๐Ÿ†• [์ถ”๊ฐ€๋ชจ๋‹ฌ] handleAddClick ํ˜ธ์ถœ:", { panel, activeTabIndex }); setAddModalPanel(panel); // ์šฐ์ธก ํŒจ๋„ ์ถ”๊ฐ€ ์‹œ, ์ขŒ์ธก์—์„œ ์„ ํƒ๋œ ํ•ญ๋ชฉ์˜ ์กฐ์ธ ์ปฌ๋Ÿผ ๊ฐ’์„ ์ž๋™์œผ๋กœ ์ฑ„์›€ @@ -1567,124 +1613,183 @@ export const SplitPanelLayoutComponent: React.FC componentConfig.rightPanel?.rightColumn ) { const leftColumnValue = selectedLeftItem[componentConfig.leftPanel.leftColumn]; - setAddModalFormData({ + const initialData = { [componentConfig.rightPanel.rightColumn]: leftColumnValue, - }); + }; + console.log("๐Ÿ†• [์ถ”๊ฐ€๋ชจ๋‹ฌ] ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ ์„ค์ •:", initialData); + setAddModalFormData(initialData); } else { + console.log("๐Ÿ†• [์ถ”๊ฐ€๋ชจ๋‹ฌ] ๋นˆ ๋ฐ์ดํ„ฐ๋กœ ์ดˆ๊ธฐํ™”"); setAddModalFormData({}); } setShowAddModal(true); }, - [selectedLeftItem, componentConfig], + [selectedLeftItem, componentConfig, activeTabIndex], ); // ์ˆ˜์ • ๋ฒ„ํŠผ ํ•ธ๋“ค๋Ÿฌ const handleEditClick = useCallback( (panel: "left" | "right", item: any) => { // ๐Ÿ†• ์šฐ์ธก ํŒจ๋„ ์ˆ˜์ • ๋ฒ„ํŠผ ์„ค์ • ํ™•์ธ - if (panel === "right" && componentConfig.rightPanel?.editButton?.mode === "modal") { - const modalScreenId = componentConfig.rightPanel?.editButton?.modalScreenId; + // ๐Ÿ”ง ํ˜„์žฌ ํ™œ์„ฑ ํƒญ์— ๋”ฐ๋ผ ํ•ด๋‹น ํƒญ์˜ editButton ์„ค์ • ์‚ฌ์šฉ + if (panel === "right") { + // ๊ธฐ๋ณธ ํƒญ(0)์ด๋ฉด rightPanel.editButton, ์ถ”๊ฐ€ ํƒญ์ด๋ฉด additionalTabs์˜ editButton ์‚ฌ์šฉ + const editButtonConfig = + activeTabIndex === 0 + ? componentConfig.rightPanel?.editButton + : componentConfig.rightPanel?.additionalTabs?.[activeTabIndex - 1]?.editButton; - if (modalScreenId) { - // ์ปค์Šคํ…€ ๋ชจ๋‹ฌ ํ™”๋ฉด ์—ด๊ธฐ - const rightTableName = componentConfig.rightPanel?.tableName || ""; + // ํ•ด๋‹น ํƒญ์˜ ํ…Œ์ด๋ธ”๋ช… ๊ฐ€์ ธ์˜ค๊ธฐ + const currentTableName = + activeTabIndex === 0 + ? componentConfig.rightPanel?.tableName || "" + : componentConfig.rightPanel?.additionalTabs?.[activeTabIndex - 1]?.tableName || ""; - // Primary Key ์ฐพ๊ธฐ (์šฐ์„ ์ˆœ์œ„: ์„ค์ •๊ฐ’ > id > ID > non-null ํ•„๋“œ) - // ๐Ÿ”ง ์„ค์ •์—์„œ primaryKeyColumn ์ง€์ • ๊ฐ€๋Šฅ - const configuredPrimaryKey = componentConfig.rightPanel?.editButton?.primaryKeyColumn; + console.log("๐Ÿ”ง [SplitPanel] ์ˆ˜์ • ๋ฒ„ํŠผ ํด๋ฆญ - ํ˜„์žฌ ํƒญ ์„ค์ • ํ™•์ธ:", { + activeTabIndex, + editButtonConfig, + currentTableName, + isModalMode: editButtonConfig?.mode === "modal", + }); - let primaryKeyName = "id"; - let primaryKeyValue: any; + if (editButtonConfig?.mode === "modal") { + const modalScreenId = editButtonConfig.modalScreenId; - if (configuredPrimaryKey && item[configuredPrimaryKey] !== undefined && item[configuredPrimaryKey] !== null) { - // ์„ค์ •๋œ Primary Key ์‚ฌ์šฉ - primaryKeyName = configuredPrimaryKey; - primaryKeyValue = item[configuredPrimaryKey]; - } else if (item.id !== undefined && item.id !== null) { - primaryKeyName = "id"; - primaryKeyValue = item.id; - } else if (item.ID !== undefined && item.ID !== null) { - primaryKeyName = "ID"; - primaryKeyValue = item.ID; - } else { - // ๐Ÿ”ง ์ฒซ ๋ฒˆ์งธ non-null ํ•„๋“œ๋ฅผ Primary Key๋กœ ๊ฐ„์ฃผ - const keys = Object.keys(item); - let found = false; - for (const key of keys) { - if (item[key] !== undefined && item[key] !== null) { - primaryKeyName = key; - primaryKeyValue = item[key]; - found = true; - break; + if (modalScreenId) { + // ์ปค์Šคํ…€ ๋ชจ๋‹ฌ ํ™”๋ฉด ์—ด๊ธฐ + + // Primary Key ์ฐพ๊ธฐ (์šฐ์„ ์ˆœ์œ„: ์„ค์ •๊ฐ’ > id > ID > non-null ํ•„๋“œ) + // ๐Ÿ”ง ์„ค์ •์—์„œ primaryKeyColumn ์ง€์ • ๊ฐ€๋Šฅ + const configuredPrimaryKey = editButtonConfig.primaryKeyColumn; + + let primaryKeyName = "id"; + let primaryKeyValue: any; + + if (configuredPrimaryKey && item[configuredPrimaryKey] !== undefined && item[configuredPrimaryKey] !== null) { + // ์„ค์ •๋œ Primary Key ์‚ฌ์šฉ + primaryKeyName = configuredPrimaryKey; + primaryKeyValue = item[configuredPrimaryKey]; + } else if (item.id !== undefined && item.id !== null) { + primaryKeyName = "id"; + primaryKeyValue = item.id; + } else if (item.ID !== undefined && item.ID !== null) { + primaryKeyName = "ID"; + primaryKeyValue = item.ID; + } else { + // ๐Ÿ”ง ์ฒซ ๋ฒˆ์งธ non-null ํ•„๋“œ๋ฅผ Primary Key๋กœ ๊ฐ„์ฃผ + const keys = Object.keys(item); + let found = false; + for (const key of keys) { + if (item[key] !== undefined && item[key] !== null) { + primaryKeyName = key; + primaryKeyValue = item[key]; + found = true; + break; + } + } + // ๋ชจ๋“  ํ•„๋“œ๊ฐ€ null์ด๋ฉด ์ฒซ ๋ฒˆ์งธ ํ•„๋“œ ์‚ฌ์šฉ + if (!found && keys.length > 0) { + primaryKeyName = keys[0]; + primaryKeyValue = item[keys[0]]; } } - // ๋ชจ๋“  ํ•„๋“œ๊ฐ€ null์ด๋ฉด ์ฒซ ๋ฒˆ์งธ ํ•„๋“œ ์‚ฌ์šฉ - if (!found && keys.length > 0) { - primaryKeyName = keys[0]; - primaryKeyValue = item[keys[0]]; - } - } - console.log("โœ… ์ˆ˜์ • ๋ชจ๋‹ฌ ์—ด๊ธฐ:", { - tableName: rightTableName, - primaryKeyName, - primaryKeyValue, - screenId: modalScreenId, - fullItem: item, - }); + console.log("โœ… ์ˆ˜์ • ๋ชจ๋‹ฌ ์—ด๊ธฐ:", { + activeTabIndex, + tableName: currentTableName, + primaryKeyName, + primaryKeyValue, + screenId: modalScreenId, + fullItem: item, + }); - // modalDataStore์—๋„ ์ €์žฅ (ํ˜ธํ™˜์„ฑ ์œ ์ง€) - import("@/stores/modalDataStore").then(({ useModalDataStore }) => { - useModalDataStore.getState().setData(rightTableName, [item]); - }); + // modalDataStore์—๋„ ์ €์žฅ (ํ˜ธํ™˜์„ฑ ์œ ์ง€) + import("@/stores/modalDataStore").then(({ useModalDataStore }) => { + useModalDataStore.getState().setData(currentTableName, [item]); + }); - // ๐Ÿ†• groupByColumns ์ถ”์ถœ - const groupByColumns = componentConfig.rightPanel?.editButton?.groupByColumns || []; + // ๐Ÿ†• groupByColumns ์ถ”์ถœ + const groupByColumns = editButtonConfig.groupByColumns || []; - console.log("๐Ÿ”ง [SplitPanel] ์ˆ˜์ • ๋ฒ„ํŠผ ํด๋ฆญ - groupByColumns ํ™•์ธ:", { - groupByColumns, - editButtonConfig: componentConfig.rightPanel?.editButton, - hasGroupByColumns: groupByColumns.length > 0, - }); + console.log("๐Ÿ”ง [SplitPanel] ์ˆ˜์ • ๋ฒ„ํŠผ ํด๋ฆญ - groupByColumns ํ™•์ธ:", { + groupByColumns, + editButtonConfig, + hasGroupByColumns: groupByColumns.length > 0, + }); - // ScreenModal ์—ด๊ธฐ ์ด๋ฒคํŠธ ๋ฐœ์ƒ (URL ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ID + groupByColumns + primaryKeyColumn ์ „๋‹ฌ) - window.dispatchEvent( - new CustomEvent("openScreenModal", { - detail: { - screenId: modalScreenId, - urlParams: { - mode: "edit", - editId: primaryKeyValue, - tableName: rightTableName, - primaryKeyColumn: primaryKeyName, // ๐Ÿ†• Primary Key ์ปฌ๋Ÿผ๋ช… ์ „๋‹ฌ - ...(groupByColumns.length > 0 && { - groupByColumns: JSON.stringify(groupByColumns), - }), + // ScreenModal ์—ด๊ธฐ ์ด๋ฒคํŠธ ๋ฐœ์ƒ (URL ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ID + groupByColumns + primaryKeyColumn ์ „๋‹ฌ) + window.dispatchEvent( + new CustomEvent("openScreenModal", { + detail: { + screenId: modalScreenId, + urlParams: { + mode: "edit", + editId: primaryKeyValue, + tableName: currentTableName, + primaryKeyColumn: primaryKeyName, // ๐Ÿ†• Primary Key ์ปฌ๋Ÿผ๋ช… ์ „๋‹ฌ + ...(groupByColumns.length > 0 && { + groupByColumns: JSON.stringify(groupByColumns), + }), + }, }, - }, - }), - ); + }), + ); - console.log("โœ… [SplitPanel] openScreenModal ์ด๋ฒคํŠธ ๋ฐœ์ƒ:", { - screenId: modalScreenId, - editId: primaryKeyValue, - tableName: rightTableName, - primaryKeyColumn: primaryKeyName, - groupByColumns: groupByColumns.length > 0 ? JSON.stringify(groupByColumns) : "์—†์Œ", - }); + console.log("โœ… [SplitPanel] openScreenModal ์ด๋ฒคํŠธ ๋ฐœ์ƒ:", { + screenId: modalScreenId, + editId: primaryKeyValue, + tableName: currentTableName, + primaryKeyColumn: primaryKeyName, + groupByColumns: groupByColumns.length > 0 ? JSON.stringify(groupByColumns) : "์—†์Œ", + }); - return; + return; + } } } // ๊ธฐ์กด ์ž๋™ ํŽธ์ง‘ ๋ชจ๋“œ (์ธ๋ผ์ธ ํŽธ์ง‘ ๋ชจ๋‹ฌ) setEditModalPanel(panel); setEditModalItem(item); - setEditModalFormData({ ...item }); + + // ๐Ÿ”ง ์šฐ์ธก ํŒจ๋„(์ถ”๊ฐ€ํƒญ ํฌํ•จ) ์ˆ˜์ • ์‹œ selectedLeftItem์˜ FK ๊ฐ’ ๋ณ‘ํ•ฉ + let mergedItem = { ...item }; + if (panel === "right" && selectedLeftItem) { + // ํ˜„์žฌ ํ™œ์„ฑ ํƒญ์˜ relation ์„ค์ • ๊ฐ€์ ธ์˜ค๊ธฐ + const currentTabConfig = + activeTabIndex === 0 + ? componentConfig.rightPanel + : componentConfig.rightPanel?.additionalTabs?.[activeTabIndex - 1]; + + const relationKeys = currentTabConfig?.relation?.keys; + const leftColumn = currentTabConfig?.relation?.leftColumn; + const rightColumn = currentTabConfig?.relation?.foreignKey || currentTabConfig?.relation?.rightColumn; + + if (relationKeys && relationKeys.length > 0) { + // ๋ณตํ•ฉํ‚ค์ธ ๊ฒฝ์šฐ + relationKeys.forEach((key: any) => { + if (key.leftColumn && key.rightColumn && selectedLeftItem[key.leftColumn] !== undefined) { + // item์— ํ•ด๋‹น FK ๊ฐ’์ด ์—†๊ฑฐ๋‚˜ ๋นˆ ๊ฐ’์ด๋ฉด selectedLeftItem์—์„œ ๊ฐ€์ ธ์˜ด + if (mergedItem[key.rightColumn] === undefined || mergedItem[key.rightColumn] === null || mergedItem[key.rightColumn] === "") { + mergedItem[key.rightColumn] = selectedLeftItem[key.leftColumn]; + } + } + }); + } else if (leftColumn && rightColumn) { + // ๋‹จ์ผํ‚ค์ธ ๊ฒฝ์šฐ + if (selectedLeftItem[leftColumn] !== undefined) { + if (mergedItem[rightColumn] === undefined || mergedItem[rightColumn] === null || mergedItem[rightColumn] === "") { + mergedItem[rightColumn] = selectedLeftItem[leftColumn]; + } + } + } + } + + setEditModalFormData(mergedItem); setShowEditModal(true); }, - [componentConfig], + [componentConfig, selectedLeftItem, activeTabIndex], ); // ์ˆ˜์ • ๋ชจ๋‹ฌ ์ €์žฅ diff --git a/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx b/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx index 939bb5d5..ca4d57d0 100644 --- a/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx +++ b/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx @@ -380,6 +380,16 @@ export function UniversalFormModalComponent({ const handleBeforeFormSave = (event: Event) => { if (!(event instanceof CustomEvent) || !event.detail?.formData) return; + // ํ•„์ˆ˜๊ฐ’ ๊ฒ€์ฆ ์‹คํ–‰ + const validation = validateRequiredFields(); + if (!validation.valid) { + event.detail.validationFailed = true; + event.detail.validationErrors = validation.missingFields; + toast.error(`ํ•„์ˆ˜ ํ•ญ๋ชฉ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”: ${validation.missingFields.join(", ")}`); + console.log("[UniversalFormModal] ํ•„์ˆ˜๊ฐ’ ๊ฒ€์ฆ ์‹คํŒจ:", validation.missingFields); + return; // ๊ฒ€์ฆ ์‹คํŒจ ์‹œ ๋ฐ์ดํ„ฐ ๋ณ‘ํ•ฉ ์ค‘๋‹จ + } + // ์„ค์ •์— ์ •์˜๋œ ํ•„๋“œ columnName ๋ชฉ๋ก ์ˆ˜์ง‘ const configuredFields = new Set(); config.sections.forEach((section) => { @@ -439,6 +449,12 @@ export function UniversalFormModalComponent({ event.detail.formData[normalizedKey] = value; console.log(`[UniversalFormModal] ํ…Œ์ด๋ธ” ์„น์…˜ ๋ณ‘ํ•ฉ: ${key} โ†’ ${normalizedKey}, ${value.length}๊ฐœ ํ•ญ๋ชฉ`); } + + // ๐Ÿ†• ์›๋ณธ ํ…Œ์ด๋ธ” ์„น์…˜ ๋ฐ์ดํ„ฐ๋„ ๋ณ‘ํ•ฉ (์‚ญ์ œ ์ถ”์ ์šฉ) + if (key.startsWith("_originalTableSectionData_") && Array.isArray(value)) { + event.detail.formData[key] = value; + console.log(`[UniversalFormModal] ์›๋ณธ ํ…Œ์ด๋ธ” ์„น์…˜ ๋ฐ์ดํ„ฐ ๋ณ‘ํ•ฉ: ${key}, ${value.length}๊ฐœ ํ•ญ๋ชฉ`); + } } // ๐Ÿ†• ์ˆ˜์ • ๋ชจ๋“œ: ์›๋ณธ ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ ์ „๋‹ฌ (UPDATE/DELETE ์ถ”์ ์šฉ) @@ -928,17 +944,19 @@ export function UniversalFormModalComponent({ newFormData[tableSectionKey] = items; console.log(`[initializeForm] ํ…Œ์ด๋ธ” ์„น์…˜ ${section.id}: formData[${tableSectionKey}]์— ์ €์žฅ๋จ`); - // ๐Ÿ†• ์›๋ณธ ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ ์ €์žฅ (์‚ญ์ œ ์ถ”์ ์šฉ) - // groupedDataInitializedRef๊ฐ€ false์ผ ๋•Œ๋งŒ ์„ค์ • (true๋ฉด _groupedData useEffect์—์„œ ์ด๋ฏธ ์ฒ˜๋ฆฌ๋จ) - // DB์—์„œ ๋กœ๋“œํ•œ ๋ฐ์ดํ„ฐ๋ฅผ originalGroupedData์— ์ €์žฅํ•ด์•ผ ์‚ญ์ œ ์‹œ ๋น„๊ต ๊ฐ€๋Šฅ + // ๐Ÿ†• ํ…Œ์ด๋ธ” ์„น์…˜ ์›๋ณธ ๋ฐ์ดํ„ฐ ์ €์žฅ (์‚ญ์ œ ์ถ”์ ์šฉ) + // ๊ฐ ํ…Œ์ด๋ธ” ์„น์…˜๋ณ„๋กœ ๋ณ„๋„์˜ ํ‚ค์— ์›๋ณธ ๋ฐ์ดํ„ฐ ์ €์žฅ (groupedDataInitializedRef์™€ ๋ฌด๊ด€ํ•˜๊ฒŒ ํ•ญ์ƒ ์ €์žฅ) + const originalTableSectionKey = `_originalTableSectionData_${section.id}`; + newFormData[originalTableSectionKey] = JSON.parse(JSON.stringify(items)); + console.log(`[initializeForm] ํ…Œ์ด๋ธ” ์„น์…˜ ${section.id}: formData[${originalTableSectionKey}]์— ์›๋ณธ ${items.length}๊ฑด ์ €์žฅ`); + + // ๊ธฐ์กด originalGroupedData์—๋„ ์ถ”๊ฐ€ (ํ•˜์œ„ ํ˜ธํ™˜์„ฑ) if (!groupedDataInitializedRef.current) { setOriginalGroupedData((prev) => { const newOriginal = [...prev, ...JSON.parse(JSON.stringify(items))]; console.log(`[initializeForm] ํ…Œ์ด๋ธ” ์„น์…˜ ${section.id}: originalGroupedData์— ${items.length}๊ฑด ์ถ”๊ฐ€ (์ด ${newOriginal.length}๊ฑด)`); return newOriginal; }); - } else { - console.log(`[initializeForm] ํ…Œ์ด๋ธ” ์„น์…˜ ${section.id}: _groupedData๋กœ ์ด๋ฏธ ์ดˆ๊ธฐํ™”๋จ, originalGroupedData ์„ค์ • ์Šคํ‚ต`); } } } catch (error) { diff --git a/frontend/lib/utils/buttonActions.ts b/frontend/lib/utils/buttonActions.ts index 29db571d..cb5bdfcc 100644 --- a/frontend/lib/utils/buttonActions.ts +++ b/frontend/lib/utils/buttonActions.ts @@ -331,19 +331,14 @@ export function resolveSpecialKeyword(sourceField: string | undefined, context: // ํŠน์ˆ˜ ํ‚ค์›Œ๋“œ ์ฒ˜๋ฆฌ switch (sourceField) { case "__userId__": - return context.userId; case "__userName__": - return context.userName; case "__companyCode__": - return context.companyCode; case "__screenId__": - return context.screenId; case "__tableName__": - return context.tableName; default: // ์ผ๋ฐ˜ ํผ ๋ฐ์ดํ„ฐ์—์„œ ๊ฐ€์ ธ์˜ค๊ธฐ @@ -458,7 +453,6 @@ export class ButtonActionExecutor { const value = formData[columnName]; // ๊ฐ’์ด ๋น„์–ด์žˆ๋Š”์ง€ ํ™•์ธ (null, undefined, ๋นˆ ๋ฌธ์ž์—ด, ๊ณต๋ฐฑ๋งŒ ์žˆ๋Š” ๋ฌธ์ž์—ด) if (value === null || value === undefined || (typeof value === "string" && value.trim() === "")) { - missingFields.push(label || columnName); } } @@ -490,7 +484,6 @@ export class ButtonActionExecutor { const timeDiff = now - lastCallTime; if (timeDiff < 2000) { - return true; // ์ค‘๋ณต ํ˜ธ์ถœ์€ ์„ฑ๊ณต์œผ๋กœ ์ฒ˜๋ฆฌ } @@ -507,7 +500,6 @@ export class ButtonActionExecutor { // ๐Ÿ†• EditModal ๋“ฑ์—์„œ ์ „๋‹ฌ๋œ onSave ์ฝœ๋ฐฑ์ด ์žˆ์œผ๋ฉด ์šฐ์„  ์‚ฌ์šฉ if (onSave) { - try { await onSave(); return true; @@ -525,6 +517,8 @@ export class ButtonActionExecutor { const beforeSaveEventDetail = { formData: context.formData, skipDefaultSave: false, + validationFailed: false, + validationErrors: [] as string[], }; window.dispatchEvent( new CustomEvent("beforeFormSave", { @@ -535,12 +529,44 @@ export class ButtonActionExecutor { // ์•ฝ๊ฐ„์˜ ๋Œ€๊ธฐ ์‹œ๊ฐ„์„ ์ฃผ์–ด ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ formData๋ฅผ ์—…๋ฐ์ดํŠธํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•จ await new Promise((resolve) => setTimeout(resolve, 100)); + console.log("๐Ÿ“ฆ [handleSave] beforeFormSave ์ด๋ฒคํŠธ ํ›„ formData keys:", Object.keys(context.formData || {})); + + // ๊ฒ€์ฆ ์‹คํŒจ ์‹œ ์ €์žฅ ์ค‘๋‹จ + if (beforeSaveEventDetail.validationFailed) { + console.log("โŒ [handleSave] ๊ฒ€์ฆ ์‹คํŒจ๋กœ ์ €์žฅ ์ค‘๋‹จ:", beforeSaveEventDetail.validationErrors); + return false; + } + // ๐Ÿ”ง skipDefaultSave ํ”Œ๋ž˜๊ทธ ํ™•์ธ - SelectedItemsDetailInput ๋“ฑ์—์„œ ์ž์ฒด UPSERT ์ฒ˜๋ฆฌ ์‹œ ๊ธฐ๋ณธ ์ €์žฅ ๊ฑด๋„ˆ๋›ฐ๊ธฐ if (beforeSaveEventDetail.skipDefaultSave) { - return true; } + // ๐Ÿ†• _tableSection_ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธ (TableSectionRenderer ์‚ฌ์šฉ ์‹œ) + // beforeFormSave ์ด๋ฒคํŠธ ํ›„์— ์ฒดํฌํ•ด์•ผ UniversalFormModal์—์„œ ๋ณ‘ํ•ฉ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Œ + const hasTableSectionData = Object.keys(context.formData || {}).some( + (k) => k.startsWith("_tableSection_") || k.startsWith("__tableSection_"), + ); + + if (hasTableSectionData) { + console.log("๐Ÿ“‹ [handleSave] _tableSection_ ๋ฐ์ดํ„ฐ ๊ฐ์ง€ - onSave ์ฝœ๋ฐฑ ๊ฑด๋„ˆ๋›ฐ๊ณ  ํ…Œ์ด๋ธ” ์„น์…˜ ์ €์žฅ ๋กœ์ง ์‚ฌ์šฉ"); + } + + // ๐Ÿ†• EditModal ๋“ฑ์—์„œ ์ „๋‹ฌ๋œ onSave ์ฝœ๋ฐฑ์ด ์žˆ์œผ๋ฉด ์šฐ์„  ์‚ฌ์šฉ + // ๋‹จ, _tableSection_ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์œผ๋ฉด ๊ฑด๋„ˆ๋›ฐ๊ธฐ (handleUniversalFormModalTableSectionSave๊ฐ€ ์ฒ˜๋ฆฌ) + if (onSave && !hasTableSectionData) { + console.log("โœ… [handleSave] onSave ์ฝœ๋ฐฑ ๋ฐœ๊ฒฌ - ์ฝœ๋ฐฑ ์‹คํ–‰ (ํ…Œ์ด๋ธ” ์„น์…˜ ๋ฐ์ดํ„ฐ ์—†์Œ)"); + try { + await onSave(); + return true; + } catch (error) { + console.error("โŒ [handleSave] onSave ์ฝœ๋ฐฑ ์‹คํ–‰ ์˜ค๋ฅ˜:", error); + throw error; + } + } + + console.log("โš ๏ธ [handleSave] ๊ธฐ๋ณธ ์ €์žฅ ๋กœ์ง ์‹คํ–‰ (onSave ์ฝœ๋ฐฑ ์—†์Œ ๋˜๋Š” _tableSection_ ๋ฐ์ดํ„ฐ ์žˆ์Œ)"); + // ๐Ÿ†• ๋ ‰ ๊ตฌ์กฐ ์ปดํฌ๋„ŒํŠธ ์ผ๊ด„ ์ €์žฅ ๊ฐ์ง€ let rackStructureLocations: any[] | undefined; let rackStructureFieldKey = "_rackStructureLocations"; @@ -564,7 +590,6 @@ export class ButtonActionExecutor { firstItem.levelNum !== undefined; if (isNewFormat || isOldFormat) { - rackStructureLocations = value; rackStructureFieldKey = key; break; @@ -577,7 +602,6 @@ export class ButtonActionExecutor { comp.type === "component" && comp.componentId === "rack-structure" && comp.columnName === key, ); if (rackStructureComponentInLayout) { - hasEmptyRackStructureField = true; rackStructureFieldKey = key; } @@ -600,7 +624,6 @@ export class ButtonActionExecutor { !rackStructureLocations; if (isRackStructureScreen) { - alert( "๋ ‰ ๊ตฌ์กฐ ๋“ฑ๋ก ํ™”๋ฉด์ž…๋‹ˆ๋‹ค.\n\n" + "๋ฏธ๋ฆฌ๋ณด๊ธฐ๋ฅผ ๋จผ์ € ์ƒ์„ฑํ•ด์ฃผ์„ธ์š”.\n" + @@ -612,7 +635,6 @@ export class ButtonActionExecutor { // ๋ ‰ ๊ตฌ์กฐ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์œผ๋ฉด ์ผ๊ด„ ์ €์žฅ if (rackStructureLocations && rackStructureLocations.length > 0) { - return await this.handleRackStructureBatchSave(config, context, rackStructureLocations, rackStructureFieldKey); } @@ -635,7 +657,6 @@ export class ButtonActionExecutor { }); if (selectedItemsKeys.length > 0) { - return await this.handleBatchSave(config, context, selectedItemsKeys); } else { console.log("โš ๏ธ [handleSave] SelectedItemsDetailInput ๋ฐ์ดํ„ฐ ๊ฐ์ง€ ์‹คํŒจ - ์ผ๋ฐ˜ ์ €์žฅ ์ง„ํ–‰"); @@ -658,7 +679,6 @@ export class ButtonActionExecutor { }); if (repeaterJsonKeys.length > 0) { - // ๐ŸŽฏ ์ฑ„๋ฒˆ ๊ทœ์น™ ํ• ๋‹น ์ฒ˜๋ฆฌ (RepeaterFieldGroup ์ €์žฅ ์ „์— ์‹คํ–‰) // ๐Ÿ”ง ์ˆ˜์ • ๋ชจ๋“œ ์ฒดํฌ: formData.id๊ฐ€ ์กด์žฌํ•˜๋ฉด UPDATE ๋ชจ๋“œ์ด๋ฏ€๋กœ ์ฑ„๋ฒˆ ์ฝ”๋“œ ์žฌํ• ๋‹น ๊ธˆ์ง€ const isEditModeRepeater = @@ -671,19 +691,16 @@ export class ButtonActionExecutor { if (key.endsWith("_numberingRuleId") && value) { const fieldName = key.replace("_numberingRuleId", ""); fieldsWithNumberingRepeater[fieldName] = value as string; - } } // ๐Ÿ”ง ์ˆ˜์ • ๋ชจ๋“œ์—์„œ๋Š” ์ฑ„๋ฒˆ ์ฝ”๋“œ ํ• ๋‹น ๊ฑด๋„ˆ๋›ฐ๊ธฐ (๊ธฐ์กด ๋ฒˆํ˜ธ ์œ ์ง€) // ์‹ ๊ทœ ๋“ฑ๋ก ๋ชจ๋“œ์—์„œ๋งŒ allocateCode ํ˜ธ์ถœํ•˜์—ฌ ์ƒˆ ๋ฒˆํ˜ธ ํ• ๋‹น if (Object.keys(fieldsWithNumberingRepeater).length > 0 && !isEditModeRepeater) { - const { allocateNumberingCode } = await import("@/lib/api/numberingRule"); for (const [fieldName, ruleId] of Object.entries(fieldsWithNumberingRepeater)) { try { - const allocateResult = await allocateNumberingCode(ruleId); if (allocateResult.success && allocateResult.data?.generatedCode) { @@ -698,7 +715,6 @@ export class ButtonActionExecutor { } } } else if (isEditModeRepeater) { - } // ๐Ÿ†• ์ƒ๋‹จ ํผ ๋ฐ์ดํ„ฐ(๋งˆ์Šคํ„ฐ ์ •๋ณด) ์ถ”์ถœ @@ -709,12 +725,12 @@ export class ButtonActionExecutor { if (fieldKey.startsWith("comp_")) return; if (fieldKey.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i)) return; if (fieldKey.endsWith("_label") || fieldKey.endsWith("_value_label")) return; - + const value = context.formData[fieldKey]; - + // JSON ๋ฐฐ์—ด ๋ฌธ์ž์—ด ์ œ์™ธ (RepeaterFieldGroup ๋ฐ์ดํ„ฐ) if (typeof value === "string" && value.startsWith("[") && value.endsWith("]")) return; - + // ๊ฐ์ฒด ํƒ€์ž…์ธ ๊ฒฝ์šฐ (๋ฒ”์šฉ_ํผ_๋ชจ๋‹ฌ ๋“ฑ) ๋‚ด๋ถ€ ํ•„๋“œ๋ฅผ ํŽผ์ณ์„œ ์ถ”๊ฐ€ if (typeof value === "object" && value !== null && !Array.isArray(value)) { Object.entries(value).forEach(([innerKey, innerValue]) => { @@ -725,7 +741,7 @@ export class ButtonActionExecutor { }); return; } - + // ์œ ํšจํ•œ ๊ฐ’๋งŒ ํฌํ•จ if (value !== undefined && value !== null && value !== "") { masterFields[fieldKey] = value; @@ -736,7 +752,7 @@ export class ButtonActionExecutor { try { const parsedData = JSON.parse(context.formData[key]); const repeaterTargetTable = parsedData[0]?._targetTable; - + if (!repeaterTargetTable) { console.warn(`โš ๏ธ [handleSave] RepeaterFieldGroup targetTable ์—†์Œ (key: ${key})`); continue; @@ -745,11 +761,21 @@ export class ButtonActionExecutor { // ๐Ÿ†• ํ’ˆ๋ชฉ ๊ณ ์œ  ํ•„๋“œ ๋ชฉ๋ก (RepeaterFieldGroup ์„ค์ •์—์„œ ๊ฐ€์ ธ์˜ด) // ์ฒซ ๋ฒˆ์งธ ์•„์ดํ…œ์˜ _repeaterFields์—์„œ ์ถ”์ถœ const repeaterFields: string[] = parsedData[0]?._repeaterFields || []; - const itemOnlyFields = new Set([...repeaterFields, 'id']); // id๋Š” ํ•ญ์ƒ ํฌํ•จ + const itemOnlyFields = new Set([...repeaterFields, "id"]); // id๋Š” ํ•ญ์ƒ ํฌํ•จ for (const item of parsedData) { // ๋ฉ”ํƒ€ ํ•„๋“œ ์ œ๊ฑฐ - const { _targetTable, _isNewItem, _existingRecord, _originalItemIds, _deletedItemIds, _repeaterFields, _subDataSelection, _subDataMaxValue, ...itemData } = item; + const { + _targetTable, + _isNewItem, + _existingRecord, + _originalItemIds, + _deletedItemIds, + _repeaterFields, + _subDataSelection, + _subDataMaxValue, + ...itemData + } = item; // ๐Ÿ”ง ํ’ˆ๋ชฉ ๊ณ ์œ  ํ•„๋“œ๋งŒ ์ถ”์ถœ (RepeaterFieldGroup ์„ค์ • ๊ธฐ๋ฐ˜) const itemOnlyData: Record = {}; @@ -758,37 +784,41 @@ export class ButtonActionExecutor { itemOnlyData[field] = itemData[field]; } }); - + // ๐Ÿ†• ํ•˜์œ„ ๋ฐ์ดํ„ฐ ์„ ํƒ์—์„œ ๊ฐ’ ์ถ”์ถœ (subDataSource ์„ค์ • ๊ธฐ๋ฐ˜) // ํ•„๋“œ ์ •์˜์—์„œ subDataSource.enabled๊ฐ€ true์ด๊ณ  sourceColumn์ด ์„ค์ •๋œ ํ•„๋“œ๋งŒ ์ฒ˜๋ฆฌ - if (_subDataSelection && typeof _subDataSelection === 'object') { + if (_subDataSelection && typeof _subDataSelection === "object") { // _repeaterFieldsConfig์—์„œ subDataSource ์„ค์ • ํ™•์ธ - const fieldsConfig = item._repeaterFieldsConfig as Array<{ - name: string; - subDataSource?: { enabled: boolean; sourceColumn: string }; - }> | undefined; - + 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; - } } }); } else { // ํ•˜์œ„ ํ˜ธํ™˜์„ฑ: fieldsConfig๊ฐ€ ์—†์œผ๋ฉด ๊ธฐ์กด ๋ฐฉ์‹ ์‚ฌ์šฉ Object.keys(_subDataSelection).forEach((subDataKey) => { - if (itemOnlyData[subDataKey] === undefined || itemOnlyData[subDataKey] === null || itemOnlyData[subDataKey] === '') { + if ( + itemOnlyData[subDataKey] === undefined || + itemOnlyData[subDataKey] === null || + itemOnlyData[subDataKey] === "" + ) { const subDataValue = _subDataSelection[subDataKey]; if (subDataValue !== undefined && subDataValue !== null) { itemOnlyData[subDataKey] = subDataValue; - } } }); @@ -799,13 +829,13 @@ export class ButtonActionExecutor { // masterFields: ์ƒ๋‹จ ํผ์—์„œ ์ˆ˜์ •ํ•œ ์ตœ์‹  ๋งˆ์Šคํ„ฐ ์ •๋ณด // itemOnlyData: ํ’ˆ๋ชฉ ๊ณ ์œ  ํ•„๋“œ๋งŒ (ํ’ˆ๋ฒˆ, ํ’ˆ๋ช…, ์ˆ˜๋Ÿ‰ ๋“ฑ) const dataWithMeta: Record = { - ...masterFields, // ์ƒ๋‹จ ๋งˆ์Šคํ„ฐ ์ •๋ณด (์ตœ์‹ ) - ...itemOnlyData, // ํ’ˆ๋ชฉ ๊ณ ์œ  ํ•„๋“œ๋งŒ + ...masterFields, // ์ƒ๋‹จ ๋งˆ์Šคํ„ฐ ์ •๋ณด (์ตœ์‹ ) + ...itemOnlyData, // ํ’ˆ๋ชฉ ๊ณ ์œ  ํ•„๋“œ๋งŒ created_by: context.userId, updated_by: context.userId, company_code: context.companyCode, }; - + // ๋ถˆํ•„์š”ํ•œ ํ•„๋“œ ์ œ๊ฑฐ Object.keys(dataWithMeta).forEach((field) => { if (field.endsWith("_label") || field.endsWith("_value_label") || field.endsWith("_numberingRuleId")) { @@ -819,23 +849,21 @@ export class ButtonActionExecutor { if (isNewRecord) { // INSERT - DynamicFormApi ์‚ฌ์šฉํ•˜์—ฌ ์ œ์–ด๊ด€๋ฆฌ ์‹คํ–‰ delete dataWithMeta.id; - + const insertResult = await DynamicFormApi.saveFormData({ screenId: context.screenId || 0, tableName: repeaterTargetTable, data: dataWithMeta as Record, }); - } else if (item.id && _existingRecord === true) { // UPDATE - ๊ธฐ์กด ๋ ˆ์ฝ”๋“œ const originalData = { id: item.id }; const updatedData = { ...dataWithMeta, id: item.id }; - + const updateResult = await apiClient.put(`/table-management/tables/${repeaterTargetTable}/edit`, { originalData, updatedData, }); - } } } catch (err) { @@ -857,7 +885,6 @@ export class ButtonActionExecutor { // ๋ฒ”์šฉ_ํผ_๋ชจ๋‹ฌ ๋‚ด๋ถ€์— _tableSection_ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ๊ณตํ†ต ํ•„๋“œ + ๊ฐœ๋ณ„ ํ’ˆ๋ชฉ ๋ณ‘ํ•ฉ ์ €์žฅ const universalFormModalResult = await this.handleUniversalFormModalTableSectionSave(config, context, formData); if (universalFormModalResult.handled) { - return universalFormModalResult.success; } @@ -955,13 +982,11 @@ export class ButtonActionExecutor { if (key.endsWith("_numberingRuleId") && value) { const fieldName = key.replace("_numberingRuleId", ""); fieldsWithNumbering[fieldName] = value as string; - } } // ๐Ÿ”ฅ ์ €์žฅ ์‹œ์ ์— allocateCode ํ˜ธ์ถœํ•˜์—ฌ ์‹ค์ œ ์ˆœ๋ฒˆ ์ฆ๊ฐ€ if (Object.keys(fieldsWithNumbering).length > 0) { - const { allocateNumberingCode } = await import("@/lib/api/numberingRule"); let hasAllocationFailure = false; @@ -969,7 +994,6 @@ export class ButtonActionExecutor { for (const [fieldName, ruleId] of Object.entries(fieldsWithNumbering)) { try { - const allocateResult = await allocateNumberingCode(ruleId); if (allocateResult.success && allocateResult.data?.generatedCode) { @@ -1040,12 +1064,10 @@ export class ButtonActionExecutor { const isLinkField = linkFieldPatterns.some((pattern) => key.endsWith(pattern)); if (isLinkField) { cleanedSplitPanelData[key] = value; - } } if (Object.keys(rawSplitPanelData).length > 0) { - } const dataWithUserInfo = { @@ -1059,7 +1081,6 @@ export class ButtonActionExecutor { // ๐Ÿ”ง formData์—์„œ๋„ id ์ œ๊ฑฐ (์‹ ๊ทœ INSERT์ด๋ฏ€๋กœ) if ("id" in dataWithUserInfo && !formData.id) { - delete dataWithUserInfo.id; } @@ -1074,7 +1095,6 @@ export class ButtonActionExecutor { // ๋ฆฌํ”ผํ„ฐ ๋ฐ์ดํ„ฐ๋Š” ๋ณ„๋„์˜ RepeaterFieldGroup/UnifiedRepeater ์ €์žฅ ๋กœ์ง์—์„œ ์ฒ˜๋ฆฌ๋จ for (const key of Object.keys(dataWithUserInfo)) { if (Array.isArray(dataWithUserInfo[key])) { - delete dataWithUserInfo[key]; } } @@ -1083,14 +1103,12 @@ export class ButtonActionExecutor { // formData์˜ ๊ฐ ํ•„๋“œ์—์„œ _deletedItemIds๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธ for (const [key, value] of Object.entries(dataWithUserInfo)) { - let parsedValue = value; // JSON ๋ฌธ์ž์—ด์ธ ๊ฒฝ์šฐ ํŒŒ์‹ฑ ์‹œ๋„ if (typeof value === "string" && value.startsWith("[")) { try { parsedValue = JSON.parse(value); - } catch (e) { // ํŒŒ์‹ฑ ์‹คํŒจํ•˜๋ฉด ์›๋ณธ ๊ฐ’ ์œ ์ง€ } @@ -1102,14 +1120,15 @@ export class ButtonActionExecutor { const targetTable = firstItem?._targetTable; if (deletedItemIds && deletedItemIds.length > 0 && targetTable) { - // ์‚ญ์ œ API ํ˜ธ์ถœ - screenId ์ „๋‹ฌํ•˜์—ฌ ์ œ์–ด๊ด€๋ฆฌ ์‹คํ–‰ ๊ฐ€๋Šฅํ•˜๋„๋ก ํ•จ for (const itemId of deletedItemIds) { try { - - const deleteResult = await DynamicFormApi.deleteFormDataFromTable(itemId, targetTable, context.screenId); + const deleteResult = await DynamicFormApi.deleteFormDataFromTable( + itemId, + targetTable, + context.screenId, + ); if (deleteResult.success) { - } else { console.warn(`โš ๏ธ [handleSave] ํ•ญ๋ชฉ ์‚ญ์ œ ์‹คํŒจ: ${itemId}`, deleteResult.message); } @@ -1128,7 +1147,6 @@ export class ButtonActionExecutor { const joinRelationshipCache: Record = {}; for (const [fieldKey, fieldValue] of Object.entries(context.formData)) { - // JSON ๋ฌธ์ž์—ด์ธ ๊ฒฝ์šฐ ํŒŒ์‹ฑ let parsedData = fieldValue; if (typeof fieldValue === "string" && fieldValue.startsWith("[")) { @@ -1153,14 +1171,13 @@ export class ButtonActionExecutor { // @ts-ignore - window์— ๋™์  ์†์„ฑ ์‚ฌ์šฉ const registeredUnifiedRepeaterTables = Array.from(window.__unifiedRepeaterInstances || []); if (registeredUnifiedRepeaterTables.includes(repeaterTargetTable)) { - continue; } // ๐Ÿ†• ๋งˆ์Šคํ„ฐ-๋””ํ…Œ์ผ ์กฐ์ธ ๊ด€๊ณ„ ์กฐํšŒ (๋ฉ”์ธ ํ…Œ์ด๋ธ” โ†’ ๋ฆฌํ”ผํ„ฐ ํ…Œ์ด๋ธ”) let joinRelationship: { mainColumn: string; detailColumn: string } | null = null; const cacheKey = `${tableName}:${repeaterTargetTable}`; - + if (tableName && repeaterTargetTable && tableName !== repeaterTargetTable) { // ์บ์‹œ์—์„œ ๋จผ์ € ํ™•์ธ if (cacheKey in joinRelationshipCache) { @@ -1168,17 +1185,16 @@ export class ButtonActionExecutor { } else { try { const joinResponse = await apiClient.get( - `/button-dataflow/join-relationship/${tableName}/${repeaterTargetTable}` + `/button-dataflow/join-relationship/${tableName}/${repeaterTargetTable}`, ); if (joinResponse.data?.success && joinResponse.data?.data?.found) { joinRelationship = { mainColumn: joinResponse.data.data.mainColumn, detailColumn: joinResponse.data.data.detailColumn, }; - } } catch (joinError) { - console.warn(`โš ๏ธ [handleSave] ์กฐ์ธ ๊ด€๊ณ„ ์กฐํšŒ ์‹คํŒจ:`, joinError); + console.warn("โš ๏ธ [handleSave] ์กฐ์ธ ๊ด€๊ณ„ ์กฐํšŒ ์‹คํŒจ:", joinError); } // ๊ฒฐ๊ณผ๋ฅผ ์บ์‹œ์— ์ €์žฅ (์—†์–ด๋„ null๋กœ ์ €์žฅํ•˜์—ฌ ์žฌ์กฐํšŒ ๋ฐฉ์ง€) joinRelationshipCache[cacheKey] = joinRelationship; @@ -1196,7 +1212,6 @@ export class ButtonActionExecutor { commonFields[key] = value; } } - } // ๐Ÿ†• ๋ฃจํŠธ ๋ ˆ๋ฒจ formData์—์„œ RepeaterFieldGroup์— ์ „๋‹ฌํ•  ๊ณตํ†ต ํ•„๋“œ ์ถ”์ถœ @@ -1227,7 +1242,6 @@ export class ButtonActionExecutor { if (mainColumnValue !== undefined && mainColumnValue !== null && mainColumnValue !== "") { // ๋ฆฌํ”ผํ„ฐ ํ…Œ์ด๋ธ”์˜ ์กฐ์ธ ์ปฌ๋Ÿผ์— ๋ฉ”์ธ ํ…Œ์ด๋ธ”์˜ ๊ฐ’ ์ฃผ์ž… commonFields[joinRelationship.detailColumn] = mainColumnValue; - } else { console.warn(`โš ๏ธ [handleSave] ์กฐ์ธ ์ปฌ๋Ÿผ ๊ฐ’์ด ์—†์Œ: ${joinRelationship.mainColumn}`); } @@ -1295,19 +1309,15 @@ export class ButtonActionExecutor { originalData, updatedData, }); - } } catch (err) { const error = err as { response?: { data?: unknown; status?: number }; message?: string }; - console.error( - `โŒ [handleSave] RepeaterFieldGroup ์ €์žฅ ์‹คํŒจ (${repeaterTargetTable}):`, - { - status: error.response?.status, - data: error.response?.data, - message: error.message, - fullError: JSON.stringify(error.response?.data, null, 2), - }, - ); + console.error(`โŒ [handleSave] RepeaterFieldGroup ์ €์žฅ ์‹คํŒจ (${repeaterTargetTable}):`, { + status: error.response?.status, + data: error.response?.data, + message: error.message, + fullError: JSON.stringify(error.response?.data, null, 2), + }); } } } @@ -1349,7 +1359,6 @@ export class ButtonActionExecutor { unifiedRepeaterTables.includes(tableName); if (shouldSkipMainSave) { - saveResult = { success: true, message: "RepeaterFieldGroup/RepeatScreenModal/UnifiedRepeater์—์„œ ์ฒ˜๋ฆฌ" }; } else { saveResult = await DynamicFormApi.saveFormData({ @@ -1360,7 +1369,6 @@ export class ButtonActionExecutor { } if (repeatScreenModalKeys.length > 0) { - // ๐Ÿ†• formData์—์„œ ์ฑ„๋ฒˆ ๊ทœ์น™์œผ๋กœ ์ƒ์„ฑ๋œ ๊ฐ’๋“ค ์ถ”์ถœ (์˜ˆ: shipment_plan_no) const numberingFields: Record = {}; for (const [fieldKey, value] of Object.entries(context.formData)) { @@ -1385,29 +1393,27 @@ export class ButtonActionExecutor { } else { try { const joinResponse = await apiClient.get( - `/button-dataflow/join-relationship/${tableName}/${targetTable}` + `/button-dataflow/join-relationship/${tableName}/${targetTable}`, ); if (joinResponse.data?.success && joinResponse.data?.data?.found) { joinRelationship = { mainColumn: joinResponse.data.data.mainColumn, detailColumn: joinResponse.data.data.detailColumn, }; - } } catch (joinError) { - console.warn(`โš ๏ธ [handleSave] RepeatScreenModal ์กฐ์ธ ๊ด€๊ณ„ ์กฐํšŒ ์‹คํŒจ:`, joinError); + console.warn("โš ๏ธ [handleSave] RepeatScreenModal ์กฐ์ธ ๊ด€๊ณ„ ์กฐํšŒ ์‹คํŒจ:", joinError); } joinRelationshipCache[cacheKey] = joinRelationship; } } // ๐Ÿ†• ์กฐ์ธ ์ปฌ๋Ÿผ ๊ฐ’ ์ค€๋น„ (๋ฉ”์ธ ํ…Œ์ด๋ธ”์—์„œ ๊ฐ€์ ธ์˜ด) - let joinColumnData: Record = {}; + const joinColumnData: Record = {}; if (joinRelationship) { const mainColumnValue = context.formData[joinRelationship.mainColumn]; if (mainColumnValue !== undefined && mainColumnValue !== null && mainColumnValue !== "") { joinColumnData[joinRelationship.detailColumn] = mainColumnValue; - } } @@ -1432,7 +1438,6 @@ export class ButtonActionExecutor { `/table-management/tables/${targetTable}/add`, dataWithMeta, ); - } else if (id) { // UPDATE const originalData = { id }; @@ -1442,7 +1447,6 @@ export class ButtonActionExecutor { originalData, updatedData, }); - } } catch (error: any) { console.error(`โŒ [handleSave] ${targetTable} ์ €์žฅ ์‹คํŒจ:`, error.response?.data || error.message); @@ -1455,7 +1459,6 @@ export class ButtonActionExecutor { // ๐Ÿ†• v2-repeat-container ๋ฐ์ดํ„ฐ ์ €์žฅ ์ฒ˜๋ฆฌ (_repeatContainerTables์— ๊ทธ๋ฃนํ™”๋œ ๋ฐ์ดํ„ฐ) const repeatContainerTables = context.formData._repeatContainerTables as Record | undefined; if (repeatContainerTables && Object.keys(repeatContainerTables).length > 0) { - for (const [targetTable, rows] of Object.entries(repeatContainerTables)) { if (!Array.isArray(rows) || rows.length === 0) continue; @@ -1468,29 +1471,27 @@ export class ButtonActionExecutor { } else { try { const joinResponse = await apiClient.get( - `/button-dataflow/join-relationship/${tableName}/${targetTable}` + `/button-dataflow/join-relationship/${tableName}/${targetTable}`, ); if (joinResponse.data?.success && joinResponse.data?.data?.found) { joinRelationship = { mainColumn: joinResponse.data.data.mainColumn, detailColumn: joinResponse.data.data.detailColumn, }; - } } catch (joinError) { - console.warn(`โš ๏ธ [handleSave] RepeatContainer ์กฐ์ธ ๊ด€๊ณ„ ์กฐํšŒ ์‹คํŒจ:`, joinError); + console.warn("โš ๏ธ [handleSave] RepeatContainer ์กฐ์ธ ๊ด€๊ณ„ ์กฐํšŒ ์‹คํŒจ:", joinError); } joinRelationshipCache[cacheKey] = joinRelationship; } } // ์กฐ์ธ ์ปฌ๋Ÿผ ๊ฐ’ ์ค€๋น„ - let joinColumnData: Record = {}; + const joinColumnData: Record = {}; if (joinRelationship) { const mainColumnValue = context.formData[joinRelationship.mainColumn]; if (mainColumnValue !== undefined && mainColumnValue !== null && mainColumnValue !== "") { joinColumnData[joinRelationship.detailColumn] = mainColumnValue; - } } @@ -1499,7 +1500,6 @@ export class ButtonActionExecutor { // ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์€ ํ–‰์€ ๊ฑด๋„ˆ๋›ฐ๊ธฐ if (_isDirty === false) { - continue; } @@ -1520,7 +1520,6 @@ export class ButtonActionExecutor { `/table-management/tables/${targetTable}/add`, dataWithMeta, ); - } else { // UPDATE (id๊ฐ€ ์žˆ์œผ๋ฉด ๊ธฐ์กด ๋ ˆ์ฝ”๋“œ) const originalData = { id }; @@ -1530,10 +1529,12 @@ export class ButtonActionExecutor { originalData, updatedData, }); - } } catch (error: any) { - console.error(`โŒ [handleSave] ${targetTable} ์ €์žฅ ์‹คํŒจ (RepeatContainer):`, error.response?.data || error.message); + console.error( + `โŒ [handleSave] ${targetTable} ์ €์žฅ ์‹คํŒจ (RepeatContainer):`, + error.response?.data || error.message, + ); } } } @@ -1550,7 +1551,6 @@ export class ButtonActionExecutor { }>; if (aggregationConfigs && aggregationConfigs.length > 0) { - for (const config of aggregationConfigs) { const { targetTable, targetColumn, joinKey, aggregatedValue, sourceValue } = config; @@ -1565,7 +1565,6 @@ export class ButtonActionExecutor { originalData, updatedData, }); - } catch (error: any) { console.error(`โŒ [handleSave] ${targetTable} ์ง‘๊ณ„ ์ €์žฅ ์‹คํŒจ:`, error.response?.data || error.message); } @@ -1579,17 +1578,16 @@ export class ButtonActionExecutor { // ๐Ÿ”ฅ ์ €์žฅ ์„ฑ๊ณต ํ›„ ์—ฐ๊ฒฐ๋œ ์ œ์–ด ์‹คํ–‰ (dataflowTiming์ด 'after'์ธ ๊ฒฝ์šฐ) if (config.enableDataflowControl && config.dataflowConfig) { - // ํ…Œ์ด๋ธ” ์„น์…˜ ๋ฐ์ดํ„ฐ ํŒŒ์‹ฑ (comp_๋กœ ์‹œ์ž‘ํ•˜๋Š” ํ•„๋“œ์— JSON ๋ฐฐ์—ด์ด ์žˆ๋Š” ๊ฒฝ์šฐ) // ์ž…๊ณ  ํ™”๋ฉด ๋“ฑ์—์„œ ํ’ˆ๋ชฉ ๋ชฉ๋ก์ด comp_xxx ํ•„๋“œ์— JSON ๋ฌธ์ž์—ด๋กœ ์ €์žฅ๋จ const formData: Record = (saveResult.data || context.formData || {}) as Record; let parsedSectionData: any[] = []; - + // comp_๋กœ ์‹œ์ž‘ํ•˜๋Š” ํ•„๋“œ์—์„œ ํ…Œ์ด๋ธ” ์„น์…˜ ๋ฐ์ดํ„ฐ ์ฐพ๊ธฐ - const compFieldKey = Object.keys(formData).find(key => - key.startsWith("comp_") && typeof formData[key] === "string" + const compFieldKey = Object.keys(formData).find( + (key) => key.startsWith("comp_") && typeof formData[key] === "string", ); - + if (compFieldKey) { try { const sectionData = JSON.parse(formData[compFieldKey]); @@ -1600,20 +1598,19 @@ export class ButtonActionExecutor { const { _isNewItem, _targetTable, _existingRecord, ...cleanItem } = item; // ๊ณตํ†ต ํ•„๋“œ(comp_ ํ•„๋“œ ์ œ์™ธ) + ์„น์…˜ ์•„์ดํ…œ ๋ณ‘ํ•ฉ const commonFields: Record = {}; - Object.keys(formData).forEach(key => { + Object.keys(formData).forEach((key) => { if (!key.startsWith("comp_") && !key.endsWith("_numberingRuleId")) { commonFields[key] = formData[key]; } }); return { ...commonFields, ...cleanItem }; }); - } } catch (parseError) { console.warn("โš ๏ธ [handleSave] ํ…Œ์ด๋ธ” ์„น์…˜ ๋ฐ์ดํ„ฐ ํŒŒ์‹ฑ ์‹คํŒจ:", parseError); } } - + // ์ €์žฅ๋œ ๋ฐ์ดํ„ฐ๋ฅผ context์— ์ถ”๊ฐ€ํ•˜์—ฌ ํ”Œ๋กœ์šฐ์— ์ „๋‹ฌ const contextWithSavedData = { ...context, @@ -1692,7 +1689,6 @@ export class ButtonActionExecutor { // ๋ณตํ•ฉํ‚ค์ธ ๊ฒฝ์šฐ ๋กœ๊ทธ ์ถœ๋ ฅ if (primaryKeys.length > 1) { - console.log(`๐Ÿ“ ์ฒซ ๋ฒˆ์งธ ํ‚ค (${primaryKeyColumn}) ๊ฐ’์„ ์‚ฌ์šฉ: ${value}`); } @@ -1771,7 +1767,6 @@ export class ButtonActionExecutor { const zone = firstLocation.zone; if (warehouseCode && floor && zone) { - try { // search ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐฑ์—”๋“œ์—์„œ ํ•„ํ„ฐ๋ง (filters๋Š” ๋ฐฑ์—”๋“œ์—์„œ ์ฒ˜๋ฆฌ ์•ˆ๋จ) const existingResponse = await DynamicFormApi.getTableData(tableName, { @@ -1851,7 +1846,6 @@ export class ButtonActionExecutor { for (let i = 0; i < recordsToInsert.length; i++) { const record = recordsToInsert[i]; try { - const result = await DynamicFormApi.saveFormData({ screenId, tableName, @@ -1942,14 +1936,12 @@ export class ButtonActionExecutor { ); if (modalComponent) { modalComponentConfig = modalComponent.componentConfig || modalComponent.properties?.componentConfig; - } } // ๐Ÿ†• ์•„์ง๋„ ์„ค์ •์„ ์ฐพ์ง€ ๋ชปํ–ˆ์œผ๋ฉด ํ™”๋ฉด ๋ ˆ์ด์•„์›ƒ API์—์„œ ๊ฐ€์ ธ์˜ค๊ธฐ if (!modalComponentConfig && screenId) { try { - const { screenApi } = await import("@/lib/api/screen"); const layoutData = await screenApi.getLayout(screenId); @@ -1961,7 +1953,6 @@ export class ButtonActionExecutor { ); if (modalLayout) { modalComponentConfig = modalLayout.properties?.componentConfig || modalLayout.componentConfig; - } } } catch (error) { @@ -2000,8 +1991,7 @@ export class ButtonActionExecutor { // ๐ŸŽฏ ์ฑ„๋ฒˆ ๊ทœ์น™ ํ• ๋‹น ์ฒ˜๋ฆฌ (์ €์žฅ ์‹œ์ ์— ์‹ค์ œ ์ˆœ๋ฒˆ ์ฆ๊ฐ€) // ๐Ÿ”ง ์ˆ˜์ • ๋ชจ๋“œ ์ฒดํฌ: formData.id ๋˜๋Š” originalGroupedData๊ฐ€ ์žˆ์œผ๋ฉด UPDATE ๋ชจ๋“œ const isEditModeUniversal = - (formData.id !== undefined && formData.id !== null && formData.id !== "") || - originalGroupedData.length > 0; + (formData.id !== undefined && formData.id !== null && formData.id !== "") || originalGroupedData.length > 0; const fieldsWithNumbering: Record = {}; @@ -2010,7 +2000,6 @@ export class ButtonActionExecutor { if (key.endsWith("_numberingRuleId") && value) { const fieldName = key.replace("_numberingRuleId", ""); fieldsWithNumbering[fieldName] = value as string; - } } @@ -2019,19 +2008,16 @@ export class ButtonActionExecutor { if (key.endsWith("_numberingRuleId") && value && !fieldsWithNumbering[key.replace("_numberingRuleId", "")]) { const fieldName = key.replace("_numberingRuleId", ""); fieldsWithNumbering[fieldName] = value as string; - } } // ๐Ÿ”ง ์ˆ˜์ • ๋ชจ๋“œ์—์„œ๋Š” ์ฑ„๋ฒˆ ์ฝ”๋“œ ํ• ๋‹น ๊ฑด๋„ˆ๋›ฐ๊ธฐ (๊ธฐ์กด ๋ฒˆํ˜ธ ์œ ์ง€) // ์‹ ๊ทœ ๋“ฑ๋ก ๋ชจ๋“œ์—์„œ๋งŒ allocateCode ํ˜ธ์ถœํ•˜์—ฌ ์ƒˆ ๋ฒˆํ˜ธ ํ• ๋‹น if (Object.keys(fieldsWithNumbering).length > 0 && !isEditModeUniversal) { - const { allocateNumberingCode } = await import("@/lib/api/numberingRule"); for (const [fieldName, ruleId] of Object.entries(fieldsWithNumbering)) { try { - const allocateResult = await allocateNumberingCode(ruleId); if (allocateResult.success && allocateResult.data?.generatedCode) { @@ -2050,7 +2036,6 @@ export class ButtonActionExecutor { } } } else if (isEditModeUniversal) { - } try { @@ -2080,7 +2065,6 @@ export class ButtonActionExecutor { ); if (hasSeparateTargetTable && Object.keys(commonFieldsData).length > 0) { - const mainRowToSave = { ...commonFieldsData, ...userInfo }; // ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ œ๊ฑฐ @@ -2120,12 +2104,10 @@ export class ButtonActionExecutor { if (!mainSaveResult.success) { throw new Error(mainSaveResult.message || "๋ฉ”์ธ ๋ฐ์ดํ„ฐ ์ €์žฅ ์‹คํŒจ"); } - } // ๊ฐ ํ…Œ์ด๋ธ” ์„น์…˜ ์ฒ˜๋ฆฌ for (const [sectionId, currentItems] of Object.entries(tableSectionData)) { - // ๐Ÿ†• ํ•ด๋‹น ์„น์…˜์˜ ์„ค์ • ์ฐพ๊ธฐ const sectionConfig = sections.find((s) => s.id === sectionId); const targetTableName = sectionConfig?.tableConfig?.saveConfig?.targetTable; @@ -2207,7 +2189,6 @@ export class ButtonActionExecutor { const hasChanges = this.checkForChanges(originalItem, currentDataWithCommon); if (hasChanges) { - // ๋ณ€๊ฒฝ๋œ ํ•„๋“œ๋งŒ ์ถ”์ถœํ•˜์—ฌ ๋ถ€๋ถ„ ์—…๋ฐ์ดํŠธ const updateResult = await DynamicFormApi.updateFormDataPartial( item.id, @@ -2222,19 +2203,36 @@ export class ButtonActionExecutor { updatedCount++; } else { - } } // 3๏ธโƒฃ ์‚ญ์ œ๋œ ํ’ˆ๋ชฉ DELETE (์›๋ณธ์—๋Š” ์žˆ์ง€๋งŒ ํ˜„์žฌ์—๋Š” ์—†๋Š” ํ•ญ๋ชฉ) + // ๐Ÿ†• ํ…Œ์ด๋ธ” ์„น์…˜๋ณ„ ์›๋ณธ ๋ฐ์ดํ„ฐ ์‚ฌ์šฉ (์šฐ์„ ), ์—†์œผ๋ฉด ์ „์—ญ originalGroupedData ์‚ฌ์šฉ + const sectionOriginalKey = `_originalTableSectionData_${sectionId}`; + const sectionOriginalData: any[] = modalData[sectionOriginalKey] || formData[sectionOriginalKey] || []; + + // ์„น์…˜๋ณ„ ์›๋ณธ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์œผ๋ฉด ์‚ฌ์šฉ, ์—†์œผ๋ฉด ์ „์—ญ originalGroupedData ์‚ฌ์šฉ + const originalDataForDelete = sectionOriginalData.length > 0 ? sectionOriginalData : originalGroupedData; + + console.log(`๐Ÿ” [DELETE ๋น„๊ต] ์„น์…˜ ${sectionId}:`, { + sectionOriginalKey, + sectionOriginalCount: sectionOriginalData.length, + globalOriginalCount: originalGroupedData.length, + usingData: sectionOriginalData.length > 0 ? "์„น์…˜๋ณ„ ์›๋ณธ" : "์ „์—ญ ์›๋ณธ", + currentCount: currentItems.length, + }); + // โš ๏ธ id ํƒ€์ž… ํ†ต์ผ: ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ๋น„๊ต (์ˆซ์ž vs ๋ฌธ์ž์—ด ๋ถˆ์ผ์น˜ ๋ฐฉ์ง€) const currentIds = new Set(currentItems.map((item) => String(item.id)).filter(Boolean)); - const deletedItems = originalGroupedData.filter((orig) => orig.id && !currentIds.has(String(orig.id))); + const deletedItems = originalDataForDelete.filter((orig) => orig.id && !currentIds.has(String(orig.id))); for (const deletedItem of deletedItems) { - // screenId ์ „๋‹ฌํ•˜์—ฌ ์ œ์–ด๊ด€๋ฆฌ ์‹คํ–‰ ๊ฐ€๋Šฅํ•˜๋„๋ก ํ•จ - const deleteResult = await DynamicFormApi.deleteFormDataFromTable(deletedItem.id, saveTableName, context.screenId); + const deleteResult = await DynamicFormApi.deleteFormDataFromTable( + deletedItem.id, + saveTableName, + context.screenId, + ); if (!deleteResult.success) { throw new Error(deleteResult.message || "ํ’ˆ๋ชฉ ์‚ญ์ œ ์‹คํŒจ"); @@ -2274,7 +2272,6 @@ export class ButtonActionExecutor { // ์†Œ์Šค ํ…Œ์ด๋ธ”๊ณผ ์ผ์น˜ํ•˜๋Š” ์„น์…˜๋งŒ ์ œ์–ด ์‹คํ–‰ if (sectionTargetTable === flowSourceInfo.sourceTable && sectionItems.length > 0) { - // ๊ณตํ†ต ํ•„๋“œ + ํ•ด๋‹น ์„น์…˜ ๋ฐ์ดํ„ฐ ๋ณ‘ํ•ฉํ•˜์—ฌ sourceData ์ƒ์„ฑ const sourceData = sectionItems.map((item: any) => ({ ...commonFieldsData, @@ -2297,7 +2294,6 @@ export class ButtonActionExecutor { // ๋งค์นญ๋˜๋Š” ์„น์…˜์ด ์—†์œผ๋ฉด ๋ฉ”์ธ ํ…Œ์ด๋ธ” ํ™•์ธ if (!controlExecuted && tableName === flowSourceInfo.sourceTable) { - const controlContext: ButtonActionContext = { ...context, selectedRowsData: [commonFieldsData], @@ -2444,10 +2440,10 @@ export class ButtonActionExecutor { // ๋ชจ๋“  ๊ทธ๋ฃน์˜ ์นดํ‹ฐ์…˜ ๊ณฑ ์ƒ์„ฑ const entryArrays = groupArrays.map((g) => g.entries); - + // ๐Ÿ†• ๋ชจ๋“  ๊ทธ๋ฃน์ด ๋น„์–ด์žˆ๋Š”์ง€ ํ™•์ธ const allGroupsEmpty = entryArrays.every((arr) => arr.length === 0); - + let combinations: any[][]; if (allGroupsEmpty) { // ๐Ÿ†• ๋ชจ๋“  ๊ทธ๋ฃน์ด ๋น„์–ด์žˆ์œผ๋ฉด ๋นˆ ์กฐํ•ฉ ํ•˜๋‚˜ ์ƒ์„ฑ (ํ’ˆ๋ชฉ ๊ธฐ๋ณธ ์ •๋ณด๋งŒ์œผ๋กœ ์ €์žฅ) @@ -2609,7 +2605,6 @@ export class ButtonActionExecutor { const primaryKeysResult = await DynamicFormApi.getTablePrimaryKeys(tableName); if (primaryKeysResult.success && primaryKeysResult.data) { primaryKeys = primaryKeysResult.data; - } } catch (error) { console.warn("๊ธฐ๋ณธํ‚ค ์กฐํšŒ ์‹คํŒจ, ํด๋ฐฑ ๋ฐฉ๋ฒ• ์‚ฌ์šฉ:", error); @@ -2624,7 +2619,6 @@ export class ButtonActionExecutor { if (primaryKeys.length > 0) { const primaryKey = primaryKeys[0]; // ์ฒซ ๋ฒˆ์งธ ๊ธฐ๋ณธํ‚ค ์‚ฌ์šฉ deleteId = rowData[primaryKey]; - } // 2์ˆœ์œ„: ํด๋ฐฑ - ์ผ๋ฐ˜์ ์ธ ID ํ•„๋“œ๋ช…๋“ค ์‹œ๋„ @@ -2657,7 +2651,6 @@ export class ButtonActionExecutor { const idField = Object.keys(rowData).find((key) => key.endsWith("_id") && rowData[key]); if (idField) deleteId = rowData[idField]; } - } console.log("์„ ํƒ๋œ ํ–‰ ๋ฐ์ดํ„ฐ:", rowData); @@ -2681,15 +2674,12 @@ export class ButtonActionExecutor { // ๋ฐ์ดํ„ฐ ์†Œ์Šค์— ๋”ฐ๋ผ ์ ์ ˆํ•œ ์ƒˆ๋กœ๊ณ ์นจ ํ˜ธ์ถœ if (flowSelectedData && flowSelectedData.length > 0) { - context.onFlowRefresh?.(); // ํ”Œ๋กœ์šฐ ์ƒˆ๋กœ๊ณ ์นจ } else { - context.onRefresh?.(); // ํ…Œ์ด๋ธ” ์ƒˆ๋กœ๊ณ ์นจ // ๐Ÿ†• ๋ถ„ํ•  ํŒจ๋„ ๋“ฑ ์ „์—ญ ํ…Œ์ด๋ธ” ์ƒˆ๋กœ๊ณ ์นจ ์ด๋ฒคํŠธ ๋ฐœ์ƒ window.dispatchEvent(new CustomEvent("refreshTable")); - } toast.success(config.successMessage || `${dataToDelete.length}๊ฐœ ํ•ญ๋ชฉ์ด ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.`); @@ -2706,7 +2696,6 @@ export class ButtonActionExecutor { if (!deleteResult.success) { throw new Error(deleteResult.message || "์‚ญ์ œ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค."); } - } else { throw new Error("์‚ญ์ œ์— ํ•„์š”ํ•œ ์ •๋ณด๊ฐ€ ๋ถ€์กฑํ•ฉ๋‹ˆ๋‹ค. (ID, ํ…Œ์ด๋ธ”๋ช… ๋˜๋Š” ํ™”๋ฉดID ๋ˆ„๋ฝ)"); } @@ -2804,7 +2793,6 @@ export class ButtonActionExecutor { if (relatedConfig?.modalLink?.dataMapping && relatedConfig.modalLink.dataMapping.length > 0) { relatedConfig.modalLink.dataMapping.forEach((mapping) => { - if (mapping.sourceField === "value") { initialData[mapping.targetField] = selectedItem.value; } else if (mapping.sourceField === "id") { @@ -3062,7 +3050,6 @@ export class ButtonActionExecutor { config: ButtonActionConfig, context: ButtonActionContext, ): Promise { - // ๐Ÿ†• 1. ํ˜„์žฌ ํ™”๋ฉด์˜ TableList ๋˜๋Š” SplitPanelLayout ์ž๋™ ๊ฐ์ง€ let dataSourceId = config.dataSourceId; @@ -3111,7 +3098,6 @@ export class ButtonActionExecutor { toast.warning("์„ ํƒ๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๋จผ์ € ํ•ญ๋ชฉ์„ ์„ ํƒํ•ด์ฃผ์„ธ์š”."); return false; } - } catch (error) { console.error("โŒ ๋ฐ์ดํ„ฐ ํ™•์ธ ์‹คํŒจ:", error); toast.error("๋ฐ์ดํ„ฐ ํ™•์ธ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค."); @@ -3159,7 +3145,6 @@ export class ButtonActionExecutor { }); finalTitle = titleParts.join(""); - } // ๊ธฐ์กด ๋ฐฉ์‹: {tableName.columnName} ํŒจํ„ด (์šฐ์„ ์ˆœ์œ„ 2) else if (config.modalTitle) { @@ -3211,7 +3196,6 @@ export class ButtonActionExecutor { // ๐Ÿ†• ํ•„๋“œ ๋งคํ•‘ ์ ์šฉ (์†Œ์Šค ์ปฌ๋Ÿผ โ†’ ํƒ€๊ฒŸ ์ปฌ๋Ÿผ) const parentData = { ...rawParentData }; if (config.fieldMappings && Array.isArray(config.fieldMappings) && config.fieldMappings.length > 0) { - config.fieldMappings.forEach((mapping: { sourceField: string; targetField: string }) => { if (mapping.sourceField && mapping.targetField && rawParentData[mapping.sourceField] !== undefined) { // ํƒ€๊ฒŸ ํ•„๋“œ์— ์†Œ์Šค ํ•„๋“œ ๊ฐ’ ๋ณต์‚ฌ @@ -3468,7 +3452,6 @@ export class ButtonActionExecutor { // ๐Ÿ†• ๋ถ„ํ•  ํŒจ๋„ ํ™”๋ฉด์ธ ๊ฒฝ์šฐ ScreenModal ์‚ฌ์šฉ (editData ์ „๋‹ฌ) if (hasSplitPanel) { - const screenModalEvent = new CustomEvent("openScreenModal", { detail: { screenId: config.targetScreenId, @@ -3486,7 +3469,7 @@ export class ButtonActionExecutor { const modalEvent = new CustomEvent("openEditModal", { detail: { screenId: config.targetScreenId, - title: isCreateMode ? (config.editModalTitle || "๋ฐ์ดํ„ฐ ๋ณต์‚ฌ") : (config.editModalTitle || "๋ฐ์ดํ„ฐ ์ˆ˜์ •"), + title: isCreateMode ? config.editModalTitle || "๋ฐ์ดํ„ฐ ๋ณต์‚ฌ" : config.editModalTitle || "๋ฐ์ดํ„ฐ ์ˆ˜์ •", description: description, modalSize: config.modalSize || "lg", editData: rowData, @@ -3571,7 +3554,6 @@ export class ButtonActionExecutor { fieldsToRemove.forEach((field) => { if (copiedData[field] !== undefined) { delete copiedData[field]; - } }); @@ -3589,22 +3571,24 @@ export class ButtonActionExecutor { ]; // ๐Ÿ†• ํ™”๋ฉด ์„ค์ •์—์„œ ์ฑ„๋ฒˆ ๊ทœ์น™ ๊ฐ€์ ธ์˜ค๊ธฐ - let screenNumberingRules: Record = {}; + const screenNumberingRules: Record = {}; if (config.targetScreenId) { try { const { screenApi } = await import("@/lib/api/screen"); const layout = await screenApi.getLayout(config.targetScreenId); - + // ๋ ˆ์ด์•„์›ƒ์—์„œ ์ฑ„๋ฒˆ ๊ทœ์น™์ด ์„ค์ •๋œ ์ปดํฌ๋„ŒํŠธ ์ฐพ๊ธฐ const findNumberingRules = (components: any[]): void => { for (const comp of components) { const compConfig = comp.componentConfig || {}; // text-input ์ปดํฌ๋„ŒํŠธ์˜ ์ฑ„๋ฒˆ ๊ทœ์น™ ํ™•์ธ - if (compConfig.autoGeneration?.type === "numbering_rule" && compConfig.autoGeneration?.options?.numberingRuleId) { + if ( + compConfig.autoGeneration?.type === "numbering_rule" && + compConfig.autoGeneration?.options?.numberingRuleId + ) { const columnName = compConfig.columnName || comp.columnName; if (columnName) { screenNumberingRules[columnName] = compConfig.autoGeneration.options.numberingRuleId; - } } // ์ค‘์ฒฉ๋œ ์ปดํฌ๋„ŒํŠธ ํ™•์ธ @@ -3613,11 +3597,10 @@ export class ButtonActionExecutor { } } }; - + if (layout?.components) { findNumberingRules(layout.components); } - } catch (error) { console.warn("โš ๏ธ ํ™”๋ฉด ๋ ˆ์ด์•„์›ƒ ์กฐํšŒ ์‹คํŒจ:", error); } @@ -3629,11 +3612,12 @@ export class ButtonActionExecutor { if (copiedData[field] !== undefined) { const originalValue = copiedData[field]; const ruleIdKey = `${field}_numberingRuleId`; - + // 1์ˆœ์œ„: ์›๋ณธ ๋ฐ์ดํ„ฐ์—์„œ ์ฑ„๋ฒˆ ๊ทœ์น™ ID ํ™•์ธ // 2์ˆœ์œ„: ํ™”๋ฉด ์„ค์ •์—์„œ ์ฑ„๋ฒˆ ๊ทœ์น™ ID ํ™•์ธ const numberingRuleId = rowData[ruleIdKey] || screenNumberingRules[field]; - const hasNumberingRule = numberingRuleId !== undefined && numberingRuleId !== null && numberingRuleId !== ""; + const hasNumberingRule = + numberingRuleId !== undefined && numberingRuleId !== null && numberingRuleId !== ""; // ํ’ˆ๋ชฉ์ฝ”๋“œ๋ฅผ ๋ฌด์กฐ๊ฑด ๊ณต๋ฐฑ์œผ๋กœ ์ดˆ๊ธฐํ™” copiedData[field] = ""; @@ -3641,9 +3625,7 @@ export class ButtonActionExecutor { // ์ฑ„๋ฒˆ ๊ทœ์น™ ID๊ฐ€ ์žˆ์œผ๋ฉด ๋ณต์‚ฌ (์ €์žฅ ์‹œ ์ž๋™ ์ƒ์„ฑ) if (hasNumberingRule) { copiedData[ruleIdKey] = numberingRuleId; - } else { - } resetFieldName = field; @@ -3656,7 +3638,6 @@ export class ButtonActionExecutor { writerFields.forEach((field) => { if (copiedData[field] !== undefined && context.userId) { copiedData[field] = context.userId; - } }); @@ -3743,7 +3724,6 @@ export class ButtonActionExecutor { * ์ œ์–ด ์ „์šฉ ์•ก์…˜ ์ฒ˜๋ฆฌ (์กฐ๊ฑด ์ฒดํฌ๋งŒ ์ˆ˜ํ–‰) */ private static async handleControl(config: ButtonActionConfig, context: ButtonActionContext): Promise { - // ๐Ÿ”ฅ ์ œ์–ด ์กฐ๊ฑด์ด ์„ค์ •๋˜์–ด ์žˆ๋Š”์ง€ ํ™•์ธ if (!config.dataflowConfig || !config.enableDataflowControl) { @@ -3766,16 +3746,12 @@ export class ButtonActionExecutor { // ์„ค์ •์ด ์—†์œผ๋ฉด ์ž๋™ ํŒ๋‹จ (์šฐ์„ ์ˆœ์œ„ ์ˆœ์„œ๋Œ€๋กœ) if (context.flowSelectedData && context.flowSelectedData.length > 0) { controlDataSource = "flow-selection"; - } else if (context.selectedRowsData && context.selectedRowsData.length > 0) { controlDataSource = "table-selection"; - } else if (context.formData && Object.keys(context.formData).length > 0) { controlDataSource = "form"; - } else { controlDataSource = "form"; // ๊ธฐ๋ณธ๊ฐ’ - } } @@ -3794,7 +3770,6 @@ export class ButtonActionExecutor { const isFlowMode = config.dataflowConfig?.controlMode === "flow" || hasFlowConfig; if (isFlowMode && config.dataflowConfig?.flowConfig) { - const { flowId, executionTiming } = config.dataflowConfig.flowConfig; if (!flowId) { @@ -3831,7 +3806,6 @@ export class ButtonActionExecutor { case "table-selection": if (context.selectedRowsData && context.selectedRowsData.length > 0) { sourceData = context.selectedRowsData; - } else { console.warn("โš ๏ธ table-selection ๋ชจ๋“œ์ด์ง€๋งŒ ์„ ํƒ๋œ ํ–‰์ด ์—†์Šต๋‹ˆ๋‹ค."); toast.error("ํ…Œ์ด๋ธ”์—์„œ ์ฒ˜๋ฆฌํ•  ํ•ญ๋ชฉ์„ ๋จผ์ € ์„ ํƒํ•ด์ฃผ์„ธ์š”."); @@ -3842,7 +3816,6 @@ export class ButtonActionExecutor { case "form": if (context.formData && Object.keys(context.formData).length > 0) { sourceData = [context.formData]; - } else { console.warn("โš ๏ธ form ๋ชจ๋“œ์ด์ง€๋งŒ ํผ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค."); } @@ -3882,16 +3855,13 @@ export class ButtonActionExecutor { ...context.formData, })); dataSourceType = "both"; - } else { sourceData = context.selectedRowsData; dataSourceType = "table-selection"; - } } else if (context.formData && Object.keys(context.formData).length > 0) { sourceData = [context.formData]; dataSourceType = "form"; - } break; } @@ -3903,18 +3873,15 @@ export class ButtonActionExecutor { }); if (result.success) { - toast.success("ํ”Œ๋กœ์šฐ ์‹คํ–‰์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."); // ํ”Œ๋กœ์šฐ ์ƒˆ๋กœ๊ณ ์นจ (ํ”Œ๋กœ์šฐ ์œ„์ ฏ์šฉ) if (context.onFlowRefresh) { - context.onFlowRefresh(); } // ํ…Œ์ด๋ธ” ์ƒˆ๋กœ๊ณ ์นจ (์ผ๋ฐ˜ ํ…Œ์ด๋ธ”์šฉ) if (context.onRefresh) { - context.onRefresh(); } @@ -3930,7 +3897,6 @@ export class ButtonActionExecutor { return false; } } else if (config.dataflowConfig?.controlMode === "relationship" && config.dataflowConfig?.relationshipConfig) { - // ๐Ÿ”ฅ table-selection ๋ชจ๋“œ์ผ ๋•Œ ์„ ํƒ๋œ ํ–‰ ๋ฐ์ดํ„ฐ๋ฅผ formData์— ๋ณ‘ํ•ฉ let mergedFormData = { ...context.formData } || {}; @@ -3942,7 +3908,6 @@ export class ButtonActionExecutor { // ์„ ํƒ๋œ ์ฒซ ๋ฒˆ์งธ ํ–‰์˜ ๋ฐ์ดํ„ฐ๋ฅผ formData์— ๋ณ‘ํ•ฉ const selectedRowData = context.selectedRowsData[0]; mergedFormData = { ...mergedFormData, ...selectedRowData }; - } // ์ƒˆ๋กœ์šด ImprovedButtonActionExecutor ์‚ฌ์šฉ @@ -3962,7 +3927,6 @@ export class ButtonActionExecutor { }); if (executionResult.success) { - toast.success(config.successMessage || "๊ด€๊ณ„ ์‹คํ–‰์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."); // ์ƒˆ๋กœ๊ณ ์นจ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ @@ -3999,7 +3963,6 @@ export class ButtonActionExecutor { * ๋‹ค์ค‘ ์ œ์–ด ์ˆœ์ฐจ ์‹คํ–‰ ์ง€์› */ public static async executeAfterSaveControl(config: ButtonActionConfig, context: ButtonActionContext): Promise { - // ์ œ์–ด ๋ฐ์ดํ„ฐ ์†Œ์Šค ๊ฒฐ์ • let controlDataSource = config.dataflowConfig?.controlDataSource; if (!controlDataSource) { @@ -4016,7 +3979,6 @@ export class ButtonActionExecutor { // ๐Ÿ”ฅ ๋‹ค์ค‘ ์ œ์–ด ์ง€์› (flowControls ๋ฐฐ์—ด) const flowControls = config.dataflowConfig?.flowControls || []; if (flowControls.length > 0) { - // ์ˆœ์„œ๋Œ€๋กœ ์ •๋ ฌ const sortedControls = [...flowControls].sort((a: any, b: any) => (a.order || 0) - (b.order || 0)); @@ -4031,11 +3993,9 @@ export class ButtonActionExecutor { let sourceData: any[]; if (context.selectedRowsData && context.selectedRowsData.length > 0) { sourceData = context.selectedRowsData; - } else { const savedData = context.savedData || context.formData || {}; sourceData = Array.isArray(savedData) ? savedData : [savedData]; - } let allSuccess = true; @@ -4052,7 +4012,6 @@ export class ButtonActionExecutor { // executionTiming ์ฒดํฌ (after๋งŒ ์‹คํ–‰) if (control.executionTiming && control.executionTiming !== "after") { - continue; } @@ -4075,7 +4034,6 @@ export class ButtonActionExecutor { }); if (result.success) { - } else { console.error(`โŒ [${i + 1}/${sortedControls.length}] ์ œ์–ด ์‹คํŒจ: ${control.flowName} - ${result.message}`); allSuccess = false; @@ -4118,7 +4076,6 @@ export class ButtonActionExecutor { // ๐Ÿ”ฅ ๊ธฐ์กด ๋‹จ์ผ ์ œ์–ด ์‹คํ–‰ (ํ•˜์œ„ ํ˜ธํ™˜์„ฑ) // dataflowTiming์ด 'after'๊ฐ€ ์•„๋‹ˆ๋ฉด ์‹คํ–‰ํ•˜์ง€ ์•Š์Œ if (config.dataflowTiming && config.dataflowTiming !== "after") { - return; } @@ -4128,7 +4085,6 @@ export class ButtonActionExecutor { // executionTiming ์ฒดํฌ const flowTiming = config.dataflowConfig.flowConfig.executionTiming; if (flowTiming && flowTiming !== "after") { - return; } @@ -4146,11 +4102,9 @@ export class ButtonActionExecutor { let sourceData: any[]; if (context.selectedRowsData && context.selectedRowsData.length > 0) { sourceData = context.selectedRowsData; - } else { const savedData = context.savedData || context.formData || {}; sourceData = Array.isArray(savedData) ? savedData : [savedData]; - } // repeat-screen-modal ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์œผ๋ฉด ๋ณ‘ํ•ฉ @@ -4158,7 +4112,6 @@ export class ButtonActionExecutor { key.startsWith("_repeatScreenModal_"), ); if (repeatScreenModalKeys.length > 0) { - } const result = await executeNodeFlow(flowId, { @@ -4168,7 +4121,6 @@ export class ButtonActionExecutor { }); if (result.success) { - toast.success("์ œ์–ด ๋กœ์ง ์‹คํ–‰์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."); } else { console.error("โŒ ์ €์žฅ ํ›„ ๋…ธ๋“œ ํ”Œ๋กœ์šฐ ์‹คํ–‰ ์‹คํŒจ:", result); @@ -4184,7 +4136,6 @@ export class ButtonActionExecutor { // ๊ด€๊ณ„ ๊ธฐ๋ฐ˜ ์ œ์–ด ์‹คํ–‰ if (config.dataflowConfig?.controlMode === "relationship" && config.dataflowConfig?.relationshipConfig) { - const buttonConfig = { actionType: config.type, dataflowConfig: config.dataflowConfig, @@ -4205,7 +4156,6 @@ export class ButtonActionExecutor { ); if (executionResult.success) { - // ์„ฑ๊ณต ํ† ์ŠคํŠธ๋Š” save ์•ก์…˜์—์„œ ์ด๋ฏธ ํ‘œ์‹œํ–ˆ์œผ๋ฏ€๋กœ ์ถ”๊ฐ€๋กœ ํ‘œ์‹œํ•˜์ง€ ์•Š์Œ } else { console.error("โŒ ์ €์žฅ ํ›„ ์ œ์–ด ์‹คํ–‰ ์‹คํŒจ:", executionResult); @@ -4218,12 +4168,10 @@ export class ButtonActionExecutor { * ๊ด€๊ณ„๋„์—์„œ ๊ฐ€์ ธ์˜จ ์•ก์…˜๋“ค์„ ์‹คํ–‰ */ private static async executeRelationshipActions(actions: any[], context: ButtonActionContext): Promise { - for (let i = 0; i < actions.length; i++) { const action = actions[i]; try { - const actionType = action.actionType || action.type; // actionType ์šฐ์„ , type ํด๋ฐฑ switch (actionType) { @@ -4274,13 +4222,11 @@ export class ButtonActionExecutor { * ์ €์žฅ ์•ก์…˜ ์‹คํ–‰ */ private static async executeActionSave(action: any, context: ButtonActionContext): Promise { - // ๐ŸŽฏ ํ•„๋“œ ๋งคํ•‘ ์ •๋ณด ์‚ฌ์šฉํ•˜์—ฌ ์ €์žฅ ๋ฐ์ดํ„ฐ ๊ตฌ์„ฑ let saveData: Record = {}; // ์•ก์…˜์— ํ•„๋“œ ๋งคํ•‘ ์ •๋ณด๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธ if (action.fieldMappings && Array.isArray(action.fieldMappings)) { - // ํ•„๋“œ ๋งคํ•‘์— ๋”ฐ๋ผ ๋ฐ์ดํ„ฐ ๊ตฌ์„ฑ action.fieldMappings.forEach((mapping: any) => { const { sourceField, targetField, defaultValue, valueType } = mapping; @@ -4299,7 +4245,6 @@ export class ButtonActionExecutor { // ํƒ€๊ฒŸ ํ•„๋“œ์— ๊ฐ’ ์„ค์ • if (targetField && value !== undefined) { saveData[targetField] = value; - } }); } else { @@ -4324,7 +4269,6 @@ export class ButtonActionExecutor { }); if (result.success) { - toast.success("๋ฐ์ดํ„ฐ๊ฐ€ ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."); } else { throw new Error(result.message || "์ €์žฅ ์‹คํŒจ"); @@ -4340,17 +4284,14 @@ export class ButtonActionExecutor { * ์—…๋ฐ์ดํŠธ ์•ก์…˜ ์‹คํ–‰ */ private static async executeActionUpdate(action: any, context: ButtonActionContext): Promise { - // ๐ŸŽฏ ํ•„๋“œ ๋งคํ•‘ ์ •๋ณด ์‚ฌ์šฉํ•˜์—ฌ ์—…๋ฐ์ดํŠธ ๋ฐ์ดํ„ฐ ๊ตฌ์„ฑ let updateData: Record = {}; // ์•ก์…˜์— ํ•„๋“œ ๋งคํ•‘ ์ •๋ณด๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธ if (action.fieldMappings && Array.isArray(action.fieldMappings)) { - // ๐Ÿ”‘ ๋จผ์ € ์„ ํƒ๋œ ๋ฐ์ดํ„ฐ์˜ ๋ชจ๋“  ํ•„๋“œ๋ฅผ ๊ธฐ๋ณธ์œผ๋กœ ํฌํ•จ (๊ธฐ๋ณธํ‚ค ๋ณด์กด) if (context.selectedRowsData?.[0]) { updateData = { ...context.selectedRowsData[0] }; - } // ํ•„๋“œ ๋งคํ•‘์— ๋”ฐ๋ผ ๋ฐ์ดํ„ฐ ๊ตฌ์„ฑ (๋ฎ์–ด์“ฐ๊ธฐ) @@ -4371,7 +4312,6 @@ export class ButtonActionExecutor { // ํƒ€๊ฒŸ ํ•„๋“œ์— ๊ฐ’ ์„ค์ • (๋ฎ์–ด์“ฐ๊ธฐ) if (targetField && value !== undefined) { updateData[targetField] = value; - } }); } else { @@ -4418,7 +4358,6 @@ export class ButtonActionExecutor { }); if (result.success) { - toast.success("๋ฐ์ดํ„ฐ๊ฐ€ ์—…๋ฐ์ดํŠธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."); } else { throw new Error(result.message || "์—…๋ฐ์ดํŠธ ์‹คํŒจ"); @@ -4434,7 +4373,6 @@ export class ButtonActionExecutor { * ์‚ญ์ œ ์•ก์…˜ ์‹คํ–‰ */ private static async executeActionDelete(action: any, context: ButtonActionContext): Promise { - // ์‹ค์ œ ์‚ญ์ œ ๋กœ์ง (๊ธฐ์กด handleDelete์™€ ์œ ์‚ฌ) if (!context.selectedRowsData || context.selectedRowsData.length === 0) { throw new Error("์‚ญ์ œํ•  ํ•ญ๋ชฉ์„ ์„ ํƒํ•ด์ฃผ์„ธ์š”."); @@ -4476,7 +4414,6 @@ export class ButtonActionExecutor { const result = await DynamicFormApi.deleteFormDataFromTable(deleteId, context.tableName, context.screenId); if (result.success) { - toast.success("๋ฐ์ดํ„ฐ๊ฐ€ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."); } else { throw new Error(result.message || "์‚ญ์ œ ์‹คํŒจ"); @@ -4498,7 +4435,6 @@ export class ButtonActionExecutor { // ์•ก์…˜์— ํ•„๋“œ ๋งคํ•‘ ์ •๋ณด๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธ if (action.fieldMappings && Array.isArray(action.fieldMappings)) { - // ๐ŸŽฏ ์ฒดํฌ๋ฐ•์Šค๋กœ ์„ ํƒ๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธ if (!context.selectedRowsData || context.selectedRowsData.length === 0) { throw new Error("์‚ฝ์ž…ํ•  ์†Œ์Šค ๋ฐ์ดํ„ฐ๋ฅผ ์„ ํƒํ•ด์ฃผ์„ธ์š”. (ํ…Œ์ด๋ธ”์—์„œ ์ฒดํฌ๋ฐ•์Šค ์„ ํƒ ํ•„์š”)"); @@ -4518,18 +4454,15 @@ export class ButtonActionExecutor { if (valueType === "form" && context.formData && sourceField) { // ํผ ๋ฐ์ดํ„ฐ์—์„œ ๊ฐ€์ ธ์˜ค๊ธฐ value = context.formData[sourceField]; - } else if (valueType === "selection" && sourceField) { // ์„ ํƒ๋œ ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ์—์„œ ๊ฐ€์ ธ์˜ค๊ธฐ (๋‹ค์–‘ํ•œ ํ•„๋“œ๋ช… ์‹œ๋„) value = sourceData[sourceField] || sourceData[sourceField + "_name"] || // ์กฐ์ธ๋œ ํ•„๋“œ (_name ์ ‘๋ฏธ์‚ฌ) sourceData[sourceField + "Name"]; // ์นด๋ฉœ์ผ€์ด์Šค - } else if (valueType === "default" || (defaultValue !== undefined && defaultValue !== "")) { // ๊ธฐ๋ณธ๊ฐ’ ์‚ฌ์šฉ (valueType์ด "default"์ด๊ฑฐ๋‚˜ defaultValue๊ฐ€ ์žˆ์„ ๋•Œ) value = defaultValue; - } else { console.warn(`โš ๏ธ ๋งคํ•‘ ์‹คํŒจ: ${sourceField} โ†’ ${targetField} (๊ฐ’์„ ์ฐพ์„ ์ˆ˜ ์—†์Œ)`); console.warn(` - valueType: ${valueType}, defaultValue: ${defaultValue}`); @@ -4543,11 +4476,9 @@ export class ButtonActionExecutor { insertData[targetField] = value; } }); - } else { // ํ•„๋“œ ๋งคํ•‘์ด ์—†์œผ๋ฉด ํผ ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ๋ณธ์œผ๋กœ ์‚ฌ์šฉ insertData = { ...context.formData }; - } try { @@ -4564,7 +4495,6 @@ export class ButtonActionExecutor { const result = await DynamicFormApi.saveFormData(formDataPayload); if (result.success) { - toast.success("๋ฐ์ดํ„ฐ๊ฐ€ ํƒ€๊ฒŸ ํ…Œ์ด๋ธ”์— ์„ฑ๊ณต์ ์œผ๋กœ ์‚ฝ์ž…๋˜์—ˆ์Šต๋‹ˆ๋‹ค."); } else { throw new Error(result.message || "์‚ฝ์ž… ์‹คํŒจ"); @@ -4699,19 +4629,18 @@ export class ButtonActionExecutor { const downloadResponse = await DynamicFormApi.getMasterDetailDownloadData( context.screenId, - context.filterConditions + context.filterConditions, ); if (downloadResponse.success && downloadResponse.data) { dataToExport = downloadResponse.data.data; visibleColumns = downloadResponse.data.columns; - + // ํ—ค๋”์™€ ์ปฌ๋Ÿผ ๋งคํ•‘ columnLabels = {}; downloadResponse.data.columns.forEach((col: string, index: number) => { columnLabels![col] = downloadResponse.data.headers[index] || col; }); - } else { toast.error("๋งˆ์Šคํ„ฐ-๋””ํ…Œ์ผ ๋ฐ์ดํ„ฐ ์กฐํšŒ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค."); return false; @@ -4724,7 +4653,7 @@ export class ButtonActionExecutor { visibleColumns!.forEach((columnName: string) => { const label = columnLabels?.[columnName] || columnName; let value = row[columnName]; - + // ์นดํ…Œ๊ณ ๋ฆฌ ์ฝ”๋“œ๋ฅผ ๋ผ๋ฒจ๋กœ ๋ณ€ํ™˜ (CATEGORY_๋กœ ์‹œ์ž‘ํ•˜๋Š” ๊ฐ’) if (value && typeof value === "string" && value.includes("CATEGORY_")) { // ๋จผ์ € _label ํ•„๋“œ ํ™•์ธ (API์—์„œ ์ œ๊ณตํ•˜๋Š” ๊ฒฝ์šฐ) @@ -4739,7 +4668,7 @@ export class ButtonActionExecutor { } } } - + filteredRow[label] = value; }); return filteredRow; @@ -5016,7 +4945,10 @@ export class ButtonActionExecutor { else if (categoryMap[columnName] && typeof value === "string") { // ์‰ผํ‘œ๋กœ ๊ตฌ๋ถ„๋œ ๋‹ค์ค‘ ๊ฐ’ ์ฒ˜๋ฆฌ if (value.includes(",")) { - const values = value.split(",").map((v) => v.trim()).filter((v) => v); + const values = value + .split(",") + .map((v) => v.trim()) + .filter((v) => v); const labels = values.map((v) => categoryMap[columnName][v] || v); value = labels.join(", "); } else if (categoryMap[columnName][value]) { @@ -5065,18 +4997,19 @@ export class ButtonActionExecutor { if (context.screenId) { const { DynamicFormApi } = await import("@/lib/api/dynamicForm"); const relationResponse = await DynamicFormApi.getMasterDetailRelation(context.screenId); - + if (relationResponse.success && relationResponse.data) { isMasterDetail = true; masterDetailRelation = relationResponse.data; - + // ๋ฒ„ํŠผ ์„ค์ •์—์„œ ์ฑ„๋ฒˆ ๊ทœ์น™ ๋“ฑ ์ถ”๊ฐ€ ์„ค์ • ๊ฐ€์ ธ์˜ค๊ธฐ // ์—…๋กœ๋“œ ํ›„ ์ œ์–ด: excelAfterUploadFlows๋ฅผ ์šฐ์„  ์‚ฌ์šฉ (ํ†ตํ•ฉ๋œ ์„ค์ •) // masterDetailExcel.afterUploadFlows๋Š” ๋ ˆ๊ฑฐ์‹œ ํ˜ธํ™˜์„ฑ์„ ์œ„ํ•ด fallback์œผ๋กœ๋งŒ ์‚ฌ์šฉ - const afterUploadFlows = config.excelAfterUploadFlows?.length > 0 - ? config.excelAfterUploadFlows - : config.masterDetailExcel?.afterUploadFlows; - + const afterUploadFlows = + config.excelAfterUploadFlows?.length > 0 + ? config.excelAfterUploadFlows + : config.masterDetailExcel?.afterUploadFlows; + if (config.masterDetailExcel) { masterDetailExcelConfig = { ...config.masterDetailExcel, @@ -5104,7 +5037,6 @@ export class ButtonActionExecutor { afterUploadFlows, }; } - } } @@ -5127,7 +5059,7 @@ export class ButtonActionExecutor { const modalId = `excel-upload-${context.tableName || ""}`; const storageKey = `modal_size_${modalId}_${context.userId || "guest"}`; - root.render( + root.render( React.createElement(ExcelUploadModal, { open: true, onOpenChange: (open: boolean) => { @@ -5200,7 +5132,6 @@ export class ButtonActionExecutor { autoSubmit: config.barcodeAutoSubmit || false, userId: context.userId, onScanSuccess: (barcode: string) => { - // ๋Œ€์ƒ ํ•„๋“œ์— ๊ฐ’ ์ž…๋ ฅ if (config.barcodeTargetField && context.onFormDataChange) { context.onFormDataChange({ @@ -5386,16 +5317,16 @@ export class ButtonActionExecutor { if (response.data.success) { const data = response.data.data; - + // ๋ณ€๊ฒฝ๋œ ํ…Œ์ด๋ธ”/์ปฌ๋Ÿผ ๋ชฉ๋ก ์ƒ์„ฑ const changedList = data.affectedData .map((d: any) => `${d.tableName}.${d.columnName}: ${d.rowsUpdated}๊ฑด`) .join(", "); - + toast.success( `์ฝ”๋“œ ๋ณ‘ํ•ฉ ์™„๋ฃŒ! ${data.affectedData.length}๊ฐœ ํ…Œ์ด๋ธ”/์ปฌ๋Ÿผ, ${data.totalRowsUpdated}๊ฐœ ํ–‰ ์—…๋ฐ์ดํŠธ`, ); - + console.log("์ฝ”๋“œ ๋ณ‘ํ•ฉ ๊ฒฐ๊ณผ:", data.affectedData); // ํ™”๋ฉด ์ƒˆ๋กœ๊ณ ์นจ @@ -5426,7 +5357,6 @@ export class ButtonActionExecutor { */ private static async handleTrackingStart(config: ButtonActionConfig, context: ButtonActionContext): Promise { try { - // ์ด๋ฏธ ์ถ”์  ์ค‘์ธ์ง€ ํ™•์ธ if (this.trackingIntervalId) { toast.warning("์ด๋ฏธ ์œ„์น˜ ์ถ”์ ์ด ์ง„ํ–‰ ์ค‘์ž…๋‹ˆ๋‹ค."); @@ -5493,7 +5423,6 @@ export class ButtonActionExecutor { updateField: "departure", updateValue: departure, }); - } catch { // ์ปฌ๋Ÿผ์ด ์—†์œผ๋ฉด ๋ฌด์‹œ } @@ -5508,7 +5437,6 @@ export class ButtonActionExecutor { updateField: "arrival", updateValue: arrival, }); - } catch { // ์ปฌ๋Ÿผ์ด ์—†์œผ๋ฉด ๋ฌด์‹œ } @@ -5677,7 +5605,6 @@ export class ButtonActionExecutor { updateValue: update.value, }); } - } else { console.warn("โš ๏ธ trip_id์— ํ•ด๋‹นํ•˜๋Š” ๋ ˆ์ฝ”๋“œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Œ:", tripId); } @@ -5704,7 +5631,6 @@ export class ButtonActionExecutor { updateValue: update.value, }); } - } catch (vehicleError) { console.warn("โš ๏ธ vehicles ํ…Œ์ด๋ธ” ์ €์žฅ ์‹คํŒจ:", vehicleError); } @@ -5770,7 +5696,6 @@ export class ButtonActionExecutor { // ์ปฌ๋Ÿผ์ด ์—†์œผ๋ฉด ๋ฌด์‹œ } } - } } catch (statusError) { console.warn("โš ๏ธ ์ƒํƒœ ๋ณ€๊ฒฝ ์‹คํŒจ:", statusError); @@ -5824,7 +5749,6 @@ export class ButtonActionExecutor { }); if (!response.data?.success) { - return null; } @@ -5832,7 +5756,6 @@ export class ButtonActionExecutor { const rows = response.data?.data?.data || response.data?.data?.rows || []; if (!rows.length) { - return null; } @@ -5933,7 +5856,6 @@ export class ButtonActionExecutor { const response = await apiClient.post("/dynamic-form/location-history", locationData); if (response.data?.success) { - } else { console.warn("โš ๏ธ ์œ„์น˜ ์ด๋ ฅ ์ €์žฅ ์‹คํŒจ:", response.data); } @@ -5964,7 +5886,6 @@ export class ButtonActionExecutor { updateField: "longitude", updateValue: longitude, }); - } catch (vehicleUpdateError) { // ์ปฌ๋Ÿผ์ด ์—†์œผ๋ฉด ์กฐ์šฉํžˆ ๋ฌด์‹œ console.warn("โš ๏ธ vehicles ํ…Œ์ด๋ธ” ์œ„์น˜ ์—…๋ฐ์ดํŠธ ์‹คํŒจ (๋ฌด์‹œ):", vehicleUpdateError); @@ -6010,7 +5931,6 @@ export class ButtonActionExecutor { */ private static async handleTransferData(config: ButtonActionConfig, context: ButtonActionContext): Promise { try { - // ์„ ํƒ๋œ ํ–‰ ๋ฐ์ดํ„ฐ ํ™•์ธ const selectedRows = context.selectedRowsData || context.flowSelectedData || []; @@ -6387,7 +6307,6 @@ export class ButtonActionExecutor { */ private static async handleSwapFields(config: ButtonActionConfig, context: ButtonActionContext): Promise { try { - const { formData, onFormDataChange } = context; // ๊ตํ™˜ํ•  ํ•„๋“œ ํ™•์ธ @@ -6436,7 +6355,6 @@ export class ButtonActionExecutor { */ private static async handleQuickInsert(config: ButtonActionConfig, context: ButtonActionContext): Promise { try { - const quickInsertConfig = config.quickInsertConfig; if (!quickInsertConfig?.targetTable) { toast.error("๋Œ€์ƒ ํ…Œ์ด๋ธ”์ด ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค."); @@ -6445,14 +6363,12 @@ export class ButtonActionExecutor { // โœ… allComponents๊ฐ€ ์žˆ์œผ๋ฉด ๊ธฐ์กด ํ•„์ˆ˜ ํ•ญ๋ชฉ ๊ฒ€์ฆ ์ˆ˜ํ–‰ if (context.allComponents && context.allComponents.length > 0) { - const requiredValidation = this.validateRequiredFields(context); if (!requiredValidation.isValid) { console.log("โŒ [handleQuickInsert] ํ•„์ˆ˜ ํ•ญ๋ชฉ ๋ˆ„๋ฝ:", requiredValidation.missingFields); toast.error(`ํ•„์ˆ˜ ํ•ญ๋ชฉ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”: ${requiredValidation.missingFields.join(", ")}`); return false; } - } // โœ… quickInsert ์ „์šฉ ๊ฒ€์ฆ: component ํƒ€์ž… ๋งคํ•‘์—์„œ ๊ฐ’์ด ๋น„์–ด์žˆ๋Š”์ง€ ํ™•์ธ @@ -6737,7 +6653,6 @@ export class ButtonActionExecutor { ); if (response.data?.success) { - // ์ €์žฅ ํ›„ ๋™์ž‘ ์„ค์ • ๋กœ๊ทธ console.log("๐Ÿ“ afterInsert ์„ค์ •:", quickInsertConfig.afterInsert); @@ -6752,7 +6667,6 @@ export class ButtonActionExecutor { if (typeof window !== "undefined") { window.dispatchEvent(new CustomEvent("refreshTable")); window.dispatchEvent(new CustomEvent("refreshCardDisplay")); - } } @@ -6798,7 +6712,6 @@ export class ButtonActionExecutor { context: ButtonActionContext, ): Promise { try { - // ๐Ÿ†• ์ถœ๋ฐœ์ง€/๋„์ฐฉ์ง€ ํ•„์ˆ˜ ์ฒดํฌ (์šดํ–‰ ์‹œ์ž‘ ๋ชจ๋“œ์ผ ๋•Œ๋งŒ) // updateTrackingMode๊ฐ€ "start"์ด๊ฑฐ๋‚˜ updateTargetValue๊ฐ€ "active"/"inactive"์ธ ๊ฒฝ์šฐ const isStartMode = @@ -6865,7 +6778,6 @@ export class ButtonActionExecutor { if (config.confirmMessage) { const confirmed = window.confirm(config.confirmMessage); if (!confirmed) { - return false; } } @@ -6977,7 +6889,6 @@ export class ButtonActionExecutor { const { apiClient } = await import("@/lib/api/client"); for (const [field, value] of Object.entries(updates)) { - const response = await apiClient.put("/dynamic-form/update-field", { tableName: targetTableName, keyField: keyField, @@ -7012,7 +6923,6 @@ export class ButtonActionExecutor { // onSave ์ฝœ๋ฐฑ์ด ์žˆ์œผ๋ฉด ์‚ฌ์šฉ if (onSave) { - try { await onSave(); toast.success(config.successMessage || "์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."); @@ -7026,7 +6936,6 @@ export class ButtonActionExecutor { // API๋ฅผ ํ†ตํ•œ ์ง์ ‘ ์ €์žฅ (๊ธฐ์กด ๋ฐฉ์‹: formData์— PK๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ) if (tableName && formData) { - try { // PK ํ•„๋“œ ์ฐพ๊ธฐ (id ๋˜๋Š” ํ…Œ์ด๋ธ”๋ช…_id) const pkField = formData.id !== undefined ? "id" : `${tableName}_id`;