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/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx b/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx index 9b8e7cf0..dd03df3e 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,16 +1613,19 @@ 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], ); // ์ˆ˜์ • ๋ฒ„ํŠผ ํ•ธ๋“ค๋Ÿฌ @@ -1681,10 +1730,44 @@ export const SplitPanelLayoutComponent: React.FC // ๊ธฐ์กด ์ž๋™ ํŽธ์ง‘ ๋ชจ๋“œ (์ธ๋ผ์ธ ํŽธ์ง‘ ๋ชจ๋‹ฌ) 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], ); // ์ˆ˜์ • ๋ชจ๋‹ฌ ์ €์žฅ