diff --git a/backend-node/src/services/nodeFlowExecutionService.ts b/backend-node/src/services/nodeFlowExecutionService.ts index 9bc59d97..a2a8aef1 100644 --- a/backend-node/src/services/nodeFlowExecutionService.ts +++ b/backend-node/src/services/nodeFlowExecutionService.ts @@ -2830,12 +2830,12 @@ export class NodeFlowExecutionService { inputData: any, context: ExecutionContext ): Promise { - const { conditions, logic } = node.data; + const { conditions, logic, targetLookup } = node.data; logger.info( `๐Ÿ” ์กฐ๊ฑด ๋…ธ๋“œ ์‹คํ–‰ - inputData ํƒ€์ž…: ${typeof inputData}, ๋ฐฐ์—ด ์—ฌ๋ถ€: ${Array.isArray(inputData)}, ๊ธธ์ด: ${Array.isArray(inputData) ? inputData.length : "N/A"}` ); - logger.info(`๐Ÿ” ์กฐ๊ฑด ๊ฐœ์ˆ˜: ${conditions?.length || 0}, ๋กœ์ง: ${logic}`); + logger.info(`๐Ÿ” ์กฐ๊ฑด ๊ฐœ์ˆ˜: ${conditions?.length || 0}, ๋กœ์ง: ${logic}, ํƒ€๊ฒŸ์กฐํšŒ: ${targetLookup ? targetLookup.tableName : "์—†์Œ"}`); if (inputData) { console.log( @@ -2865,6 +2865,9 @@ export class NodeFlowExecutionService { // ๋ฐฐ์—ด์˜ ๊ฐ ํ•ญ๋ชฉ์— ๋Œ€ํ•ด ์กฐ๊ฑด ํ‰๊ฐ€ (EXISTS ์กฐ๊ฑด์€ ๋น„๋™๊ธฐ) for (const item of inputData) { + // ํƒ€๊ฒŸ ํ…Œ์ด๋ธ” ์กฐํšŒ (DB ๊ธฐ์กด๊ฐ’ ๋น„๊ต์šฉ) + const targetRow = await this.lookupTargetRow(targetLookup, item, context); + const results: boolean[] = []; for (const condition of conditions) { @@ -2887,9 +2890,14 @@ export class NodeFlowExecutionService { `๐Ÿ” EXISTS ์กฐ๊ฑด: ${condition.field} (${fieldValue}) ${condition.operator} ${condition.lookupTable}.${condition.lookupField} => ${existsResult}` ); } else { - // ์ผ๋ฐ˜ ์—ฐ์‚ฐ์ž ์ฒ˜๋ฆฌ + // ๋น„๊ต๊ฐ’ ๊ฒฐ์ •: static(๊ณ ์ •๊ฐ’) / field(๊ฐ™์€ ๋ฐ์ดํ„ฐ ๋‚ด ํ•„๋“œ) / target(DB ๊ธฐ์กด๊ฐ’) let compareValue = condition.value; - if (condition.valueType === "field") { + if (condition.valueType === "target" && targetRow) { + compareValue = targetRow[condition.value]; + logger.info( + `๐ŸŽฏ ํƒ€๊ฒŸ(DB) ๋น„๊ต: ${condition.field} (${fieldValue}) vs DB.${condition.value} (${compareValue})` + ); + } else if (condition.valueType === "field") { compareValue = item[condition.value]; logger.info( `๐Ÿ”„ ํ•„๋“œ ์ฐธ์กฐ ๋น„๊ต: ${condition.field} (${fieldValue}) vs ${condition.value} (${compareValue})` @@ -2931,6 +2939,9 @@ export class NodeFlowExecutionService { } // ๋‹จ์ผ ๊ฐ์ฒด์ธ ๊ฒฝ์šฐ + // ํƒ€๊ฒŸ ํ…Œ์ด๋ธ” ์กฐํšŒ (DB ๊ธฐ์กด๊ฐ’ ๋น„๊ต์šฉ) + const targetRow = await this.lookupTargetRow(targetLookup, inputData, context); + const results: boolean[] = []; for (const condition of conditions) { @@ -2953,9 +2964,14 @@ export class NodeFlowExecutionService { `๐Ÿ” EXISTS ์กฐ๊ฑด: ${condition.field} (${fieldValue}) ${condition.operator} ${condition.lookupTable}.${condition.lookupField} => ${existsResult}` ); } else { - // ์ผ๋ฐ˜ ์—ฐ์‚ฐ์ž ์ฒ˜๋ฆฌ + // ๋น„๊ต๊ฐ’ ๊ฒฐ์ •: static(๊ณ ์ •๊ฐ’) / field(๊ฐ™์€ ๋ฐ์ดํ„ฐ ๋‚ด ํ•„๋“œ) / target(DB ๊ธฐ์กด๊ฐ’) let compareValue = condition.value; - if (condition.valueType === "field") { + if (condition.valueType === "target" && targetRow) { + compareValue = targetRow[condition.value]; + logger.info( + `๐ŸŽฏ ํƒ€๊ฒŸ(DB) ๋น„๊ต: ${condition.field} (${fieldValue}) vs DB.${condition.value} (${compareValue})` + ); + } else if (condition.valueType === "field") { compareValue = inputData[condition.value]; logger.info( `๐Ÿ”„ ํ•„๋“œ ์ฐธ์กฐ ๋น„๊ต: ${condition.field} (${fieldValue}) vs ${condition.value} (${compareValue})` @@ -2990,6 +3006,63 @@ export class NodeFlowExecutionService { }; } + /** + * ์กฐ๊ฑด ๋…ธ๋“œ์˜ ํƒ€๊ฒŸ ํ…Œ์ด๋ธ” ์กฐํšŒ (DB ๊ธฐ์กด๊ฐ’ ๋น„๊ต์šฉ) + * targetLookup ์„ค์ •์ด ์žˆ์„ ๋•Œ, ์†Œ์Šค ๋ฐ์ดํ„ฐ์˜ ํ‚ค๊ฐ’์œผ๋กœ DB์—์„œ ๊ธฐ์กด ๋ ˆ์ฝ”๋“œ๋ฅผ ์กฐํšŒ + */ + private static async lookupTargetRow( + targetLookup: any, + sourceRow: any, + context: ExecutionContext + ): Promise { + if (!targetLookup?.tableName || !targetLookup?.lookupKeys?.length) { + return null; + } + + try { + const whereConditions = targetLookup.lookupKeys + .map((key: any, idx: number) => `"${key.targetField}" = $${idx + 1}`) + .join(" AND "); + + const lookupValues = targetLookup.lookupKeys.map( + (key: any) => sourceRow[key.sourceField] + ); + + // ํ‚ค๊ฐ’์ด ๋น„์–ด์žˆ์œผ๋ฉด ์กฐํšŒ ๋ถˆํ•„์š” + if (lookupValues.some((v: any) => v === null || v === undefined || v === "")) { + logger.info(`โš ๏ธ ์กฐ๊ฑด ๋…ธ๋“œ ํƒ€๊ฒŸ ์กฐํšŒ: ํ‚ค๊ฐ’์ด ๋น„์–ด์žˆ์–ด ์Šคํ‚ต`); + return null; + } + + // company_code ํ•„ํ„ฐ๋ง (๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ) + const companyCode = context.buttonContext?.companyCode || sourceRow.company_code; + let sql = `SELECT * FROM "${targetLookup.tableName}" WHERE ${whereConditions}`; + const params = [...lookupValues]; + + if (companyCode && companyCode !== "*") { + sql += ` AND company_code = $${params.length + 1}`; + params.push(companyCode); + } + + sql += " LIMIT 1"; + + logger.info(`๐ŸŽฏ ์กฐ๊ฑด ๋…ธ๋“œ ํƒ€๊ฒŸ ์กฐํšŒ: ${targetLookup.tableName}, ์กฐ๊ฑด: ${whereConditions}, ๊ฐ’: ${JSON.stringify(lookupValues)}`); + + const targetRow = await queryOne(sql, params); + + if (targetRow) { + logger.info(`๐ŸŽฏ ํƒ€๊ฒŸ ๋ฐ์ดํ„ฐ ์กฐํšŒ ์„ฑ๊ณต`); + } else { + logger.info(`๐ŸŽฏ ํƒ€๊ฒŸ ๋ฐ์ดํ„ฐ ์—†์Œ (์‹ ๊ทœ ๋ ˆ์ฝ”๋“œ)`); + } + + return targetRow; + } catch (error: any) { + logger.warn(`โš ๏ธ ์กฐ๊ฑด ๋…ธ๋“œ ํƒ€๊ฒŸ ์กฐํšŒ ์‹คํŒจ: ${error.message}`); + return null; + } + } + /** * EXISTS_IN / NOT_EXISTS_IN ์กฐ๊ฑด ํ‰๊ฐ€ * ๋‹ค๋ฅธ ํ…Œ์ด๋ธ”์— ๊ฐ’์ด ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธ diff --git a/frontend/components/dataflow/node-editor/panels/properties/ConditionProperties.tsx b/frontend/components/dataflow/node-editor/panels/properties/ConditionProperties.tsx index a2d060d4..76354925 100644 --- a/frontend/components/dataflow/node-editor/panels/properties/ConditionProperties.tsx +++ b/frontend/components/dataflow/node-editor/panels/properties/ConditionProperties.tsx @@ -251,6 +251,14 @@ export function ConditionProperties({ nodeId, data }: ConditionPropertiesProps) const [logic, setLogic] = useState<"AND" | "OR">(data.logic || "AND"); const [availableFields, setAvailableFields] = useState([]); + // ํƒ€๊ฒŸ ์กฐํšŒ ์„ค์ • (DB ๊ธฐ์กด๊ฐ’ ๋น„๊ต์šฉ) + const [targetLookup, setTargetLookup] = useState<{ + tableName: string; + tableLabel?: string; + lookupKeys: Array<{ sourceField: string; targetField: string; sourceFieldLabel?: string }>; + } | undefined>(data.targetLookup); + const [targetLookupColumns, setTargetLookupColumns] = useState([]); + // EXISTS ์—ฐ์‚ฐ์ž์šฉ ์ƒํƒœ const [allTables, setAllTables] = useState([]); const [tableColumnsCache, setTableColumnsCache] = useState>({}); @@ -262,8 +270,20 @@ export function ConditionProperties({ nodeId, data }: ConditionPropertiesProps) setDisplayName(data.displayName || "์กฐ๊ฑด ๋ถ„๊ธฐ"); setConditions(data.conditions || []); setLogic(data.logic || "AND"); + setTargetLookup(data.targetLookup); }, [data]); + // targetLookup ํ…Œ์ด๋ธ” ๋ณ€๊ฒฝ ์‹œ ์ปฌ๋Ÿผ ๋ชฉ๋ก ๋กœ๋“œ + useEffect(() => { + if (targetLookup?.tableName) { + loadTableColumns(targetLookup.tableName).then((cols) => { + setTargetLookupColumns(cols); + }); + } else { + setTargetLookupColumns([]); + } + }, [targetLookup?.tableName]); + // ์ „์ฒด ํ…Œ์ด๋ธ” ๋ชฉ๋ก ๋กœ๋“œ (EXISTS ์—ฐ์‚ฐ์ž์šฉ) useEffect(() => { const loadAllTables = async () => { @@ -559,6 +579,47 @@ export function ConditionProperties({ nodeId, data }: ConditionPropertiesProps) }); }; + // ํƒ€๊ฒŸ ์กฐํšŒ ํ…Œ์ด๋ธ” ๋ณ€๊ฒฝ + const handleTargetLookupTableChange = async (tableName: string) => { + await ensureTablesLoaded(); + const tableInfo = allTables.find((t) => t.tableName === tableName); + const newLookup = { + tableName, + tableLabel: tableInfo?.tableLabel || tableName, + lookupKeys: targetLookup?.lookupKeys || [], + }; + setTargetLookup(newLookup); + updateNode(nodeId, { targetLookup: newLookup }); + + // ์ปฌ๋Ÿผ ๋กœ๋“œ + const cols = await loadTableColumns(tableName); + setTargetLookupColumns(cols); + }; + + // ํƒ€๊ฒŸ ์กฐํšŒ ํ‚ค ํ•„๋“œ ๋ณ€๊ฒฝ + const handleTargetLookupKeyChange = (sourceField: string, targetField: string) => { + if (!targetLookup) return; + const sourceFieldInfo = availableFields.find((f) => f.name === sourceField); + const newLookup = { + ...targetLookup, + lookupKeys: [{ sourceField, targetField, sourceFieldLabel: sourceFieldInfo?.label || sourceField }], + }; + setTargetLookup(newLookup); + updateNode(nodeId, { targetLookup: newLookup }); + }; + + // ํƒ€๊ฒŸ ์กฐํšŒ ์ œ๊ฑฐ + const handleRemoveTargetLookup = () => { + setTargetLookup(undefined); + updateNode(nodeId, { targetLookup: undefined }); + // target ํƒ€์ž… ์กฐ๊ฑด๋“ค์„ field๋กœ ๋ณ€๊ฒฝ + const newConditions = conditions.map((c) => + (c as any).valueType === "target" ? { ...c, valueType: "field" } : c + ); + setConditions(newConditions); + updateNode(nodeId, { conditions: newConditions }); + }; + return (
@@ -597,6 +658,119 @@ export function ConditionProperties({ nodeId, data }: ConditionPropertiesProps)
+ {/* ํƒ€๊ฒŸ ์กฐํšŒ (DB ๊ธฐ์กด๊ฐ’ ๋น„๊ต) */} +
+
+

+ + ํƒ€๊ฒŸ ์กฐํšŒ (DB ๊ธฐ์กด๊ฐ’) +

+
+ + {!targetLookup ? ( +
+
+ DB์˜ ๊ธฐ์กด๊ฐ’๊ณผ ๋น„๊ตํ•˜๋ ค๋ฉด ํƒ€๊ฒŸ ํ…Œ์ด๋ธ”์„ ์„ค์ •ํ•˜์„ธ์š”. +
+ +
+ ) : ( +
+
+ ํƒ€๊ฒŸ ํ…Œ์ด๋ธ” + +
+ + {/* ํ…Œ์ด๋ธ” ์„ ํƒ */} + {allTables.length > 0 ? ( + + ) : ( +
+ ํ…Œ์ด๋ธ” ๋กœ๋”ฉ ์ค‘... +
+ )} + + {/* ํ‚ค ํ•„๋“œ ๋งคํ•‘ */} + {targetLookup.tableName && ( +
+ +
+ + = + {targetLookupColumns.length > 0 ? ( + + ) : ( +
+ ์ปฌ๋Ÿผ ๋กœ๋”ฉ ์ค‘... +
+ )} +
+
+ ๋น„๊ต ๊ฐ’ ํƒ€์ž…์—์„œ "ํƒ€๊ฒŸ ํ•„๋“œ (DB ๊ธฐ์กด๊ฐ’)"์„ ์„ ํƒํ•˜๋ฉด ์ด ํ…Œ์ด๋ธ”์˜ ๊ธฐ์กด๊ฐ’๊ณผ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค. +
+
+ )} +
+ )} +
+ {/* ์กฐ๊ฑด์‹ */}
@@ -738,15 +912,46 @@ export function ConditionProperties({ nodeId, data }: ConditionPropertiesProps) ๊ณ ์ •๊ฐ’ ํ•„๋“œ ์ฐธ์กฐ + {targetLookup?.tableName && ( + ํƒ€๊ฒŸ ํ•„๋“œ (DB ๊ธฐ์กด๊ฐ’) + )}
- {(condition as any).valueType === "field" ? ( + {(condition as any).valueType === "target" ? ( + // ํƒ€๊ฒŸ ํ•„๋“œ (DB ๊ธฐ์กด๊ฐ’): ํƒ€๊ฒŸ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ์—์„œ ์„ ํƒ + targetLookupColumns.length > 0 ? ( + + ) : ( +
+ ํƒ€๊ฒŸ ์กฐํšŒ๋ฅผ ๋จผ์ € ์„ค์ •ํ•˜์„ธ์š” +
+ ) + ) : (condition as any).valueType === "field" ? ( // ํ•„๋“œ ์ฐธ์กฐ: ๋“œ๋กญ๋‹ค์šด์œผ๋กœ ์„ ํƒ availableFields.length > 0 ? ( updateTab({ panelHeaderHeight: parseInt(e.target.value) || 48 })} - placeholder="48" - className="h-8 w-24 text-xs" - /> -
{/* ===== 2. ํ…Œ์ด๋ธ” ์„ ํƒ ===== */} -
- -
- - - - - - - - - ํ…Œ์ด๋ธ”์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. - - {availableRightTables.map((table) => ( - updateTab({ tableName: table.tableName, columns: [] })} - > - - {table.displayName || table.tableName} - - ))} - - - - -
+
+

ํ…Œ์ด๋ธ” ์„ค์ •

+ + + + + + + + ํ…Œ์ด๋ธ”์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. + + {availableRightTables.map((table) => ( + updateTab({ tableName: table.tableName, columns: [] })} + > + + {table.displayName || table.tableName} + {table.displayName && ({table.tableName})} + + ))} + + + +
- {/* ===== 3. ํ‘œ์‹œ ๋ชจ๋“œ ===== */} -
- -
+ {/* ===== 3. ํ‘œ์‹œ ๋ชจ๋“œ + ์š”์•ฝ ์„ค์ • ===== */} +
+

ํ‘œ์‹œ ์„ค์ •

+
{/* ์š”์•ฝ ์„ค์ • (๋ชฉ๋ก ๋ชจ๋“œ) */} - {tab.displayMode === "list" && ( -
-
- + {(tab.displayMode || "list") === "list" && ( +
+ +
+ updateTab({ summaryColumnCount: parseInt(e.target.value) || 3 })} - min={1} - max={10} - className="h-8 text-xs" + className="bg-white" /> +

์ ‘๊ธฐ ์ „์— ํ‘œ์‹œํ•  ์ปฌ๋Ÿผ ๊ฐœ์ˆ˜ (๊ธฐ๋ณธ: 3๊ฐœ)

-
+
+
+ +

์ปฌ๋Ÿผ๋ช… ํ‘œ์‹œ ์—ฌ๋ถ€

+
updateTab({ summaryShowLabel: !!checked })} + onCheckedChange={(checked) => updateTab({ summaryShowLabel: checked as boolean })} /> -
)}
{/* ===== 4. ์ปฌ๋Ÿผ ๋งคํ•‘ (์—ฐ๊ฒฐ ํ‚ค) ===== */} -
- -

- ์ขŒ์ธก ํŒจ๋„ ์„ ํƒ ์‹œ ๊ด€๋ จ ๋ฐ์ดํ„ฐ๋งŒ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค -

-
+
+

์ปฌ๋Ÿผ ๋งคํ•‘ (์—ฐ๊ฒฐ ํ‚ค)

+

์ขŒ์ธก ํŒจ๋„ ์„ ํƒ ์‹œ ๊ด€๋ จ ๋ฐ์ดํ„ฐ๋งŒ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค

+
@@ -515,10 +508,7 @@ const AdditionalTabConfigPanel: React.FC = ({ value={tab.relation?.keys?.[0]?.rightColumn || tab.relation?.foreignKey || "__none__"} onValueChange={(value) => { if (value === "__none__") { - // ์„ ํƒ ์•ˆ ํ•จ - ์กฐ์ธ ํ‚ค ์ œ๊ฑฐ - updateTab({ - relation: undefined, - }); + updateTab({ relation: undefined }); } else { updateTab({ relation: { @@ -530,17 +520,13 @@ const AdditionalTabConfigPanel: React.FC = ({ } }} > - + - - ์„ ํƒ ์•ˆ ํ•จ (์ „์ฒด ๋ฐ์ดํ„ฐ) - + ์„ ํƒ ์•ˆ ํ•จ (์ „์ฒด ๋ฐ์ดํ„ฐ) {tabColumns.map((col) => ( - - {col.columnLabel || col.columnName} - + {col.columnLabel || col.columnName} ))} @@ -549,215 +535,202 @@ const AdditionalTabConfigPanel: React.FC = ({
{/* ===== 5. ๊ธฐ๋Šฅ ๋ฒ„ํŠผ ===== */} -
- +
+

๊ธฐ๋Šฅ ๋ฒ„ํŠผ

- updateTab({ showSearch: !!checked })} - /> + updateTab({ showSearch: !!checked })} />
- updateTab({ showAdd: !!checked })} - /> + updateTab({ showAdd: !!checked })} />
- updateTab({ showEdit: !!checked })} - /> + updateTab({ showEdit: !!checked })} />
- updateTab({ showDelete: !!checked })} - /> + updateTab({ showDelete: !!checked })} />
- {/* ===== 6. ํ‘œ์‹œ ์ปฌ๋Ÿผ ์„ค์ • ===== */} -
-
- - -
-

- ํ‘œ์‹œํ•  ์ปฌ๋Ÿผ์„ ์„ ํƒํ•˜์„ธ์š”. ์„ ํƒํ•˜์ง€ ์•Š์œผ๋ฉด ๋ชจ๋“  ์ปฌ๋Ÿผ์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. -

+ {/* ===== 6. ํ‘œ์‹œํ•  ์ปฌ๋Ÿผ - DnD + Entity ์กฐ์ธ ํ†ตํ•ฉ ===== */} + {(() => { + const selectedColumns = tab.columns || []; + const filteredTabCols = tabColumns.filter((c) => !["company_code", "company_name"].includes(c.columnName)); + const unselectedCols = filteredTabCols.filter((c) => !selectedColumns.some((sc) => sc.name === c.columnName)); + const dbNumericTypes = ["numeric", "decimal", "integer", "bigint", "double precision", "real", "smallint", "int4", "int8", "float4", "float8"]; + const inputNumericTypes = ["number", "decimal", "currency", "integer"]; - {/* ํ…Œ์ด๋ธ” ๋ฏธ์„ ํƒ ์ƒํƒœ */} - {!tab.tableName && ( -
-

๋จผ์ € ํ…Œ์ด๋ธ”์„ ์„ ํƒํ•˜์„ธ์š”

-
- )} + const handleTabDragEnd = (event: DragEndEvent) => { + const { active, over } = event; + if (over && active.id !== over.id) { + const oldIndex = selectedColumns.findIndex((c) => c.name === active.id); + const newIndex = selectedColumns.findIndex((c) => c.name === over.id); + if (oldIndex !== -1 && newIndex !== -1) { + updateTab({ columns: arrayMove([...selectedColumns], oldIndex, newIndex) }); + } + } + }; - {/* ํ…Œ์ด๋ธ” ์„ ํƒ๋จ - ์ปฌ๋Ÿผ ๋ชฉ๋ก */} - {tab.tableName && ( -
- {/* ๋กœ๋”ฉ ์ƒํƒœ */} - {loadingTabColumns && ( -
-

์ปฌ๋Ÿผ์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘...

-
- )} + return ( +
+

ํ‘œ์‹œํ•  ์ปฌ๋Ÿผ ({selectedColumns.length}๊ฐœ ์„ ํƒ)

+
+ {!tab.tableName ? ( +

ํ…Œ์ด๋ธ”์„ ์„ ํƒํ•ด์ฃผ์„ธ์š”

+ ) : loadingTabColumns ? ( +

์ปฌ๋Ÿผ์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘...

+ ) : ( + <> + {selectedColumns.length > 0 && ( + + c.name)} strategy={verticalListSortingStrategy}> +
+ {selectedColumns.map((col, index) => { + const colInfo = tabColumns.find((c) => c.columnName === col.name); + const isNumeric = colInfo && ( + dbNumericTypes.includes(colInfo.dataType?.toLowerCase() || "") || + inputNumericTypes.includes(colInfo.input_type?.toLowerCase() || "") || + inputNumericTypes.includes(colInfo.webType?.toLowerCase() || "") + ); + return ( + { + const newColumns = [...selectedColumns]; + newColumns[index] = { ...newColumns[index], label: value }; + updateTab({ columns: newColumns }); + }} + onWidthChange={(value) => { + const newColumns = [...selectedColumns]; + newColumns[index] = { ...newColumns[index], width: value }; + updateTab({ columns: newColumns }); + }} + onFormatChange={(checked) => { + const newColumns = [...selectedColumns]; + newColumns[index] = { ...newColumns[index], format: { ...newColumns[index].format, type: "number", thousandSeparator: checked } }; + updateTab({ columns: newColumns }); + }} + onRemove={() => updateTab({ columns: selectedColumns.filter((_, i) => i !== index) })} + /> + ); + })} +
+
+
+ )} - {/* ์„ค์ •๋œ ์ปฌ๋Ÿผ์ด ์—†์„ ๋•Œ */} - {!loadingTabColumns && (tab.columns || []).length === 0 && ( -
-

์„ค์ •๋œ ์ปฌ๋Ÿผ์ด ์—†์Šต๋‹ˆ๋‹ค

-

์ปฌ๋Ÿผ์„ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š์œผ๋ฉด ๋ชจ๋“  ์ปฌ๋Ÿผ์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค

-
- )} + {selectedColumns.length > 0 && unselectedCols.length > 0 && ( +
+ ๋ฏธ์„ ํƒ ์ปฌ๋Ÿผ +
+ )} - {/* ์„ค์ •๋œ ์ปฌ๋Ÿผ ๋ชฉ๋ก */} - {!loadingTabColumns && (tab.columns || []).length > 0 && ( - (tab.columns || []).map((col, colIndex) => ( -
- {/* ์ƒ๋‹จ: ์ˆœ์„œ ๋ณ€๊ฒฝ + ์‚ญ์ œ ๋ฒ„ํŠผ */} -
-
- - - #{colIndex + 1} -
- + + {column.columnLabel || column.columnName} +
+ ))}
- {/* ์ปฌ๋Ÿผ ์„ ํƒ */} -
- - -
+ {/* Entity ์กฐ์ธ ์ปฌ๋Ÿผ - ์•„์ฝ”๋””์–ธ (์ ‘๊ธฐ/ํŽผ์น˜๊ธฐ) */} + {(() => { + const joinData = tab.tableName ? entityJoinColumnsMap?.[tab.tableName] : null; + if (!joinData || joinData.joinTables.length === 0) return null; - {/* ๋ผ๋ฒจ + ๋„ˆ๋น„ */} -
-
- - { - const newColumns = [...(tab.columns || [])]; - newColumns[colIndex] = { ...col, label: e.target.value }; - updateTab({ columns: newColumns }); - }} - placeholder="ํ‘œ์‹œ ๋ผ๋ฒจ" - className="h-8 text-xs" - /> -
-
- - { - const newColumns = [...(tab.columns || [])]; - newColumns[colIndex] = { ...col, width: parseInt(e.target.value) || 100 }; - updateTab({ columns: newColumns }); - }} - placeholder="100" - className="h-8 text-xs" - /> -
-
-
- )) - )} + return joinData.joinTables.map((joinTable, tableIndex) => { + const joinColumnsToShow = joinTable.availableColumns.filter((column) => { + const matchingJoinColumn = joinData.availableColumns.find( + (jc) => jc.tableName === joinTable.tableName && jc.columnName === column.columnName, + ); + if (!matchingJoinColumn) return false; + return !selectedColumns.some((c) => c.name === matchingJoinColumn.joinAlias); + }); + const addedCount = joinTable.availableColumns.length - joinColumnsToShow.length; + if (joinColumnsToShow.length === 0 && addedCount === 0) return null; + + return ( +
+ + + + {joinTable.tableName} + {addedCount > 0 && ( + {addedCount}๊ฐœ ์„ ํƒ + )} + {joinColumnsToShow.length}๊ฐœ ๋‚จ์Œ + +
+ {joinColumnsToShow.map((column, colIndex) => { + const matchingJoinColumn = joinData.availableColumns.find( + (jc) => jc.tableName === joinTable.tableName && jc.columnName === column.columnName, + ); + if (!matchingJoinColumn) return null; + + return ( +
{ + updateTab({ + columns: [...selectedColumns, { + name: matchingJoinColumn.joinAlias, + label: matchingJoinColumn.suggestedLabel || matchingJoinColumn.columnLabel, + width: 100, + isEntityJoin: true, + joinInfo: { + sourceTable: tab.tableName!, + sourceColumn: (joinTable as any).joinConfig?.sourceColumn || "", + referenceTable: matchingJoinColumn.tableName, + joinAlias: matchingJoinColumn.joinAlias, + }, + }], + }); + }} + > + + + {column.columnLabel || column.columnName} +
+ ); + })} + {joinColumnsToShow.length === 0 && ( +

๋ชจ๋“  ์ปฌ๋Ÿผ์ด ์ด๋ฏธ ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค

+ )} +
+
+ ); + }); + })()} + + )} +
- )} -
+ ); + })()} {/* ===== 7. ์ถ”๊ฐ€ ๋ชจ๋‹ฌ ์ปฌ๋Ÿผ ์„ค์ • (showAdd์ผ ๋•Œ) ===== */} {tab.showAdd && ( -
+
- +

์ถ”๊ฐ€ ๋ชจ๋‹ฌ ์ปฌ๋Ÿผ ์„ค์ •

)} - {/* ===== 7.5 Entity ์กฐ์ธ ์ปฌ๋Ÿผ ===== */} - {(() => { - const joinData = tab.tableName ? entityJoinColumnsMap?.[tab.tableName] : null; - if (!joinData || joinData.joinTables.length === 0) return null; - - return ( -
- -

์—ฐ๊ด€ ํ…Œ์ด๋ธ”์˜ ์ปฌ๋Ÿผ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค

- {joinData.joinTables.map((joinTable, tableIndex) => ( -
-
- - {joinTable.tableName} - {joinTable.currentDisplayColumn} -
-
- {joinTable.availableColumns.map((column, colIndex) => { - const matchingJoinColumn = joinData.availableColumns.find( - (jc) => jc.tableName === joinTable.tableName && jc.columnName === column.columnName, - ); - if (!matchingJoinColumn) return null; - const tabColumns2 = tab.columns || []; - const isAdded = tabColumns2.some((c) => c.name === matchingJoinColumn.joinAlias); - - return ( -
{ - if (isAdded) { - updateTab({ columns: tabColumns2.filter((c) => c.name !== matchingJoinColumn.joinAlias) }); - } else { - updateTab({ - columns: [...tabColumns2, { - name: matchingJoinColumn.joinAlias, - label: matchingJoinColumn.suggestedLabel || matchingJoinColumn.columnLabel, - width: 100, - isEntityJoin: true, - joinInfo: { - sourceTable: tab.tableName!, - sourceColumn: (joinTable as any).joinConfig?.sourceColumn || "", - referenceTable: matchingJoinColumn.tableName, - joinAlias: matchingJoinColumn.joinAlias, - }, - }], - }); - } - }} - > - - - {column.columnLabel} - {column.dataType} -
- ); - })} -
-
- ))} -
- ); - })()} + {/* Entity ์กฐ์ธ ์ปฌ๋Ÿผ์€ ํ‘œ์‹œ ์ปฌ๋Ÿผ ๋ชฉ๋ก์— ํ†ตํ•ฉ๋จ */} {/* ===== 8. ๋ฐ์ดํ„ฐ ํ•„ํ„ฐ๋ง ===== */} -
- +
+

๋ฐ์ดํ„ฐ ํ•„ํ„ฐ๋ง

= ({
{/* ===== 9. ์ค‘๋ณต ๋ฐ์ดํ„ฐ ์ œ๊ฑฐ ===== */} -
+
- +

์ค‘๋ณต ๋ฐ์ดํ„ฐ ์ œ๊ฑฐ

{ @@ -1019,8 +927,8 @@ const AdditionalTabConfigPanel: React.FC = ({ {/* ===== 10. ์ˆ˜์ • ๋ฒ„ํŠผ ์„ค์ • ===== */} {tab.showEdit && ( -
- +
+

์ˆ˜์ • ๋ฒ„ํŠผ ์„ค์ •

@@ -1125,8 +1033,8 @@ const AdditionalTabConfigPanel: React.FC = ({ {/* ===== 11. ์‚ญ์ œ ๋ฒ„ํŠผ ์„ค์ • ===== */} {tab.showDelete && ( -
- +
+

์‚ญ์ œ ๋ฒ„ํŠผ ์„ค์ •

@@ -1196,7 +1104,7 @@ const AdditionalTabConfigPanel: React.FC = ({ ํƒญ ์‚ญ์ œ
-
+ ); diff --git a/frontend/types/node-editor.ts b/frontend/types/node-editor.ts index ef0d78ff..c298b82f 100644 --- a/frontend/types/node-editor.ts +++ b/frontend/types/node-editor.ts @@ -118,7 +118,7 @@ export interface ConditionNodeData { field: string; operator: ConditionOperator; value: any; - valueType?: "static" | "field"; // ๋น„๊ต ๊ฐ’ ํƒ€์ž… + valueType?: "static" | "field" | "target"; // ๋น„๊ต ๊ฐ’ ํƒ€์ž… (target: DB ๊ธฐ์กด๊ฐ’ ๋น„๊ต) // EXISTS_IN / NOT_EXISTS_IN ์ „์šฉ ํ•„๋“œ lookupTable?: string; // ์กฐํšŒํ•  ํ…Œ์ด๋ธ”๋ช… lookupTableLabel?: string; // ์กฐํšŒํ•  ํ…Œ์ด๋ธ” ๋ผ๋ฒจ @@ -127,6 +127,16 @@ export interface ConditionNodeData { }>; logic: "AND" | "OR"; displayName?: string; + // ํƒ€๊ฒŸ ํ…Œ์ด๋ธ” ์กฐํšŒ (DB ๊ธฐ์กด๊ฐ’๊ณผ ๋น„๊ตํ•  ๋•Œ ์‚ฌ์šฉ) + targetLookup?: { + tableName: string; + tableLabel?: string; + lookupKeys: Array<{ + sourceField: string; // ์†Œ์Šค(ํผ) ๋ฐ์ดํ„ฐ์˜ ํ‚ค ํ•„๋“œ + targetField: string; // ํƒ€๊ฒŸ(DB) ํ…Œ์ด๋ธ”์˜ ํ‚ค ํ•„๋“œ + sourceFieldLabel?: string; + }>; + }; } // ํ•„๋“œ ๋งคํ•‘ ๋…ธ๋“œ