From 2b747a103017fd16032939d1f34b43985e8d0451 Mon Sep 17 00:00:00 2001 From: kjs Date: Thu, 11 Dec 2025 11:37:40 +0900 Subject: [PATCH] =?UTF-8?q?=EC=86=8C=EC=8A=A4=ED=95=84=EB=93=9C=20?= =?UTF-8?q?=EB=AA=BB=EC=B0=BE=EB=8A=94=20=EB=B2=84=EA=B7=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/services/nodeFlowExecutionService.ts | 47 +- .../SplitPanelLayoutComponent.tsx | 412 ++- .../SplitPanelLayoutConfigPanel.tsx | 2570 ++++++++++------- .../components/split-panel-layout/types.ts | 21 +- 4 files changed, 1806 insertions(+), 1244 deletions(-) diff --git a/backend-node/src/services/nodeFlowExecutionService.ts b/backend-node/src/services/nodeFlowExecutionService.ts index a7333af4..461cd8d2 100644 --- a/backend-node/src/services/nodeFlowExecutionService.ts +++ b/backend-node/src/services/nodeFlowExecutionService.ts @@ -1646,7 +1646,18 @@ export class NodeFlowExecutionService { // WHERE ์กฐ๊ฑด ์ƒ์„ฑ const whereClauses: string[] = []; whereConditions?.forEach((condition: any) => { - const condValue = data[condition.field]; + // ๐Ÿ”ฅ ์ˆ˜์ •: sourceField๊ฐ€ ์žˆ์œผ๋ฉด ์†Œ์Šค ๋ฐ์ดํ„ฐ์—์„œ ๊ฐ’์„ ๊ฐ€์ ธ์˜ด + let condValue: any; + if (condition.sourceField) { + condValue = data[condition.sourceField]; + } else if ( + condition.staticValue !== undefined && + condition.staticValue !== "" + ) { + condValue = condition.staticValue; + } else { + condValue = data[condition.field]; + } if (condition.operator === "IS NULL") { whereClauses.push(`${condition.field} IS NULL`); @@ -1987,7 +1998,18 @@ export class NodeFlowExecutionService { // WHERE ์กฐ๊ฑด ์ƒ์„ฑ whereConditions?.forEach((condition: any) => { - const condValue = data[condition.field]; + // ๐Ÿ”ฅ ์ˆ˜์ •: sourceField๊ฐ€ ์žˆ์œผ๋ฉด ์†Œ์Šค ๋ฐ์ดํ„ฐ์—์„œ ๊ฐ’์„ ๊ฐ€์ ธ์˜ด + let condValue: any; + if (condition.sourceField) { + condValue = data[condition.sourceField]; + } else if ( + condition.staticValue !== undefined && + condition.staticValue !== "" + ) { + condValue = condition.staticValue; + } else { + condValue = data[condition.field]; + } if (condition.operator === "IS NULL") { whereClauses.push(`${condition.field} IS NULL`); @@ -2889,7 +2911,26 @@ export class NodeFlowExecutionService { const values: any[] = []; const clauses = conditions.map((condition, index) => { - const value = data ? data[condition.field] : condition.value; + // ๐Ÿ”ฅ ์ˆ˜์ •: sourceField๊ฐ€ ์žˆ์œผ๋ฉด ์†Œ์Šค ๋ฐ์ดํ„ฐ์—์„œ ๊ฐ’์„ ๊ฐ€์ ธ์˜ค๊ณ , + // ์—†์œผ๋ฉด staticValue ๋˜๋Š” ๊ธฐ์กด field ์‚ฌ์šฉ + let value: any; + if (data) { + if (condition.sourceField) { + // sourceField๊ฐ€ ์žˆ์œผ๋ฉด ์†Œ์Šค ๋ฐ์ดํ„ฐ์—์„œ ํ•ด๋‹น ํ•„๋“œ์˜ ๊ฐ’์„ ๊ฐ€์ ธ์˜ด + value = data[condition.sourceField]; + } else if ( + condition.staticValue !== undefined && + condition.staticValue !== "" + ) { + // staticValue๊ฐ€ ์žˆ์œผ๋ฉด ์‚ฌ์šฉ + value = condition.staticValue; + } else { + // ๋‘˜ ๋‹ค ์—†์œผ๋ฉด ๊ธฐ์กด ๋ฐฉ์‹ (field๋กœ ๊ฐ’ ์กฐํšŒ) + value = data[condition.field]; + } + } else { + value = condition.value; + } values.push(value); // ์—ฐ์‚ฐ์ž๋ฅผ SQL ๋ฌธ๋ฒ•์œผ๋กœ ๋ณ€ํ™˜ diff --git a/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx b/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx index ac44eded..ba911c3c 100644 --- a/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx +++ b/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx @@ -60,15 +60,15 @@ export const SplitPanelLayoutComponent: React.FC const resizable = componentConfig.resizable ?? true; const minLeftWidth = componentConfig.minLeftWidth || 200; const minRightWidth = componentConfig.minRightWidth || 300; - + // ํ•„๋“œ ํ‘œ์‹œ ์œ ํ‹ธ๋ฆฌํ‹ฐ (ํ•˜๋“œ์ฝ”๋”ฉ ์ œ๊ฑฐ, ๋™์ ์œผ๋กœ ์ž‘๋™) const shouldShowField = (fieldName: string): boolean => { const lower = fieldName.toLowerCase(); - + // ๊ธฐ๋ณธ ์ œ์™ธ: id, ๋น„๋ฐ€๋ฒˆํ˜ธ, ํ† ํฐ, ํšŒ์‚ฌ์ฝ”๋“œ if (lower === "id" || lower === "company_code" || lower === "company_name") return false; if (lower.includes("password") || lower.includes("token")) return false; - + // ๋‚˜๋จธ์ง€๋Š” ๋ชจ๋‘ ํ‘œ์‹œ! return true; }; @@ -284,26 +284,102 @@ export const SplitPanelLayoutComponent: React.FC })); }, [leftData, leftGrouping]); - // ์…€ ๊ฐ’ ํฌ๋งทํŒ… ํ•จ์ˆ˜ (์นดํ…Œ๊ณ ๋ฆฌ ํƒ€์ž… ์ฒ˜๋ฆฌ) + // ๋‚ ์งœ ํฌ๋งทํŒ… ํ—ฌํผ ํ•จ์ˆ˜ + const formatDateValue = useCallback((value: any, dateFormat: string): string => { + if (!value) return "-"; + const date = new Date(value); + if (isNaN(date.getTime())) return String(value); + + if (dateFormat === "relative") { + // ์ƒ๋Œ€ ์‹œ๊ฐ„ (์˜ˆ: 3์ผ ์ „, 2์‹œ๊ฐ„ ์ „) + const now = new Date(); + const diffMs = now.getTime() - date.getTime(); + const diffSec = Math.floor(diffMs / 1000); + const diffMin = Math.floor(diffSec / 60); + const diffHour = Math.floor(diffMin / 60); + const diffDay = Math.floor(diffHour / 24); + const diffMonth = Math.floor(diffDay / 30); + const diffYear = Math.floor(diffMonth / 12); + + if (diffYear > 0) return `${diffYear}๋…„ ์ „`; + if (diffMonth > 0) return `${diffMonth}๊ฐœ์›” ์ „`; + if (diffDay > 0) return `${diffDay}์ผ ์ „`; + if (diffHour > 0) return `${diffHour}์‹œ๊ฐ„ ์ „`; + if (diffMin > 0) return `${diffMin}๋ถ„ ์ „`; + return "๋ฐฉ๊ธˆ ์ „"; + } + + // ํฌ๋งท ๋ฌธ์ž์—ด ์น˜ํ™˜ + return dateFormat + .replace("YYYY", String(date.getFullYear())) + .replace("MM", String(date.getMonth() + 1).padStart(2, "0")) + .replace("DD", String(date.getDate()).padStart(2, "0")) + .replace("HH", String(date.getHours()).padStart(2, "0")) + .replace("mm", String(date.getMinutes()).padStart(2, "0")) + .replace("ss", String(date.getSeconds()).padStart(2, "0")); + }, []); + + // ์ˆซ์ž ํฌ๋งทํŒ… ํ—ฌํผ ํ•จ์ˆ˜ + const formatNumberValue = useCallback((value: any, format: any): string => { + if (value === null || value === undefined || value === "") return "-"; + const num = typeof value === "number" ? value : parseFloat(String(value)); + if (isNaN(num)) return String(value); + + const options: Intl.NumberFormatOptions = { + minimumFractionDigits: format?.decimalPlaces ?? 0, + maximumFractionDigits: format?.decimalPlaces ?? 10, + useGrouping: format?.thousandSeparator ?? false, + }; + + let result = num.toLocaleString("ko-KR", options); + if (format?.prefix) result = format.prefix + result; + if (format?.suffix) result = result + format.suffix; + return result; + }, []); + + // ์…€ ๊ฐ’ ํฌ๋งทํŒ… ํ•จ์ˆ˜ (์นดํ…Œ๊ณ ๋ฆฌ ํƒ€์ž… ์ฒ˜๋ฆฌ + ๋‚ ์งœ/์ˆซ์ž ํฌ๋งท) const formatCellValue = useCallback( ( columnName: string, value: any, categoryMappings: Record>, + format?: { + type?: "number" | "currency" | "date" | "text"; + thousandSeparator?: boolean; + decimalPlaces?: number; + prefix?: string; + suffix?: string; + dateFormat?: string; + }, ) => { if (value === null || value === undefined) return "-"; + // ๐Ÿ†• ๋‚ ์งœ ํฌ๋งท ์ ์šฉ + if (format?.type === "date" || format?.dateFormat) { + return formatDateValue(value, format?.dateFormat || "YYYY-MM-DD"); + } + + // ๐Ÿ†• ์ˆซ์ž ํฌ๋งท ์ ์šฉ + if ( + format?.type === "number" || + format?.type === "currency" || + format?.thousandSeparator || + format?.decimalPlaces !== undefined + ) { + return formatNumberValue(value, format); + } + // ๐Ÿ†• ์นดํ…Œ๊ณ ๋ฆฌ ๋งคํ•‘ ์ฐพ๊ธฐ (์—ฌ๋Ÿฌ ํ‚ค ํ˜•ํƒœ ์‹œ๋„) // 1. ์ „์ฒด ์ปฌ๋Ÿผ๋ช… (์˜ˆ: "item_info.material") // 2. ์ปฌ๋Ÿผ๋ช…๋งŒ (์˜ˆ: "material") let mapping = categoryMappings[columnName]; - + if (!mapping && columnName.includes(".")) { // ์กฐ์ธ๋œ ์ปฌ๋Ÿผ์˜ ๊ฒฝ์šฐ ์ปฌ๋Ÿผ๋ช…๋งŒ์œผ๋กœ ๋‹ค์‹œ ์‹œ๋„ const simpleColumnName = columnName.split(".").pop() || columnName; mapping = categoryMappings[simpleColumnName]; } - + if (mapping && mapping[String(value)]) { const categoryData = mapping[String(value)]; const displayLabel = categoryData.label || String(value); @@ -323,10 +399,28 @@ export const SplitPanelLayoutComponent: React.FC ); } + // ๐Ÿ†• ์ž๋™ ๋‚ ์งœ ๊ฐ์ง€ (ISO 8601 ํ˜•์‹ ๋˜๋Š” Date ๊ฐ์ฒด) + if (typeof value === "string" && value.match(/^\d{4}-\d{2}-\d{2}(T|\s)/)) { + return formatDateValue(value, "YYYY-MM-DD"); + } + + // ๐Ÿ†• ์ž๋™ ์ˆซ์ž ๊ฐ์ง€ (์ˆซ์ž ๋˜๋Š” ์ˆซ์ž ๋ฌธ์ž์—ด) - ์†Œ์ˆ˜์  ์žˆ์œผ๋ฉด ์ •์ˆ˜๋กœ ๋ณ€ํ™˜ + if (typeof value === "number") { + // ์ˆซ์ž์ธ ๊ฒฝ์šฐ ์ •์ˆ˜๋กœ ํ‘œ์‹œ (์†Œ์ˆ˜์  ์ œ๊ฑฐ) + return Number.isInteger(value) ? String(value) : String(Math.round(value * 100) / 100); + } + if (typeof value === "string" && /^-?\d+\.?\d*$/.test(value.trim())) { + // ์ˆซ์ž ๋ฌธ์ž์—ด์ธ ๊ฒฝ์šฐ (์˜ˆ: "5.00" โ†’ "5") + const num = parseFloat(value); + if (!isNaN(num)) { + return Number.isInteger(num) ? String(num) : String(Math.round(num * 100) / 100); + } + } + // ์ผ๋ฐ˜ ๊ฐ’ return String(value); }, - [], + [formatDateValue, formatNumberValue], ); // ์ขŒ์ธก ๋ฐ์ดํ„ฐ ๋กœ๋“œ @@ -392,7 +486,7 @@ export const SplitPanelLayoutComponent: React.FC if (relationshipType === "detail") { // ์ƒ์„ธ ๋ชจ๋“œ: ๋™์ผ ํ…Œ์ด๋ธ”์˜ ์ƒ์„ธ ์ •๋ณด (๐Ÿ†• ์—”ํ‹ฐํ‹ฐ ์กฐ์ธ ํ™œ์„ฑํ™”) const primaryKey = leftItem.id || leftItem.ID || Object.values(leftItem)[0]; - + // ๐Ÿ†• ์—”ํ‹ฐํ‹ฐ ์กฐ์ธ API ์‚ฌ์šฉ const { entityJoinApi } = await import("@/lib/api/entityJoin"); const result = await entityJoinApi.getTableDataWithJoins(rightTableName, { @@ -400,29 +494,81 @@ export const SplitPanelLayoutComponent: React.FC enableEntityJoin: true, // ์—”ํ‹ฐํ‹ฐ ์กฐ์ธ ํ™œ์„ฑํ™” size: 1, }); - + const detail = result.items && result.items.length > 0 ? result.items[0] : null; setRightData(detail); } else if (relationshipType === "join") { // ์กฐ์ธ ๋ชจ๋“œ: ๋‹ค๋ฅธ ํ…Œ์ด๋ธ”์˜ ๊ด€๋ จ ๋ฐ์ดํ„ฐ (์—ฌ๋Ÿฌ ๊ฐœ) - const leftColumn = componentConfig.rightPanel?.relation?.leftColumn; - const rightColumn = componentConfig.rightPanel?.relation?.foreignKey; + const keys = componentConfig.rightPanel?.relation?.keys; const leftTable = componentConfig.leftPanel?.tableName; - if (leftColumn && rightColumn && leftTable) { - const leftValue = leftItem[leftColumn]; - const joinedData = await dataApi.getJoinedData( - leftTable, - rightTableName, - leftColumn, - rightColumn, - leftValue, - componentConfig.rightPanel?.dataFilter, // ๐Ÿ†• ๋ฐ์ดํ„ฐ ํ•„ํ„ฐ ์ „๋‹ฌ - true, // ๐Ÿ†• Entity ์กฐ์ธ ํ™œ์„ฑํ™” - componentConfig.rightPanel?.columns, // ๐Ÿ†• ํ‘œ์‹œ ์ปฌ๋Ÿผ ์ „๋‹ฌ (item_info.item_name ๋“ฑ) - componentConfig.rightPanel?.deduplication, // ๐Ÿ†• ์ค‘๋ณต ์ œ๊ฑฐ ์„ค์ • ์ „๋‹ฌ - ); - setRightData(joinedData || []); // ๋ชจ๋“  ๊ด€๋ จ ๋ ˆ์ฝ”๋“œ (๋ฐฐ์—ด) + // ๐Ÿ†• ๋ณตํ•ฉํ‚ค ์ง€์› + if (keys && keys.length > 0 && leftTable) { + // ๋ณตํ•ฉํ‚ค: ์—ฌ๋Ÿฌ ์กฐ๊ฑด์œผ๋กœ ํ•„ํ„ฐ๋ง + const { entityJoinApi } = await import("@/lib/api/entityJoin"); + + // ๋ณตํ•ฉํ‚ค ์กฐ๊ฑด ์ƒ์„ฑ + const searchConditions: Record = {}; + keys.forEach((key) => { + if (key.leftColumn && key.rightColumn && leftItem[key.leftColumn] !== undefined) { + searchConditions[key.rightColumn] = leftItem[key.leftColumn]; + } + }); + + console.log("๐Ÿ”— [๋ถ„ํ• ํŒจ๋„] ๋ณตํ•ฉํ‚ค ์กฐ๊ฑด:", searchConditions); + + // ์—”ํ‹ฐํ‹ฐ ์กฐ์ธ API๋กœ ๋ฐ์ดํ„ฐ ์กฐํšŒ + const result = await entityJoinApi.getTableDataWithJoins(rightTableName, { + search: searchConditions, + enableEntityJoin: true, + size: 1000, + }); + + console.log("๐Ÿ”— [๋ถ„ํ• ํŒจ๋„] ๋ณตํ•ฉํ‚ค ์กฐํšŒ ๊ฒฐ๊ณผ:", result); + + // ์ถ”๊ฐ€ dataFilter ์ ์šฉ + let filteredData = result.data || []; + const dataFilter = componentConfig.rightPanel?.dataFilter; + if (dataFilter?.enabled && dataFilter.conditions?.length > 0) { + filteredData = filteredData.filter((item: any) => { + return dataFilter.conditions.every((cond: any) => { + const value = item[cond.column]; + const condValue = cond.value; + switch (cond.operator) { + case "equals": + return value === condValue; + case "notEquals": + return value !== condValue; + case "contains": + return String(value).includes(String(condValue)); + default: + return true; + } + }); + }); + } + + setRightData(filteredData); + } else { + // ๋‹จ์ผํ‚ค (ํ•˜์œ„ ํ˜ธํ™˜์„ฑ) + const leftColumn = componentConfig.rightPanel?.relation?.leftColumn; + const rightColumn = componentConfig.rightPanel?.relation?.foreignKey; + + if (leftColumn && rightColumn && leftTable) { + const leftValue = leftItem[leftColumn]; + const joinedData = await dataApi.getJoinedData( + leftTable, + rightTableName, + leftColumn, + rightColumn, + leftValue, + componentConfig.rightPanel?.dataFilter, // ๐Ÿ†• ๋ฐ์ดํ„ฐ ํ•„ํ„ฐ ์ „๋‹ฌ + true, // ๐Ÿ†• Entity ์กฐ์ธ ํ™œ์„ฑํ™” + componentConfig.rightPanel?.columns, // ๐Ÿ†• ํ‘œ์‹œ ์ปฌ๋Ÿผ ์ „๋‹ฌ (item_info.item_name ๋“ฑ) + componentConfig.rightPanel?.deduplication, // ๐Ÿ†• ์ค‘๋ณต ์ œ๊ฑฐ ์„ค์ • ์ „๋‹ฌ + ); + setRightData(joinedData || []); // ๋ชจ๋“  ๊ด€๋ จ ๋ ˆ์ฝ”๋“œ (๋ฐฐ์—ด) + } } } } catch (error) { @@ -711,7 +857,7 @@ export const SplitPanelLayoutComponent: React.FC // ๐Ÿ†• ์šฐ์ธก ํŒจ๋„ ์ปฌ๋Ÿผ ์„ค์ •์—์„œ ์กฐ์ธ๋œ ํ…Œ์ด๋ธ” ์ถ”์ถœ const rightColumns = componentConfig.rightPanel?.columns || []; const tablesToLoad = new Set([rightTableName]); - + // ์ปฌ๋Ÿผ๋ช…์—์„œ ํ…Œ์ด๋ธ”๋ช… ์ถ”์ถœ (์˜ˆ: "item_info.material" -> "item_info") rightColumns.forEach((col: any) => { const colName = col.name || col.columnName; @@ -744,15 +890,15 @@ export const SplitPanelLayoutComponent: React.FC color: item.color, }; }); - + // ์กฐ์ธ๋œ ํ…Œ์ด๋ธ”์˜ ๊ฒฝ์šฐ "ํ…Œ์ด๋ธ”๋ช….์ปฌ๋Ÿผ๋ช…" ํ˜•ํƒœ๋กœ ์ €์žฅ const mappingKey = tableName === rightTableName ? columnName : `${tableName}.${columnName}`; mappings[mappingKey] = valueMap; - + // ๐Ÿ†• ์ปฌ๋Ÿผ๋ช…๋งŒ์œผ๋กœ๋„ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก ์ถ”๊ฐ€ ์ €์žฅ (๋ชจ๋“  ํ…Œ์ด๋ธ”) // ๊ธฐ์กด ๋งคํ•‘์ด ์žˆ์œผ๋ฉด ๋ณ‘ํ•ฉ, ์—†์œผ๋ฉด ์ƒˆ๋กœ ์ƒ์„ฑ mappings[columnName] = { ...(mappings[columnName] || {}), ...valueMap }; - + console.log(`โœ… ์šฐ์ธก ์นดํ…Œ๊ณ ๋ฆฌ ๋งคํ•‘ ๋กœ๋“œ [${mappingKey}]:`, valueMap); console.log(`โœ… ์šฐ์ธก ์นดํ…Œ๊ณ ๋ฆฌ ๋งคํ•‘ (์ปฌ๋Ÿผ๋ช…๋งŒ) [${columnName}]:`, mappings[columnName]); } @@ -818,15 +964,15 @@ export const SplitPanelLayoutComponent: React.FC // ๐Ÿ†• ์šฐ์ธก ํŒจ๋„ ์ˆ˜์ • ๋ฒ„ํŠผ ์„ค์ • ํ™•์ธ if (panel === "right" && componentConfig.rightPanel?.editButton?.mode === "modal") { const modalScreenId = componentConfig.rightPanel?.editButton?.modalScreenId; - + if (modalScreenId) { // ์ปค์Šคํ…€ ๋ชจ๋‹ฌ ํ™”๋ฉด ์—ด๊ธฐ const rightTableName = componentConfig.rightPanel?.tableName || ""; - + // Primary Key ์ฐพ๊ธฐ (์šฐ์„ ์ˆœ์œ„: id > ID > ์ฒซ ๋ฒˆ์งธ ํ•„๋“œ) let primaryKeyName = "id"; let primaryKeyValue: any; - + if (item.id !== undefined && item.id !== null) { primaryKeyName = "id"; primaryKeyValue = item.id; @@ -839,29 +985,29 @@ export const SplitPanelLayoutComponent: React.FC primaryKeyName = firstKey; primaryKeyValue = item[firstKey]; } - - console.log(`โœ… ์ˆ˜์ • ๋ชจ๋‹ฌ ์—ด๊ธฐ:`, { + + console.log("โœ… ์ˆ˜์ • ๋ชจ๋‹ฌ ์—ด๊ธฐ:", { tableName: rightTableName, primaryKeyName, primaryKeyValue, screenId: modalScreenId, fullItem: item, }); - + // modalDataStore์—๋„ ์ €์žฅ (ํ˜ธํ™˜์„ฑ ์œ ์ง€) import("@/stores/modalDataStore").then(({ useModalDataStore }) => { useModalDataStore.getState().setData(rightTableName, [item]); }); - + // ๐Ÿ†• groupByColumns ์ถ”์ถœ const groupByColumns = componentConfig.rightPanel?.editButton?.groupByColumns || []; - + console.log("๐Ÿ”ง [SplitPanel] ์ˆ˜์ • ๋ฒ„ํŠผ ํด๋ฆญ - groupByColumns ํ™•์ธ:", { groupByColumns, editButtonConfig: componentConfig.rightPanel?.editButton, hasGroupByColumns: groupByColumns.length > 0, }); - + // ScreenModal ์—ด๊ธฐ ์ด๋ฒคํŠธ ๋ฐœ์ƒ (URL ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ID + groupByColumns ์ „๋‹ฌ) window.dispatchEvent( new CustomEvent("openScreenModal", { @@ -878,18 +1024,18 @@ export const SplitPanelLayoutComponent: React.FC }, }), ); - + console.log("โœ… [SplitPanel] openScreenModal ์ด๋ฒคํŠธ ๋ฐœ์ƒ:", { screenId: modalScreenId, editId: primaryKeyValue, tableName: rightTableName, groupByColumns: groupByColumns.length > 0 ? JSON.stringify(groupByColumns) : "์—†์Œ", }); - + return; } } - + // ๊ธฐ์กด ์ž๋™ ํŽธ์ง‘ ๋ชจ๋“œ (์ธ๋ผ์ธ ํŽธ์ง‘ ๋ชจ๋‹ฌ) setEditModalPanel(panel); setEditModalItem(item); @@ -1026,7 +1172,7 @@ export const SplitPanelLayoutComponent: React.FC try { console.log("๐Ÿ—‘๏ธ ๋ฐ์ดํ„ฐ ์‚ญ์ œ:", { tableName, primaryKey }); - + // ๐Ÿ” ์ค‘๋ณต ์ œ๊ฑฐ ์„ค์ • ๋””๋ฒ„๊น… console.log("๐Ÿ” ์ค‘๋ณต ์ œ๊ฑฐ ๋””๋ฒ„๊น…:", { panel: deleteModalPanel, @@ -1041,25 +1187,25 @@ export const SplitPanelLayoutComponent: React.FC if (deleteModalPanel === "right" && componentConfig.rightPanel?.dataFilter?.deduplication?.enabled) { const deduplication = componentConfig.rightPanel.dataFilter.deduplication; const groupByColumn = deduplication.groupByColumn; - + if (groupByColumn && deleteModalItem[groupByColumn]) { const groupValue = deleteModalItem[groupByColumn]; console.log(`๐Ÿ”— ์ค‘๋ณต ์ œ๊ฑฐ ํ™œ์„ฑํ™”: ${groupByColumn} = ${groupValue} ๊ธฐ์ค€์œผ๋กœ ๋ชจ๋“  ๋ ˆ์ฝ”๋“œ ์‚ญ์ œ`); - + // groupByColumn ๊ฐ’์œผ๋กœ ํ•„ํ„ฐ๋งํ•˜์—ฌ ์‚ญ์ œ const filterConditions: Record = { [groupByColumn]: groupValue, }; - + // ์ขŒ์ธก ํŒจ๋„์˜ ์„ ํƒ๋œ ํ•ญ๋ชฉ ์ •๋ณด๋„ ํฌํ•จ (customer_id ๋“ฑ) if (selectedLeftItem && componentConfig.rightPanel?.mode === "join") { const leftColumn = componentConfig.rightPanel.join.leftColumn; const rightColumn = componentConfig.rightPanel.join.rightColumn; filterConditions[rightColumn] = selectedLeftItem[leftColumn]; } - + console.log("๐Ÿ—‘๏ธ ๊ทธ๋ฃน ์‚ญ์ œ ์กฐ๊ฑด:", filterConditions); - + // ๊ทธ๋ฃน ์‚ญ์ œ API ํ˜ธ์ถœ result = await dataApi.deleteGroupRecords(tableName, filterConditions); } else { @@ -1527,6 +1673,7 @@ export const SplitPanelLayoutComponent: React.FC leftColumnLabels[colName] || (typeof col === "object" ? col.label : null) || colName, width: typeof col === "object" ? col.width : 150, align: (typeof col === "object" ? col.align : "left") as "left" | "center" | "right", + format: typeof col === "object" ? col.format : undefined, // ๐Ÿ†• ํฌ๋งท ์„ค์ • ํฌํ•จ }; }) : Object.keys(filteredData[0] || {}) @@ -1537,6 +1684,7 @@ export const SplitPanelLayoutComponent: React.FC label: leftColumnLabels[key] || key, width: 150, align: "left" as const, + format: undefined, // ๐Ÿ†• ๊ธฐ๋ณธ๊ฐ’ })); // ๐Ÿ”ง ๊ทธ๋ฃนํ™”๋œ ๋ฐ์ดํ„ฐ ๋ Œ๋”๋ง @@ -1587,7 +1735,12 @@ export const SplitPanelLayoutComponent: React.FC className="px-3 py-2 text-sm whitespace-nowrap text-gray-900" style={{ textAlign: col.align || "left" }} > - {formatCellValue(col.name, item[col.name], leftCategoryMappings)} + {formatCellValue( + col.name, + item[col.name], + leftCategoryMappings, + col.format, + )} ))} @@ -1643,7 +1796,7 @@ export const SplitPanelLayoutComponent: React.FC className="px-3 py-2 text-sm whitespace-nowrap text-gray-900" style={{ textAlign: col.align || "left" }} > - {formatCellValue(col.name, item[col.name], leftCategoryMappings)} + {formatCellValue(col.name, item[col.name], leftCategoryMappings, col.format)} ))} @@ -1747,7 +1900,7 @@ export const SplitPanelLayoutComponent: React.FC } else { // ์„ค์ •๋œ ์ปฌ๋Ÿผ์ด ์—†์œผ๋ฉด ์ž๋™์œผ๋กœ ์ฒซ 2๊ฐœ ํ•„๋“œ ํ‘œ์‹œ const keys = Object.keys(item).filter( - (k) => k !== "id" && k !== "ID" && k !== "children" && k !== "level" && shouldShowField(k) + (k) => k !== "id" && k !== "ID" && k !== "children" && k !== "level" && shouldShowField(k), ); displayFields = keys.slice(0, 2).map((key) => ({ label: leftColumnLabels[key] || key, @@ -1960,6 +2113,7 @@ export const SplitPanelLayoutComponent: React.FC ? displayColumns.map((col) => ({ ...col, label: rightColumnLabels[col.name] || col.label || col.name, + format: col.format, // ๐Ÿ†• ํฌ๋งท ์„ค์ • ์œ ์ง€ })) : Object.keys(filteredData[0] || {}) .filter((key) => shouldShowField(key)) @@ -1969,6 +2123,7 @@ export const SplitPanelLayoutComponent: React.FC label: rightColumnLabels[key] || key, width: 150, align: "left" as const, + format: undefined, // ๐Ÿ†• ๊ธฐ๋ณธ๊ฐ’ })); return ( @@ -2014,7 +2169,7 @@ export const SplitPanelLayoutComponent: React.FC className="px-3 py-2 text-sm whitespace-nowrap text-gray-900" style={{ textAlign: col.align || "left" }} > - {formatCellValue(col.name, item[col.name], rightCategoryMappings)} + {formatCellValue(col.name, item[col.name], rightCategoryMappings, col.format)} ))} {!isDesignMode && ( @@ -2022,7 +2177,9 @@ export const SplitPanelLayoutComponent: React.FC
{(componentConfig.rightPanel?.editButton?.enabled ?? true) && ( )} - + {(componentConfig.rightPanel?.deleteButton?.enabled ?? true) && ( + + )}
)} @@ -2083,26 +2242,28 @@ export const SplitPanelLayoutComponent: React.FC .map((col) => { // ๐Ÿ†• ์—”ํ‹ฐํ‹ฐ ์กฐ์ธ ์ปฌ๋Ÿผ ์ฒ˜๋ฆฌ (์˜ˆ: item_info.item_number โ†’ item_number ๋˜๋Š” item_id_name) let value = item[col.name]; - if (value === undefined && col.name.includes('.')) { - const columnName = col.name.split('.').pop(); + if (value === undefined && col.name.includes(".")) { + const columnName = col.name.split(".").pop(); // 1์ฐจ: ์ปฌ๋Ÿผ๋ช… ๊ทธ๋Œ€๋กœ (์˜ˆ: item_number) - value = item[columnName || '']; + value = item[columnName || ""]; // 2์ฐจ: item_info.item_number โ†’ item_id_name ๋˜๋Š” item_id_item_number ํ˜•์‹ ํ™•์ธ if (value === undefined) { - const parts = col.name.split('.'); + const parts = col.name.split("."); if (parts.length === 2) { const refTable = parts[0]; // item_info const refColumn = parts[1]; // item_number ๋˜๋Š” item_name // FK ์ปฌ๋Ÿผ๋ช… ์ถ”๋ก : item_info โ†’ item_id - const fkColumn = refTable.replace('_info', '').replace('_mng', '') + '_id'; - + const fkColumn = refTable.replace("_info", "").replace("_mng", "") + "_id"; + // ๋ฐฑ์—”๋“œ์—์„œ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ณ„์นญ ํŒจํ„ด: // 1) item_id_name (๊ธฐ๋ณธ referenceColumn) // 2) item_id_item_name (์ถ”๊ฐ€ ์ปฌ๋Ÿผ) - if (refColumn === refTable.replace('_info', '').replace('_mng', '') + '_number' || - refColumn === refTable.replace('_info', '').replace('_mng', '') + '_code') { + if ( + refColumn === refTable.replace("_info", "").replace("_mng", "") + "_number" || + refColumn === refTable.replace("_info", "").replace("_mng", "") + "_code" + ) { // ๊ธฐ๋ณธ ์ฐธ์กฐ ์ปฌ๋Ÿผ (item_number, customer_code ๋“ฑ) - const aliasKey = fkColumn + '_name'; + const aliasKey = fkColumn + "_name"; value = item[aliasKey]; } else { // ์ถ”๊ฐ€ ์ปฌ๋Ÿผ (item_name, customer_name ๋“ฑ) @@ -2120,26 +2281,28 @@ export const SplitPanelLayoutComponent: React.FC .map((col) => { // ๐Ÿ†• ์—”ํ‹ฐํ‹ฐ ์กฐ์ธ ์ปฌ๋Ÿผ ์ฒ˜๋ฆฌ let value = item[col.name]; - if (value === undefined && col.name.includes('.')) { - const columnName = col.name.split('.').pop(); + if (value === undefined && col.name.includes(".")) { + const columnName = col.name.split(".").pop(); // 1์ฐจ: ์ปฌ๋Ÿผ๋ช… ๊ทธ๋Œ€๋กœ - value = item[columnName || '']; + value = item[columnName || ""]; // 2์ฐจ: {fk_column}_name ๋˜๋Š” {fk_column}_{ref_column} ํ˜•์‹ ํ™•์ธ if (value === undefined) { - const parts = col.name.split('.'); + const parts = col.name.split("."); if (parts.length === 2) { const refTable = parts[0]; // item_info const refColumn = parts[1]; // item_number ๋˜๋Š” item_name // FK ์ปฌ๋Ÿผ๋ช… ์ถ”๋ก : item_info โ†’ item_id - const fkColumn = refTable.replace('_info', '').replace('_mng', '') + '_id'; - + const fkColumn = refTable.replace("_info", "").replace("_mng", "") + "_id"; + // ๋ฐฑ์—”๋“œ์—์„œ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ณ„์นญ ํŒจํ„ด: // 1) item_id_name (๊ธฐ๋ณธ referenceColumn) // 2) item_id_item_name (์ถ”๊ฐ€ ์ปฌ๋Ÿผ) - if (refColumn === refTable.replace('_info', '').replace('_mng', '') + '_number' || - refColumn === refTable.replace('_info', '').replace('_mng', '') + '_code') { + if ( + refColumn === refTable.replace("_info", "").replace("_mng", "") + "_number" || + refColumn === refTable.replace("_info", "").replace("_mng", "") + "_code" + ) { // ๊ธฐ๋ณธ ์ฐธ์กฐ ์ปฌ๋Ÿผ - const aliasKey = fkColumn + '_name'; + const aliasKey = fkColumn + "_name"; value = item[aliasKey]; } else { // ์ถ”๊ฐ€ ์ปฌ๋Ÿผ @@ -2158,11 +2321,11 @@ export const SplitPanelLayoutComponent: React.FC firstValues = Object.entries(item) .filter(([key]) => !key.toLowerCase().includes("id")) .slice(0, summaryCount) - .map(([key, value]) => [key, value, ''] as [string, any, string]); + .map(([key, value]) => [key, value, ""] as [string, any, string]); allValues = Object.entries(item) .filter(([key, value]) => value !== null && value !== undefined && value !== "") - .map(([key, value]) => [key, value, ''] as [string, any, string]); + .map(([key, value]) => [key, value, ""] as [string, any, string]); } return ( @@ -2180,30 +2343,15 @@ export const SplitPanelLayoutComponent: React.FC
{firstValues.map(([key, value, label], idx) => { // ํฌ๋งท ์„ค์ • ๋ฐ ๋ณผ๋“œ ์„ค์ • ์ฐพ๊ธฐ - const colConfig = rightColumns?.find(c => c.name === key); + const colConfig = rightColumns?.find((c) => c.name === key); const format = colConfig?.format; const boldValue = colConfig?.bold ?? false; - - // ๐Ÿ†• ์นดํ…Œ๊ณ ๋ฆฌ ๋งคํ•‘ ์ ์šฉ - const formattedValue = formatCellValue(key, value, rightCategoryMappings); - - // ์ˆซ์ž ํฌ๋งท ์ ์šฉ (์นดํ…Œ๊ณ ๋ฆฌ๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ๋งŒ) - let displayValue: React.ReactNode = formattedValue; - if (typeof formattedValue === 'string' && value !== null && value !== undefined && value !== "" && format) { - const numValue = typeof value === 'number' ? value : parseFloat(String(value)); - if (!isNaN(numValue)) { - displayValue = numValue.toLocaleString('ko-KR', { - minimumFractionDigits: format.decimalPlaces ?? 0, - maximumFractionDigits: format.decimalPlaces ?? 10, - useGrouping: format.thousandSeparator ?? false, - }); - if (format.prefix) displayValue = format.prefix + displayValue; - if (format.suffix) displayValue = displayValue + format.suffix; - } - } - + + // ๐Ÿ†• ํฌ๋งท ์ ์šฉ (๋‚ ์งœ/์ˆซ์ž/์นดํ…Œ๊ณ ๋ฆฌ) + const displayValue = formatCellValue(key, value, rightCategoryMappings, format); + const showLabel = componentConfig.rightPanel?.summaryShowLabel ?? true; - + return (
{showLabel && ( @@ -2211,8 +2359,8 @@ export const SplitPanelLayoutComponent: React.FC {label || getColumnLabel(key)}: )} - {displayValue} @@ -2233,19 +2381,19 @@ export const SplitPanelLayoutComponent: React.FC }} className="h-7" > - + {componentConfig.rightPanel?.editButton?.buttonLabel || "์ˆ˜์ •"} )} {/* ์‚ญ์ œ ๋ฒ„ํŠผ */} - {!isDesignMode && ( + {!isDesignMode && (componentConfig.rightPanel?.deleteButton?.enabled ?? true) && ( @@ -2274,27 +2422,12 @@ export const SplitPanelLayoutComponent: React.FC {allValues.map(([key, value, label]) => { // ํฌ๋งท ์„ค์ • ์ฐพ๊ธฐ - const colConfig = rightColumns?.find(c => c.name === key); + const colConfig = rightColumns?.find((c) => c.name === key); const format = colConfig?.format; - - // ๐Ÿ†• ์นดํ…Œ๊ณ ๋ฆฌ ๋งคํ•‘ ์ ์šฉ - const formattedValue = formatCellValue(key, value, rightCategoryMappings); - - // ์ˆซ์ž ํฌ๋งท ์ ์šฉ (์นดํ…Œ๊ณ ๋ฆฌ๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ๋งŒ) - let displayValue: React.ReactNode = formattedValue; - if (typeof formattedValue === 'string' && value !== null && value !== undefined && value !== "" && format) { - const numValue = typeof value === 'number' ? value : parseFloat(String(value)); - if (!isNaN(numValue)) { - displayValue = numValue.toLocaleString('ko-KR', { - minimumFractionDigits: format.decimalPlaces ?? 0, - maximumFractionDigits: format.decimalPlaces ?? 10, - useGrouping: format.thousandSeparator ?? false, - }); - if (format.prefix) displayValue = format.prefix + displayValue; - if (format.suffix) displayValue = displayValue + format.suffix; - } - } - + + // ๐Ÿ†• ํฌ๋งท ์ ์šฉ (๋‚ ์งœ/์ˆซ์ž/์นดํ…Œ๊ณ ๋ฆฌ) + const displayValue = formatCellValue(key, value, rightCategoryMappings, format); + return ( @@ -2310,8 +2443,8 @@ export const SplitPanelLayoutComponent: React.FC
)}
- ); - })} + ); + })} ) : (
@@ -2336,21 +2469,24 @@ export const SplitPanelLayoutComponent: React.FC console.log("๐Ÿ” [๋””๋ฒ„๊น…] ์ƒ์„ธ ๋ชจ๋“œ ํ‘œ์‹œ ๋กœ์ง:"); console.log(" ๐Ÿ“‹ rightData ์ „์ฒด:", rightData); console.log(" ๐Ÿ“‹ rightData keys:", Object.keys(rightData)); - console.log(" โš™๏ธ ์„ค์ •๋œ ์ปฌ๋Ÿผ:", rightColumns.map((c) => `${c.name} (${c.label})`)); - + console.log( + " โš™๏ธ ์„ค์ •๋œ ์ปฌ๋Ÿผ:", + rightColumns.map((c) => `${c.name} (${c.label})`), + ); + // ์„ค์ •๋œ ์ปฌ๋Ÿผ๋งŒ ํ‘œ์‹œ displayEntries = rightColumns .map((col) => { // ๐Ÿ†• ์—”ํ‹ฐํ‹ฐ ์กฐ์ธ ์ปฌ๋Ÿผ ์ฒ˜๋ฆฌ (์˜ˆ: item_info.item_name โ†’ item_name) let value = rightData[col.name]; console.log(` ๐Ÿ”Ž ์ปฌ๋Ÿผ "${col.name}": ์ง์ ‘ ์ ‘๊ทผ = ${value}`); - - if (value === undefined && col.name.includes('.')) { - const columnName = col.name.split('.').pop(); - value = rightData[columnName || '']; + + if (value === undefined && col.name.includes(".")) { + const columnName = col.name.split(".").pop(); + value = rightData[columnName || ""]; console.log(` โ†’ ๋ณ€ํ™˜ ํ›„ "${columnName}" ์ ‘๊ทผ = ${value}`); } - + return [col.name, value, col.label] as [string, any, string]; }) .filter(([key, value]) => { diff --git a/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutConfigPanel.tsx b/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutConfigPanel.tsx index 387ef85f..bbb115e0 100644 --- a/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutConfigPanel.tsx +++ b/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutConfigPanel.tsx @@ -71,9 +71,7 @@ const GroupByColumnsSelector: React.FC<{ if (!tableName) { return (
-

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

+

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

); } @@ -83,14 +81,14 @@ const GroupByColumnsSelector: React.FC<{ {loading ? (
-

๋กœ๋”ฉ ์ค‘...

+

๋กœ๋”ฉ ์ค‘...

) : columns.length === 0 ? (
-

์ปฌ๋Ÿผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค

+

์ปฌ๋Ÿผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค

) : ( -
+
{columns.map((col) => (
toggleColumn(col.columnName)} /> -
)} -

+

์„ ํƒ๋œ ์ปฌ๋Ÿผ: {selectedColumns.length > 0 ? selectedColumns.join(", ") : "์—†์Œ"}
๊ฐ™์€ ๊ฐ’์„ ๊ฐ€์ง„ ๋ชจ๋“  ๋ ˆ์ฝ”๋“œ๋ฅผ ํ•จ๊ป˜ ๋ถˆ๋Ÿฌ์˜ต๋‹ˆ๋‹ค @@ -135,7 +130,9 @@ const ScreenSelector: React.FC<{ try { const { screenApi } = await import("@/lib/api/screen"); const response = await screenApi.getScreens({ page: 1, size: 1000 }); - setScreens(response.data.map((s) => ({ screenId: s.screenId, screenName: s.screenName, screenCode: s.screenCode }))); + setScreens( + response.data.map((s) => ({ screenId: s.screenId, screenName: s.screenName, screenCode: s.screenCode })), + ); } catch (error) { console.error("ํ™”๋ฉด ๋ชฉ๋ก ๋กœ๋“œ ์‹คํŒจ:", error); } finally { @@ -161,11 +158,11 @@ const ScreenSelector: React.FC<{ - + - ํ™”๋ฉด์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. + ํ™”๋ฉด์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. {screens.map((screen) => ( - +

{screen.screenName} - {screen.screenCode} + {screen.screenCode}
))} @@ -213,7 +208,7 @@ export const SplitPanelLayoutConfigPanel: React.FC>({}); - + // ๐Ÿ†• ์ž…๋ ฅ ํ•„๋“œ์šฉ ๋กœ์ปฌ ์ƒํƒœ const [isUserEditing, setIsUserEditing] = useState(false); const [localTitles, setLocalTitles] = useState({ @@ -223,7 +218,7 @@ export const SplitPanelLayoutConfigPanel: React.FC { if (!isUserEditing) { @@ -273,7 +268,7 @@ export const SplitPanelLayoutConfigPanel: React.FC ({ ...prev, [tableName]: columns })); - + // ๐Ÿ†• ์—”ํ‹ฐํ‹ฐ ํƒ€์ž… ์ปฌ๋Ÿผ์˜ ์ฐธ์กฐ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ๋„ ๋กœ๋“œ await loadEntityReferenceColumns(tableName, columns); } catch (error) { @@ -366,30 +361,31 @@ export const SplitPanelLayoutConfigPanel: React.FC ({ ...prev, [tableName]: false })); } }; - + // ๐Ÿ†• ์—”ํ‹ฐํ‹ฐ ์ฐธ์กฐ ํ…Œ์ด๋ธ”์˜ ์ปฌ๋Ÿผ ๋กœ๋“œ const loadEntityReferenceColumns = async (sourceTableName: string, columns: ColumnInfo[]) => { const entityColumns = columns.filter( - col => (col.input_type === 'entity' || col.webType === 'entity') && col.referenceTable + (col) => (col.input_type === "entity" || col.webType === "entity") && col.referenceTable, ); - + if (entityColumns.length === 0) { return; } - - console.log(`๐Ÿ”— ํ…Œ์ด๋ธ” ${sourceTableName}์˜ ์—”ํ‹ฐํ‹ฐ ์ฐธ์กฐ ${entityColumns.length}๊ฐœ ๋ฐœ๊ฒฌ:`, - entityColumns.map(c => `${c.columnName} -> ${c.referenceTable}`) + + console.log( + `๐Ÿ”— ํ…Œ์ด๋ธ” ${sourceTableName}์˜ ์—”ํ‹ฐํ‹ฐ ์ฐธ์กฐ ${entityColumns.length}๊ฐœ ๋ฐœ๊ฒฌ:`, + entityColumns.map((c) => `${c.columnName} -> ${c.referenceTable}`), ); - - const referenceTableData: Array<{tableName: string, columns: ColumnInfo[]}> = []; - + + const referenceTableData: Array<{ tableName: string; columns: ColumnInfo[] }> = []; + // ๊ฐ ์ฐธ์กฐ ํ…Œ์ด๋ธ”์˜ ์ปฌ๋Ÿผ ๋กœ๋“œ for (const entityCol of entityColumns) { const refTableName = entityCol.referenceTable!; - + // ์ด๋ฏธ ๋กœ๋“œํ–ˆ์œผ๋ฉด ์Šคํ‚ต - if (referenceTableData.some(t => t.tableName === refTableName)) continue; - + if (referenceTableData.some((t) => t.tableName === refTableName)) continue; + try { const refColumnsResponse = await tableTypeApi.getColumns(refTableName); const refColumns: ColumnInfo[] = (refColumnsResponse || []).map((col: any) => ({ @@ -399,24 +395,24 @@ export const SplitPanelLayoutConfigPanel: React.FC ({ + setEntityReferenceTables((prev) => ({ ...prev, - [sourceTableName]: referenceTableData + [sourceTableName]: referenceTableData, })); - + console.log(`โœ… [์—”ํ‹ฐํ‹ฐ ์ฐธ์กฐ] ${sourceTableName}์˜ ์ฐธ์กฐ ํ…Œ์ด๋ธ” ์ €์žฅ ์™„๋ฃŒ:`, { sourceTableName, referenceTableCount: referenceTableData.length, - referenceTables: referenceTableData.map(t => `${t.tableName}(${t.columns.length}๊ฐœ)`), + referenceTables: referenceTableData.map((t) => `${t.tableName}(${t.columns.length}๊ฐœ)`), }); }; @@ -450,7 +446,7 @@ export const SplitPanelLayoutConfigPanel: React.FC = [] + existingColumns: Array<{ name: string; label: string; required?: boolean }> = [], ) => { const tableColumns = loadedTableColumns[tableName]; if (!tableColumns) { @@ -460,10 +456,13 @@ export const SplitPanelLayoutConfigPanel: React.FC col.isPrimaryKey); - console.log(`๐Ÿ”‘ ํ…Œ์ด๋ธ” ${tableName}์˜ PK ์ปฌ๋Ÿผ:`, pkColumns.map(c => c.columnName)); + console.log( + `๐Ÿ”‘ ํ…Œ์ด๋ธ” ${tableName}์˜ PK ์ปฌ๋Ÿผ:`, + pkColumns.map((c) => c.columnName), + ); // ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌ๋˜๋Š” ์ปฌ๋Ÿผ (๋ฐฑ์—”๋“œ์—์„œ ์ž๋™ ์ถ”๊ฐ€) - const autoHandledColumns = ['company_code', 'company_name']; + const autoHandledColumns = ["company_code", "company_name"]; // ๊ธฐ์กด ์ปฌ๋Ÿผ ์ด๋ฆ„ ๋ชฉ๋ก const existingColumnNames = existingColumns.map((col) => col.name); @@ -479,7 +478,10 @@ export const SplitPanelLayoutConfigPanel: React.FC 0) { - console.log(`โœ… PK ์ปฌ๋Ÿผ ${pkColumnsToAdd.length}๊ฐœ ์ž๋™ ์ถ”๊ฐ€:`, pkColumnsToAdd.map(c => c.name)); + console.log( + `โœ… PK ์ปฌ๋Ÿผ ${pkColumnsToAdd.length}๊ฐœ ์ž๋™ ์ถ”๊ฐ€:`, + pkColumnsToAdd.map((c) => c.name), + ); } return [...pkColumnsToAdd, ...existingColumns]; @@ -505,7 +507,7 @@ export const SplitPanelLayoutConfigPanel: React.FC { return leftTableName ? loadedTableColumns[leftTableName] || [] : []; @@ -513,7 +515,7 @@ export const SplitPanelLayoutConfigPanel: React.FC { return rightTableName ? loadedTableColumns[rightTableName] || [] : []; @@ -581,7 +583,7 @@ export const SplitPanelLayoutConfigPanel: React.FC {/* ์ขŒ์ธก ํŒจ๋„ ์„ค์ • */} -
+

์ขŒ์ธก ํŒจ๋„ ์„ค์ • (๋งˆ์Šคํ„ฐ)

@@ -590,7 +592,7 @@ export const SplitPanelLayoutConfigPanel: React.FC { setIsUserEditing(true); - setLocalTitles(prev => ({ ...prev, left: e.target.value })); + setLocalTitles((prev) => ({ ...prev, left: e.target.value })); }} onBlur={() => { setIsUserEditing(false); @@ -671,16 +673,10 @@ export const SplitPanelLayoutConfigPanel: React.FC -

- ์„ ํƒ๋œ ํ•ญ๋ชฉ์˜ ์–ด๋–ค ์ปฌ๋Ÿผ ๊ฐ’์„ ์‚ฌ์šฉํ• ์ง€ (์˜ˆ: dept_code) -

+

์„ ํƒ๋œ ํ•ญ๋ชฉ์˜ ์–ด๋–ค ์ปฌ๋Ÿผ ๊ฐ’์„ ์‚ฌ์šฉํ• ์ง€ (์˜ˆ: dept_code)

- @@ -691,34 +687,34 @@ export const SplitPanelLayoutConfigPanel: React.FC์ปฌ๋Ÿผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. {leftTableColumns - .filter((column) => !['company_code', 'company_name'].includes(column.columnName)) + .filter((column) => !["company_code", "company_name"].includes(column.columnName)) .map((column) => ( - { - updateLeftPanel({ - itemAddConfig: { - ...config.leftPanel?.itemAddConfig, - sourceColumn: value, - parentColumn: config.leftPanel?.itemAddConfig?.parentColumn || "", - } - }); - }} - className="text-xs" - > - - {column.columnLabel || column.columnName} - - ({column.columnName}) - - - ))} + { + updateLeftPanel({ + itemAddConfig: { + ...config.leftPanel?.itemAddConfig, + sourceColumn: value, + parentColumn: config.leftPanel?.itemAddConfig?.parentColumn || "", + }, + }); + }} + className="text-xs" + > + + {column.columnLabel || column.columnName} + ({column.columnName}) + + ))} @@ -733,11 +729,7 @@ export const SplitPanelLayoutConfigPanel: React.FC - @@ -748,34 +740,34 @@ export const SplitPanelLayoutConfigPanel: React.FC์ปฌ๋Ÿผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. {leftTableColumns - .filter((column) => !['company_code', 'company_name'].includes(column.columnName)) + .filter((column) => !["company_code", "company_name"].includes(column.columnName)) .map((column) => ( - { - updateLeftPanel({ - itemAddConfig: { - ...config.leftPanel?.itemAddConfig, - parentColumn: value, - sourceColumn: config.leftPanel?.itemAddConfig?.sourceColumn || "", - } - }); - }} - className="text-xs" - > - - {column.columnLabel || column.columnName} - - ({column.columnName}) - - - ))} + { + updateLeftPanel({ + itemAddConfig: { + ...config.leftPanel?.itemAddConfig, + parentColumn: value, + sourceColumn: config.leftPanel?.itemAddConfig?.sourceColumn || "", + }, + }); + }} + className="text-xs" + > + + {column.columnLabel || column.columnName} + ({column.columnName}) + + ))} @@ -791,17 +783,14 @@ export const SplitPanelLayoutConfigPanel: React.FC { const currentColumns = config.leftPanel?.itemAddConfig?.addModalColumns || []; - const newColumns = [ - ...currentColumns, - { name: "", label: "", required: false }, - ]; - updateLeftPanel({ + const newColumns = [...currentColumns, { name: "", label: "", required: false }]; + updateLeftPanel({ itemAddConfig: { ...config.leftPanel?.itemAddConfig, addModalColumns: newColumns, parentColumn: config.leftPanel?.itemAddConfig?.parentColumn || "", sourceColumn: config.leftPanel?.itemAddConfig?.sourceColumn || "", - } + }, }); }} className="h-6 text-[10px]" @@ -810,9 +799,7 @@ export const SplitPanelLayoutConfigPanel: React.FC
-

- ํ•˜์œ„ ํ•ญ๋ชฉ ์ถ”๊ฐ€ ์‹œ ์ž…๋ ฅ๋ฐ›์„ ํ•„๋“œ๋ฅผ ์„ ํƒํ•˜์„ธ์š” -

+

ํ•˜์œ„ ํ•ญ๋ชฉ ์ถ”๊ฐ€ ์‹œ ์ž…๋ ฅ๋ฐ›์„ ํ•„๋“œ๋ฅผ ์„ ํƒํ•˜์„ธ์š”

{(config.leftPanel?.itemAddConfig?.addModalColumns || []).length === 0 ? ( @@ -821,7 +808,7 @@ export const SplitPanelLayoutConfigPanel: React.FC ) : ( (config.leftPanel?.itemAddConfig?.addModalColumns || []).map((col, index) => { - const column = leftTableColumns.find(c => c.columnName === col.name); + const column = leftTableColumns.find((c) => c.columnName === col.name); const isPK = column?.isPrimaryKey || false; return ( @@ -829,7 +816,7 @@ export const SplitPanelLayoutConfigPanel: React.FC {isPK && ( @@ -856,41 +843,41 @@ export const SplitPanelLayoutConfigPanel: React.FC์ปฌ๋Ÿผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. {leftTableColumns - .filter((column) => !['company_code', 'company_name'].includes(column.columnName)) + .filter((column) => !["company_code", "company_name"].includes(column.columnName)) .map((column) => ( - { - const newColumns = [...(config.leftPanel?.itemAddConfig?.addModalColumns || [])]; - newColumns[index] = { - ...newColumns[index], - name: value, - label: column.columnLabel || value, - }; - updateLeftPanel({ - itemAddConfig: { - ...config.leftPanel?.itemAddConfig, - addModalColumns: newColumns, - parentColumn: config.leftPanel?.itemAddConfig?.parentColumn || "", - sourceColumn: config.leftPanel?.itemAddConfig?.sourceColumn || "", - } - }); - }} - className="text-xs" - > - - {column.columnLabel || column.columnName} - - ({column.columnName}) - - - ))} + { + const newColumns = [ + ...(config.leftPanel?.itemAddConfig?.addModalColumns || []), + ]; + newColumns[index] = { + ...newColumns[index], + name: value, + label: column.columnLabel || value, + }; + updateLeftPanel({ + itemAddConfig: { + ...config.leftPanel?.itemAddConfig, + addModalColumns: newColumns, + parentColumn: config.leftPanel?.itemAddConfig?.parentColumn || "", + sourceColumn: config.leftPanel?.itemAddConfig?.sourceColumn || "", + }, + }); + }} + className="text-xs" + > + + {column.columnLabel || column.columnName} + ({column.columnName}) + + ))} @@ -908,13 +895,13 @@ export const SplitPanelLayoutConfigPanel: React.FC { const newColumns = (config.leftPanel?.itemAddConfig?.addModalColumns || []).filter( - (_, i) => i !== index + (_, i) => i !== index, ); - updateLeftPanel({ + updateLeftPanel({ itemAddConfig: { ...config.leftPanel?.itemAddConfig, addModalColumns: newColumns, parentColumn: config.leftPanel?.itemAddConfig?.parentColumn || "", sourceColumn: config.leftPanel?.itemAddConfig?.sourceColumn || "", - } + }, }); }} className="h-7 w-7 p-0" @@ -962,10 +949,7 @@ export const SplitPanelLayoutConfigPanel: React.FC { const currentColumns = config.leftPanel?.columns || []; - const newColumns = [ - ...currentColumns, - { name: "", label: "", width: 100 }, - ]; + const newColumns = [...currentColumns, { name: "", label: "", width: 100 }]; updateLeftPanel({ columns: newColumns }); }} className="h-7 text-xs" @@ -984,227 +968,342 @@ export const SplitPanelLayoutConfigPanel: React.FC

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

-

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

+

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

) : ( (config.leftPanel?.columns || []).map((col, index) => { const isTableMode = config.leftPanel?.displayMode === "table"; - + return ( -
-
- {/* ์ˆœ์„œ ๋ณ€๊ฒฝ ๋ฒ„ํŠผ */} -
- - -
- -
- - - - - - - - ์ปฌ๋Ÿผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. -
- {/* ๊ธฐ๋ณธ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ */} - - {leftTableColumns.map((column) => ( - { - const newColumns = [...(config.leftPanel?.columns || [])]; - newColumns[index] = { - ...newColumns[index], - name: value, - label: column.columnLabel || value, - }; - updateLeftPanel({ columns: newColumns }); - // Popover ๋‹ซ๊ธฐ - document.body.click(); - }} - className="text-xs" - > - - {column.columnLabel || column.columnName} - - ({column.columnName}) - - - ))} - - - {/* ๐Ÿ†• ์—”ํ‹ฐํ‹ฐ ์ฐธ์กฐ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ */} - {leftTableName && entityReferenceTables[leftTableName]?.map((refTable) => ( - - {refTable.columns.map((column) => { - const fullColumnName = `${refTable.tableName}.${column.columnName}`; - return ( - { - const newColumns = [...(config.leftPanel?.columns || [])]; - newColumns[index] = { - ...newColumns[index], - name: value, - label: column.columnLabel || column.columnName, - }; - updateLeftPanel({ columns: newColumns }); - // Popover ๋‹ซ๊ธฐ - document.body.click(); - }} - className="text-xs pl-6" - > - - {column.columnLabel || column.columnName} - - ({column.columnName}) - - - ); - })} - - ))} -
-
-
-
-
- -
- - {/* ํ…Œ์ด๋ธ” ๋ชจ๋“œ ์ „์šฉ ์˜ต์…˜ */} - {isTableMode && ( -
-
- - { +
+
+ {/* ์ˆœ์„œ ๋ณ€๊ฒฝ ๋ฒ„ํŠผ */} +
+
-
- - + + +
-
-
+ +
+ + {/* ํ…Œ์ด๋ธ” ๋ชจ๋“œ ์ „์šฉ ์˜ต์…˜ */} + {isTableMode && ( +
+
+ + { const newColumns = [...(config.leftPanel?.columns || [])]; newColumns[index] = { ...newColumns[index], - sortable: e.target.checked, + width: parseInt(e.target.value) || 100, }; updateLeftPanel({ columns: newColumns }); }} - className="h-3 w-3" + className="h-7 text-xs" /> - ์ •๋ ฌ๊ฐ€๋Šฅ - +
+
+ + +
+
+ +
-
- )} -
- ); + )} + + {/* ๐Ÿ†• ๋‚ ์งœ ํƒ€์ž… ํฌ๋งท ์„ค์ • (์ขŒ์ธก) */} + {(() => { + const column = leftTableColumns.find((c) => c.columnName === col.name); + const dbDateTypes = [ + "date", + "timestamp", + "timestamptz", + "timestamp with time zone", + "timestamp without time zone", + "time", + "timetz", + ]; + const inputDateTypes = ["date", "datetime", "time"]; + + const isDate = + column && + (dbDateTypes.includes(column.dataType?.toLowerCase() || "") || + inputDateTypes.includes(column.input_type?.toLowerCase() || "") || + inputDateTypes.includes(column.webType?.toLowerCase() || "")); + if (!isDate) return null; + return ( +
+ + +
+ ); + })()} + + {/* ๐Ÿ†• ์ˆซ์ž ํƒ€์ž… ํฌ๋งท ์„ค์ • (์ขŒ์ธก) */} + {(() => { + const column = leftTableColumns.find((c) => c.columnName === col.name); + const dbNumericTypes = [ + "numeric", + "decimal", + "integer", + "bigint", + "double precision", + "real", + "smallint", + "int4", + "int8", + "float4", + "float8", + ]; + const inputNumericTypes = ["number", "decimal", "currency", "integer"]; + + const isNumeric = + column && + (dbNumericTypes.includes(column.dataType?.toLowerCase() || "") || + inputNumericTypes.includes(column.input_type?.toLowerCase() || "") || + inputNumericTypes.includes(column.webType?.toLowerCase() || "")); + if (!isNumeric) return null; + return ( +
+ +
+ +
+ + { + const newColumns = [...(config.leftPanel?.columns || [])]; + newColumns[index] = { + ...newColumns[index], + format: { + ...newColumns[index].format, + type: "number", + decimalPlaces: e.target.value ? parseInt(e.target.value) : undefined, + }, + }; + updateLeftPanel({ columns: newColumns }); + }} + className="h-6 w-12 text-xs" + /> +
+
+
+ ); + })()} +
+ ); }) )}
@@ -1220,10 +1319,7 @@ export const SplitPanelLayoutConfigPanel: React.FC { const currentColumns = config.leftPanel?.addModalColumns || []; - const newColumns = [ - ...currentColumns, - { name: "", label: "", required: false }, - ]; + const newColumns = [...currentColumns, { name: "", label: "", required: false }]; updateLeftPanel({ addModalColumns: newColumns }); }} className="h-7 text-xs" @@ -1232,9 +1328,7 @@ export const SplitPanelLayoutConfigPanel: React.FC
-

- ์ถ”๊ฐ€ ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ๋ชจ๋‹ฌ์— ํ‘œ์‹œ๋  ์ž…๋ ฅ ํ•„๋“œ๋ฅผ ์„ ํƒํ•˜์„ธ์š” -

+

์ถ”๊ฐ€ ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ๋ชจ๋‹ฌ์— ํ‘œ์‹œ๋  ์ž…๋ ฅ ํ•„๋“œ๋ฅผ ์„ ํƒํ•˜์„ธ์š”

{(config.leftPanel?.addModalColumns || []).length === 0 ? ( @@ -1244,110 +1338,106 @@ export const SplitPanelLayoutConfigPanel: React.FC { // ํ˜„์žฌ ์ปฌ๋Ÿผ์ด PK์ธ์ง€ ํ™•์ธ - const column = leftTableColumns.find(c => c.columnName === col.name); + const column = leftTableColumns.find((c) => c.columnName === col.name); const isPK = column?.isPrimaryKey || false; - + return ( -
- {isPK && ( - - PK - - )} -
- - - - - - - - ์ปฌ๋Ÿผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. - - {leftTableColumns - .filter((column) => !['company_code', 'company_name'].includes(column.columnName)) - .map((column) => ( - { - const newColumns = [...(config.leftPanel?.addModalColumns || [])]; - newColumns[index] = { - ...newColumns[index], - name: value, - label: column.columnLabel || value, - }; - updateLeftPanel({ addModalColumns: newColumns }); - }} - className="text-xs" - > - - {column.columnLabel || column.columnName} - - ({column.columnName}) - - - ))} - - - - -
-
- -
- -
- ); + {isPK && ( + + PK + + )} +
+ + + + + + + + ์ปฌ๋Ÿผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. + + {leftTableColumns + .filter((column) => !["company_code", "company_name"].includes(column.columnName)) + .map((column) => ( + { + const newColumns = [...(config.leftPanel?.addModalColumns || [])]; + newColumns[index] = { + ...newColumns[index], + name: value, + label: column.columnLabel || value, + }; + updateLeftPanel({ addModalColumns: newColumns }); + }} + className="text-xs" + > + + {column.columnLabel || column.columnName} + ({column.columnName}) + + ))} + + + + +
+
+ +
+ +
+ ); }) )}
@@ -1356,27 +1446,28 @@ export const SplitPanelLayoutConfigPanel: React.FC {/* ์ขŒ์ธก ํŒจ๋„ ๋ฐ์ดํ„ฐ ํ•„ํ„ฐ๋ง */} -
+

์ขŒ์ธก ํŒจ๋„ ๋ฐ์ดํ„ฐ ํ•„ํ„ฐ๋ง

-

- ํŠน์ • ์ปฌ๋Ÿผ ๊ฐ’์œผ๋กœ ์ขŒ์ธก ํŒจ๋„ ๋ฐ์ดํ„ฐ๋ฅผ ํ•„ํ„ฐ๋งํ•ฉ๋‹ˆ๋‹ค -

- ({ - columnName: col.columnName, - columnLabel: col.columnLabel || col.columnName, - dataType: col.dataType || "text", - input_type: (col as any).input_type, - } as any))} - config={config.leftPanel?.dataFilter} - onConfigChange={(dataFilter) => updateLeftPanel({ dataFilter })} - menuObjid={menuObjid} // ๐Ÿ†• ๋ฉ”๋‰ด OBJID ์ „๋‹ฌ - /> +

ํŠน์ • ์ปฌ๋Ÿผ ๊ฐ’์œผ๋กœ ์ขŒ์ธก ํŒจ๋„ ๋ฐ์ดํ„ฐ๋ฅผ ํ•„ํ„ฐ๋งํ•ฉ๋‹ˆ๋‹ค

+ + ({ + columnName: col.columnName, + columnLabel: col.columnLabel || col.columnName, + dataType: col.dataType || "text", + input_type: (col as any).input_type, + }) as any, + )} + config={config.leftPanel?.dataFilter} + onConfigChange={(dataFilter) => updateLeftPanel({ dataFilter })} + menuObjid={menuObjid} // ๐Ÿ†• ๋ฉ”๋‰ด OBJID ์ „๋‹ฌ + />
{/* ์šฐ์ธก ํŒจ๋„ ์„ค์ • */} -
+

์šฐ์ธก ํŒจ๋„ ์„ค์ • ({relationshipType === "detail" ? "์ƒ์„ธ" : "์กฐ์ธ"})

@@ -1385,7 +1476,7 @@ export const SplitPanelLayoutConfigPanel: React.FC { setIsUserEditing(true); - setLocalTitles(prev => ({ ...prev, right: e.target.value })); + setLocalTitles((prev) => ({ ...prev, right: e.target.value })); }} onBlur={() => { setIsUserEditing(false); @@ -1484,7 +1575,7 @@ export const SplitPanelLayoutConfigPanel: React.FC - +
-

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

+

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

@@ -1521,110 +1610,232 @@ export const SplitPanelLayoutConfigPanel: React.FC - -

์ขŒ์ธก ํ…Œ์ด๋ธ”์˜ ์ปฌ๋Ÿผ์„ ์šฐ์ธก ํ…Œ์ด๋ธ”์˜ ์ปฌ๋Ÿผ๊ณผ ์—ฐ๊ฒฐํ•ฉ๋‹ˆ๋‹ค

+
+
+ +

์ขŒ์ธก ํ…Œ์ด๋ธ”์˜ ์ปฌ๋Ÿผ์„ ์šฐ์ธก ํ…Œ์ด๋ธ”์˜ ์ปฌ๋Ÿผ๊ณผ ์—ฐ๊ฒฐํ•ฉ๋‹ˆ๋‹ค

+
+ +
-
- - - - - - - - - ์ปฌ๋Ÿผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. - - {leftTableColumns.map((column) => ( - { +

๋ณตํ•ฉํ‚ค: ์—ฌ๋Ÿฌ ์ปฌ๋Ÿผ์œผ๋กœ ์กฐ์ธ (์˜ˆ: item_code + lot_number)

+ + {/* ๋ณตํ•ฉํ‚ค๊ฐ€ ์„ค์ •๋œ ๊ฒฝ์šฐ */} + {(config.rightPanel?.relation?.keys || []).length > 0 ? ( + <> + {(config.rightPanel?.relation?.keys || []).map((key, index) => ( +
+
+ ์กฐ์ธ ํ‚ค {index + 1} + +
+
+
+ + +
+
+ + +
+
+
+ ))} + + ) : ( + /* ๋‹จ์ผํ‚ค (ํ•˜์œ„ ํ˜ธํ™˜์„ฑ) */ + <> +
+ + + + + + + + + ์ปฌ๋Ÿผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. + + {leftTableColumns.map((column) => ( + { + updateRightPanel({ + relation: { ...config.rightPanel?.relation, leftColumn: value }, + }); + setLeftColumnOpen(false); + }} + > + + {column.columnName} + ({column.columnLabel || ""}) + + ))} + + + + +
+ +
+ +
+ +
+ + + + + + + + + ์ปฌ๋Ÿผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. + + {rightTableColumns.map((column) => ( + { + updateRightPanel({ + relation: { ...config.rightPanel?.relation, foreignKey: value }, + }); + setRightColumnOpen(false); + }} + > + + {column.columnName} + ({column.columnLabel || ""}) + + ))} + + + + +
+ + )}
)} @@ -1653,10 +1864,7 @@ export const SplitPanelLayoutConfigPanel: React.FC { const currentColumns = config.rightPanel?.columns || []; - const newColumns = [ - ...currentColumns, - { name: "", label: "", width: 100 }, - ]; + const newColumns = [...currentColumns, { name: "", label: "", width: 100 }]; updateRightPanel({ columns: newColumns }); }} className="h-7 text-xs" @@ -1675,371 +1883,461 @@ export const SplitPanelLayoutConfigPanel: React.FC

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

-

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

+

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

) : ( (config.rightPanel?.columns || []).map((col, index) => { const isTableMode = config.rightPanel?.displayMode === "table"; - + return ( -
-
- {/* ์ˆœ์„œ ๋ณ€๊ฒฝ ๋ฒ„ํŠผ */} -
- - -
- -
- - - - - - - - ์ปฌ๋Ÿผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. -
- {/* ๊ธฐ๋ณธ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ */} - - {rightTableColumns.map((column) => ( - { - const newColumns = [...(config.rightPanel?.columns || [])]; - newColumns[index] = { - ...newColumns[index], - name: value, - label: column.columnLabel || value, - }; - updateRightPanel({ columns: newColumns }); - // Popover ๋‹ซ๊ธฐ - document.body.click(); - }} - className="text-xs" - > - - {column.columnLabel || column.columnName} - - ({column.columnName}) - - - ))} - - - {/* ๐Ÿ†• ์—”ํ‹ฐํ‹ฐ ์ฐธ์กฐ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ */} - {rightTableName && entityReferenceTables[rightTableName]?.map((refTable) => ( - - {refTable.columns.map((column) => { - const fullColumnName = `${refTable.tableName}.${column.columnName}`; - return ( - { - const newColumns = [...(config.rightPanel?.columns || [])]; - newColumns[index] = { - ...newColumns[index], - name: value, - label: column.columnLabel || column.columnName, - }; - updateRightPanel({ columns: newColumns }); - // Popover ๋‹ซ๊ธฐ - document.body.click(); - }} - className="text-xs pl-6" - > - - {column.columnLabel || column.columnName} - - ({column.columnName}) - - - ); - })} - - ))} -
-
-
-
-
- -
- - {/* ํ…Œ์ด๋ธ” ๋ชจ๋“œ ์ „์šฉ ์˜ต์…˜ */} - {isTableMode && ( -
-
- - { +
+
+ {/* ์ˆœ์„œ ๋ณ€๊ฒฝ ๋ฒ„ํŠผ */} +
+
-
- - + + +
-
-
+ +
+ + {/* ํ…Œ์ด๋ธ” ๋ชจ๋“œ ์ „์šฉ ์˜ต์…˜ */} + {isTableMode && ( +
+
+ + { const newColumns = [...(config.rightPanel?.columns || [])]; newColumns[index] = { ...newColumns[index], - sortable: e.target.checked, + width: parseInt(e.target.value) || 100, }; updateRightPanel({ columns: newColumns }); }} - className="h-3 w-3" + className="h-7 text-xs" /> - ์ •๋ ฌ๊ฐ€๋Šฅ - -
-
- )} - - {/* LIST ๋ชจ๋“œ: ๋ณผ๋“œ ์„ค์ • */} - {!isTableMode && ( -
- - -
- )} - - {/* ๐Ÿ†• ์ˆซ์ž ํƒ€์ž… ํฌ๋งท ์„ค์ • */} - {(() => { - // ์ปฌ๋Ÿผ ํƒ€์ž… ํ™•์ธ - const column = rightTableColumns.find(c => c.columnName === col.name); - const isNumeric = column && ['numeric', 'decimal', 'integer', 'bigint', 'double precision', 'real'].includes(column.dataType?.toLowerCase() || ''); - - if (!isNumeric) return null; - - return ( -
- - -
- {/* ์ฒœ ๋‹จ์œ„ ๊ตฌ๋ถ„์ž */} -
+
+ + +
+
+ - - {/* ์†Œ์ˆ˜์  ์ž๋ฆฟ์ˆ˜ */} -
- - { - const newColumns = [...(config.rightPanel?.columns || [])]; - newColumns[index] = { - ...newColumns[index], - format: { - ...newColumns[index].format, - decimalPlaces: e.target.value ? parseInt(e.target.value) : undefined, - }, - }; - updateRightPanel({ columns: newColumns }); - }} - className="h-7 text-xs" - /> -
- -
- {/* ์ ‘๋‘์‚ฌ */} -
- - { - const newColumns = [...(config.rightPanel?.columns || [])]; - newColumns[index] = { - ...newColumns[index], - format: { - ...newColumns[index].format, - prefix: e.target.value || undefined, - }, - }; - updateRightPanel({ columns: newColumns }); - }} - className="h-7 text-xs" - /> -
- - {/* ์ ‘๋ฏธ์‚ฌ */} -
- - { - const newColumns = [...(config.rightPanel?.columns || [])]; - newColumns[index] = { - ...newColumns[index], - format: { - ...newColumns[index].format, - suffix: e.target.value || undefined, - }, - }; - updateRightPanel({ columns: newColumns }); - }} - className="h-7 text-xs" - /> -
-
- - {/* ๋ฏธ๋ฆฌ๋ณด๊ธฐ */} - {(col.format?.thousandSeparator || col.format?.prefix || col.format?.suffix || col.format?.decimalPlaces !== undefined) && ( +
+ )} + + {/* LIST ๋ชจ๋“œ: ๋ณผ๋“œ ์„ค์ • */} + {!isTableMode && ( +
+ + +
+ )} + + {/* ๐Ÿ†• ๋‚ ์งœ ํƒ€์ž… ํฌ๋งท ์„ค์ • */} + {(() => { + // ์ปฌ๋Ÿผ ํƒ€์ž… ํ™•์ธ (DB ํƒ€์ž… + ์ž…๋ ฅ ํƒ€์ž… ๋ชจ๋‘ ์ฒดํฌ) + const column = rightTableColumns.find((c) => c.columnName === col.name); + const dbDateTypes = [ + "date", + "timestamp", + "timestamptz", + "timestamp with time zone", + "timestamp without time zone", + "time", + "timetz", + ]; + const inputDateTypes = ["date", "datetime", "time"]; + + const isDate = + column && + (dbDateTypes.includes(column.dataType?.toLowerCase() || "") || + inputDateTypes.includes(column.input_type?.toLowerCase() || "") || + inputDateTypes.includes(column.webType?.toLowerCase() || "")); + + if (!isDate) return null; + + return ( +
+ + + {/* ๋ฏธ๋ฆฌ๋ณด๊ธฐ */}
-

๋ฏธ๋ฆฌ๋ณด๊ธฐ:

+

๋ฏธ๋ฆฌ๋ณด๊ธฐ:

- {col.format?.prefix || ''} - {(1234567.89).toLocaleString('ko-KR', { - minimumFractionDigits: col.format?.decimalPlaces ?? 0, - maximumFractionDigits: col.format?.decimalPlaces ?? 10, - useGrouping: col.format?.thousandSeparator ?? false, - })} - {col.format?.suffix || ''} + {(() => { + const now = new Date(); + const format = col.format?.dateFormat || "YYYY-MM-DD"; + if (format === "relative") return "3์ผ ์ „"; + return format + .replace("YYYY", String(now.getFullYear())) + .replace("MM", String(now.getMonth() + 1).padStart(2, "0")) + .replace("DD", String(now.getDate()).padStart(2, "0")) + .replace("HH", String(now.getHours()).padStart(2, "0")) + .replace("mm", String(now.getMinutes()).padStart(2, "0")) + .replace("ss", String(now.getSeconds()).padStart(2, "0")); + })()}

- )} -
- ); - })()} -
- ); +
+ ); + })()} + + {/* ๐Ÿ†• ์ˆซ์ž ํƒ€์ž… ํฌ๋งท ์„ค์ • */} + {(() => { + // ์ปฌ๋Ÿผ ํƒ€์ž… ํ™•์ธ (DB ํƒ€์ž… + ์ž…๋ ฅ ํƒ€์ž… ๋ชจ๋‘ ์ฒดํฌ) + const column = rightTableColumns.find((c) => c.columnName === col.name); + const dbNumericTypes = [ + "numeric", + "decimal", + "integer", + "bigint", + "double precision", + "real", + "smallint", + "int4", + "int8", + "float4", + "float8", + ]; + const inputNumericTypes = ["number", "decimal", "currency", "integer"]; + + const isNumeric = + column && + (dbNumericTypes.includes(column.dataType?.toLowerCase() || "") || + inputNumericTypes.includes(column.input_type?.toLowerCase() || "") || + inputNumericTypes.includes(column.webType?.toLowerCase() || "")); + + if (!isNumeric) return null; + + return ( +
+ + +
+ {/* ์ฒœ ๋‹จ์œ„ ๊ตฌ๋ถ„์ž */} + + + {/* ์†Œ์ˆ˜์  ์ž๋ฆฟ์ˆ˜ */} +
+ + { + const newColumns = [...(config.rightPanel?.columns || [])]; + newColumns[index] = { + ...newColumns[index], + format: { + ...newColumns[index].format, + decimalPlaces: e.target.value ? parseInt(e.target.value) : undefined, + }, + }; + updateRightPanel({ columns: newColumns }); + }} + className="h-7 text-xs" + /> +
+
+ +
+ {/* ์ ‘๋‘์‚ฌ */} +
+ + { + const newColumns = [...(config.rightPanel?.columns || [])]; + newColumns[index] = { + ...newColumns[index], + format: { + ...newColumns[index].format, + prefix: e.target.value || undefined, + }, + }; + updateRightPanel({ columns: newColumns }); + }} + className="h-7 text-xs" + /> +
+ + {/* ์ ‘๋ฏธ์‚ฌ */} +
+ + { + const newColumns = [...(config.rightPanel?.columns || [])]; + newColumns[index] = { + ...newColumns[index], + format: { + ...newColumns[index].format, + suffix: e.target.value || undefined, + }, + }; + updateRightPanel({ columns: newColumns }); + }} + className="h-7 text-xs" + /> +
+
+ + {/* ๋ฏธ๋ฆฌ๋ณด๊ธฐ */} + {(col.format?.thousandSeparator || + col.format?.prefix || + col.format?.suffix || + col.format?.decimalPlaces !== undefined) && ( +
+

๋ฏธ๋ฆฌ๋ณด๊ธฐ:

+

+ {col.format?.prefix || ""} + {(1234567.89).toLocaleString("ko-KR", { + minimumFractionDigits: col.format?.decimalPlaces ?? 0, + maximumFractionDigits: col.format?.decimalPlaces ?? 10, + useGrouping: col.format?.thousandSeparator ?? false, + })} + {col.format?.suffix || ""} +

+
+ )} +
+ ); + })()} +
+ ); }) )}
@@ -2055,10 +2353,7 @@ export const SplitPanelLayoutConfigPanel: React.FC { const currentColumns = config.rightPanel?.addModalColumns || []; - const newColumns = [ - ...currentColumns, - { name: "", label: "", required: false }, - ]; + const newColumns = [...currentColumns, { name: "", label: "", required: false }]; updateRightPanel({ addModalColumns: newColumns }); }} className="h-7 text-xs" @@ -2068,9 +2363,7 @@ export const SplitPanelLayoutConfigPanel: React.FC
-

- ์ถ”๊ฐ€ ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ๋ชจ๋‹ฌ์— ํ‘œ์‹œ๋  ์ž…๋ ฅ ํ•„๋“œ๋ฅผ ์„ ํƒํ•˜์„ธ์š” -

+

์ถ”๊ฐ€ ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ๋ชจ๋‹ฌ์— ํ‘œ์‹œ๋  ์ž…๋ ฅ ํ•„๋“œ๋ฅผ ์„ ํƒํ•˜์„ธ์š”

{(config.rightPanel?.addModalColumns || []).length === 0 ? ( @@ -2080,120 +2373,114 @@ export const SplitPanelLayoutConfigPanel: React.FC { // ํ˜„์žฌ ์ปฌ๋Ÿผ์ด PK์ธ์ง€ ํ™•์ธ - const column = rightTableColumns.find(c => c.columnName === col.name); + const column = rightTableColumns.find((c) => c.columnName === col.name); const isPK = column?.isPrimaryKey || false; - + return ( -
- {isPK && ( - - PK - - )} -
- - - - - - - - ์ปฌ๋Ÿผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. - - {rightTableColumns - .filter((column) => !['company_code', 'company_name'].includes(column.columnName)) - .map((column) => ( - { - const newColumns = [...(config.rightPanel?.addModalColumns || [])]; - newColumns[index] = { - ...newColumns[index], - name: value, - label: column.columnLabel || value, - }; - updateRightPanel({ addModalColumns: newColumns }); - }} - className="text-xs" - > - - {column.columnLabel || column.columnName} - - ({column.columnName}) - - - ))} - - - - -
-
- -
- -
- ); + {isPK && ( + + PK + + )} +
+ + + + + + + + ์ปฌ๋Ÿผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. + + {rightTableColumns + .filter((column) => !["company_code", "company_name"].includes(column.columnName)) + .map((column) => ( + { + const newColumns = [...(config.rightPanel?.addModalColumns || [])]; + newColumns[index] = { + ...newColumns[index], + name: value, + label: column.columnLabel || value, + }; + updateRightPanel({ addModalColumns: newColumns }); + }} + className="text-xs" + > + + {column.columnLabel || column.columnName} + ({column.columnName}) + + ))} + + + + +
+
+ +
+ +
+ ); }) )}
{/* ์ค‘๊ณ„ ํ…Œ์ด๋ธ” ์„ค์ • */} -
+
-

- ์ค‘๊ณ„ ํ…Œ์ด๋ธ”์„ ์‚ฌ์šฉํ•˜์—ฌ ๋‹ค๋Œ€๋‹ค ๊ด€๊ณ„๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค -

+

์ค‘๊ณ„ ํ…Œ์ด๋ธ”์„ ์‚ฌ์šฉํ•˜์—ฌ ๋‹ค๋Œ€๋‹ค ๊ด€๊ณ„๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค

@@ -2211,9 +2498,7 @@ export const SplitPanelLayoutConfigPanel: React.FC -

- ๋ฐ์ดํ„ฐ๊ฐ€ ์‹ค์ œ๋กœ ์ €์žฅ๋  ์ค‘๊ณ„ ํ…Œ์ด๋ธ”๋ช… -

+

๋ฐ์ดํ„ฐ๊ฐ€ ์‹ค์ œ๋กœ ์ €์žฅ๋  ์ค‘๊ณ„ ํ…Œ์ด๋ธ”๋ช…

@@ -2232,9 +2517,7 @@ export const SplitPanelLayoutConfigPanel: React.FC -

- ์ขŒ์ธก ํŒจ๋„์—์„œ ์„ ํƒํ•œ ํ•ญ๋ชฉ์˜ ์–ด๋–ค ์ปฌ๋Ÿผ๊ฐ’์„ ๊ฐ€์ ธ์˜ฌ์ง€ -

+

์ขŒ์ธก ํŒจ๋„์—์„œ ์„ ํƒํ•œ ํ•ญ๋ชฉ์˜ ์–ด๋–ค ์ปฌ๋Ÿผ๊ฐ’์„ ๊ฐ€์ ธ์˜ฌ์ง€

@@ -2253,9 +2536,7 @@ export const SplitPanelLayoutConfigPanel: React.FC -

- ์ค‘๊ณ„ ํ…Œ์ด๋ธ”์˜ ์–ด๋–ค ์ปฌ๋Ÿผ์— ์ขŒ์ธก๊ฐ’์„ ์ €์žฅํ• ์ง€ -

+

์ค‘๊ณ„ ํ…Œ์ด๋ธ”์˜ ์–ด๋–ค ์ปฌ๋Ÿผ์— ์ขŒ์ธก๊ฐ’์„ ์ €์žฅํ• ์ง€

@@ -2277,11 +2558,9 @@ export const SplitPanelLayoutConfigPanel: React.FC -

- ์ž๋™์œผ๋กœ ์ฑ„์›Œ์งˆ ์ปฌ๋Ÿผ๊ณผ ๊ธฐ๋ณธ๊ฐ’ (์˜ˆ: is_primary: false) -

+

์ž๋™์œผ๋กœ ์ฑ„์›Œ์งˆ ์ปฌ๋Ÿผ๊ณผ ๊ธฐ๋ณธ๊ฐ’ (์˜ˆ: is_primary: false)

@@ -2289,33 +2568,32 @@ export const SplitPanelLayoutConfigPanel: React.FC {/* ์šฐ์ธก ํŒจ๋„ ๋ฐ์ดํ„ฐ ํ•„ํ„ฐ๋ง */} -
+

์šฐ์ธก ํŒจ๋„ ๋ฐ์ดํ„ฐ ํ•„ํ„ฐ๋ง

-

- ํŠน์ • ์ปฌ๋Ÿผ ๊ฐ’์œผ๋กœ ์šฐ์ธก ํŒจ๋„ ๋ฐ์ดํ„ฐ๋ฅผ ํ•„ํ„ฐ๋งํ•ฉ๋‹ˆ๋‹ค -

- ({ - columnName: col.columnName, - columnLabel: col.columnLabel || col.columnName, - dataType: col.dataType || "text", - input_type: (col as any).input_type, - } as any))} - config={config.rightPanel?.dataFilter} - onConfigChange={(dataFilter) => updateRightPanel({ dataFilter })} - menuObjid={menuObjid} // ๐Ÿ†• ๋ฉ”๋‰ด OBJID ์ „๋‹ฌ - /> +

ํŠน์ • ์ปฌ๋Ÿผ ๊ฐ’์œผ๋กœ ์šฐ์ธก ํŒจ๋„ ๋ฐ์ดํ„ฐ๋ฅผ ํ•„ํ„ฐ๋งํ•ฉ๋‹ˆ๋‹ค

+ + ({ + columnName: col.columnName, + columnLabel: col.columnLabel || col.columnName, + dataType: col.dataType || "text", + input_type: (col as any).input_type, + }) as any, + )} + config={config.rightPanel?.dataFilter} + onConfigChange={(dataFilter) => updateRightPanel({ dataFilter })} + menuObjid={menuObjid} // ๐Ÿ†• ๋ฉ”๋‰ด OBJID ์ „๋‹ฌ + />
{/* ์šฐ์ธก ํŒจ๋„ ์ค‘๋ณต ์ œ๊ฑฐ */} -
+

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

-

- ๊ฐ™์€ ๊ฐ’์„ ๊ฐ€์ง„ ๋ฐ์ดํ„ฐ๋ฅผ ํ•˜๋‚˜๋กœ ํ†ตํ•ฉํ•˜์—ฌ ํ‘œ์‹œ -

+

๊ฐ™์€ ๊ฐ’์„ ๊ฐ€์ง„ ๋ฐ์ดํ„ฐ๋ฅผ ํ•˜๋‚˜๋กœ ํ†ตํ•ฉํ•˜์—ฌ ํ‘œ์‹œ

{config.rightPanel?.deduplication?.enabled && ( -
+
{/* ์ค‘๋ณต ์ œ๊ฑฐ ๊ธฐ์ค€ ์ปฌ๋Ÿผ */}
@@ -2360,7 +2638,7 @@ export const SplitPanelLayoutConfigPanel: React.FC -

+

์ด ์ปฌ๋Ÿผ์˜ ๊ฐ’์ด ๊ฐ™์€ ๋ฐ์ดํ„ฐ๋“ค ์ค‘ ํ•˜๋‚˜๋งŒ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค

@@ -2386,11 +2664,15 @@ export const SplitPanelLayoutConfigPanel: React.FC๊ธฐ์ค€๋‹จ๊ฐ€๋กœ ์„ค์ •๋œ ๋ฐ์ดํ„ฐ -

- {config.rightPanel?.deduplication?.keepStrategy === "latest" && "๊ฐ€์žฅ ์ตœ๊ทผ์— ์ถ”๊ฐ€๋œ ๋ฐ์ดํ„ฐ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค"} - {config.rightPanel?.deduplication?.keepStrategy === "earliest" && "๊ฐ€์žฅ ๋จผ์ € ์ถ”๊ฐ€๋œ ๋ฐ์ดํ„ฐ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค"} - {config.rightPanel?.deduplication?.keepStrategy === "current_date" && "์˜ค๋Š˜ ๋‚ ์งœ ๊ธฐ์ค€์œผ๋กœ ์œ ํšจํ•œ ๊ธฐ๊ฐ„์˜ ๋ฐ์ดํ„ฐ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค"} - {config.rightPanel?.deduplication?.keepStrategy === "base_price" && "๊ธฐ์ค€๋‹จ๊ฐ€(base_price)๋กœ ์ฒดํฌ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค"} +

+ {config.rightPanel?.deduplication?.keepStrategy === "latest" && + "๊ฐ€์žฅ ์ตœ๊ทผ์— ์ถ”๊ฐ€๋œ ๋ฐ์ดํ„ฐ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค"} + {config.rightPanel?.deduplication?.keepStrategy === "earliest" && + "๊ฐ€์žฅ ๋จผ์ € ์ถ”๊ฐ€๋œ ๋ฐ์ดํ„ฐ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค"} + {config.rightPanel?.deduplication?.keepStrategy === "current_date" && + "์˜ค๋Š˜ ๋‚ ์งœ ๊ธฐ์ค€์œผ๋กœ ์œ ํšจํ•œ ๊ธฐ๊ฐ„์˜ ๋ฐ์ดํ„ฐ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค"} + {config.rightPanel?.deduplication?.keepStrategy === "base_price" && + "๊ธฐ์ค€๋‹จ๊ฐ€(base_price)๋กœ ์ฒดํฌ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค"}

@@ -2412,7 +2694,10 @@ export const SplitPanelLayoutConfigPanel: React.FC {rightTableColumns - .filter(col => col.dataType === 'date' || col.dataType === 'timestamp' || col.columnName.includes('date')) + .filter( + (col) => + col.dataType === "date" || col.dataType === "timestamp" || col.columnName.includes("date"), + ) .map((col) => ( {col.columnLabel || col.columnName} @@ -2420,7 +2705,7 @@ export const SplitPanelLayoutConfigPanel: React.FC -

+

์ด ์ปฌ๋Ÿผ์˜ ๊ฐ’์œผ๋กœ ์ตœ์‹ /์ตœ์ดˆ๋ฅผ ํŒ๋‹จํ•ฉ๋‹ˆ๋‹ค (๋ณดํ†ต ๋‚ ์งœ ์ปฌ๋Ÿผ)

@@ -2430,13 +2715,11 @@ export const SplitPanelLayoutConfigPanel: React.FC {/* ๐Ÿ†• ์šฐ์ธก ํŒจ๋„ ์ˆ˜์ • ๋ฒ„ํŠผ ์„ค์ • */} -
+

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

-

- ์šฐ์ธก ๋ฆฌ์ŠคํŠธ์˜ ์ˆ˜์ • ๋ฒ„ํŠผ ๋™์ž‘ ๋ฐฉ์‹ ์„ค์ • -

+

์šฐ์ธก ๋ฆฌ์ŠคํŠธ์˜ ์ˆ˜์ • ๋ฒ„ํŠผ ๋™์ž‘ ๋ฐฉ์‹ ์„ค์ •

{(config.rightPanel?.editButton?.enabled ?? true) && ( -
+
{/* ์ˆ˜์ • ๋ชจ๋“œ */}
@@ -2478,7 +2761,7 @@ export const SplitPanelLayoutConfigPanel: React.FC์ปค์Šคํ…€ ๋ชจ๋‹ฌ ํ™”๋ฉด -

+

{config.rightPanel?.editButton?.mode === "modal" ? "์ง€์ •ํ•œ ํ™”๋ฉด์„ ๋ชจ๋‹ฌ๋กœ ์—ด์–ด ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค" : "ํ˜„์žฌ ์œ„์น˜์—์„œ ์ง์ ‘ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค"} @@ -2500,9 +2783,7 @@ export const SplitPanelLayoutConfigPanel: React.FC -

- ์ˆ˜์ • ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ์—ด๋ฆด ํ™”๋ฉด์„ ์„ ํƒํ•˜์„ธ์š” -

+

์ˆ˜์ • ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ์—ด๋ฆด ํ™”๋ฉด์„ ์„ ํƒํ•˜์„ธ์š”

)} @@ -2574,9 +2855,100 @@ export const SplitPanelLayoutConfigPanel: React.FC - {/* ๋ ˆ์ด์•„์›ƒ ์„ค์ • */} -
+ {/* ๐Ÿ†• ์šฐ์ธก ํŒจ๋„ ์‚ญ์ œ ๋ฒ„ํŠผ ์„ค์ • */} +
+
+
+

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

+
+ { + updateRightPanel({ + deleteButton: { + enabled: checked, + buttonLabel: config.rightPanel?.deleteButton?.buttonLabel, + buttonVariant: config.rightPanel?.deleteButton?.buttonVariant, + }, + }); + }} + /> +
+ {(config.rightPanel?.deleteButton?.enabled ?? true) && ( +
+
+ {/* ๋ฒ„ํŠผ ๋ผ๋ฒจ */} +
+ + { + updateRightPanel({ + deleteButton: { + ...config.rightPanel?.deleteButton!, + buttonLabel: e.target.value || undefined, + enabled: config.rightPanel?.deleteButton?.enabled ?? true, + }, + }); + }} + className="h-8 text-xs" + /> +
+ + {/* ๋ฒ„ํŠผ ์Šคํƒ€์ผ */} +
+ + +
+
+ + {/* ์‚ญ์ œ ํ™•์ธ ๋ฉ”์‹œ์ง€ */} +
+ + { + updateRightPanel({ + deleteButton: { + ...config.rightPanel?.deleteButton!, + confirmMessage: e.target.value || undefined, + enabled: config.rightPanel?.deleteButton?.enabled ?? true, + }, + }); + }} + className="h-8 text-xs" + /> +
+
+ )} +
+ + {/* ๋ ˆ์ด์•„์›ƒ ์„ค์ • */} +
; }; // ์šฐ์ธก ํŒจ๋„ ์ถ”๊ฐ€ ์‹œ ์ค‘๊ณ„ ํ…Œ์ด๋ธ” ์„ค์ • (N:M ๊ด€๊ณ„) @@ -117,7 +122,7 @@ export interface SplitPanelLayoutConfig { leftPanelColumn?: string; // ์ขŒ์ธก ํŒจ๋„์˜ ์–ด๋–ค ์ปฌ๋Ÿผ๊ฐ’์„ ๊ฐ€์ ธ์˜ฌ์ง€ targetColumn?: string; // targetTable์˜ ์–ด๋–ค ์ปฌ๋Ÿผ์— ๋„ฃ์„์ง€ }; - + // ํ…Œ์ด๋ธ” ๋ชจ๋“œ ์„ค์ • tableConfig?: { showCheckbox?: boolean; // ์ฒดํฌ๋ฐ•์Šค ํ‘œ์‹œ ์—ฌ๋ถ€ @@ -150,6 +155,14 @@ export interface SplitPanelLayoutConfig { buttonVariant?: "default" | "outline" | "ghost"; // ๋ฒ„ํŠผ ์Šคํƒ€์ผ (๊ธฐ๋ณธ: "outline") groupByColumns?: string[]; // ๐Ÿ†• ๊ทธ๋ฃนํ•‘ ๊ธฐ์ค€ ์ปฌ๋Ÿผ๋“ค (์˜ˆ: ["customer_id", "item_id"]) }; + + // ๐Ÿ†• ์‚ญ์ œ ๋ฒ„ํŠผ ์„ค์ • + deleteButton?: { + enabled: boolean; // ์‚ญ์ œ ๋ฒ„ํŠผ ํ‘œ์‹œ ์—ฌ๋ถ€ (๊ธฐ๋ณธ: true) + buttonLabel?: string; // ๋ฒ„ํŠผ ๋ผ๋ฒจ (๊ธฐ๋ณธ: "์‚ญ์ œ") + buttonVariant?: "default" | "outline" | "ghost" | "destructive"; // ๋ฒ„ํŠผ ์Šคํƒ€์ผ (๊ธฐ๋ณธ: "ghost") + confirmMessage?: string; // ์‚ญ์ œ ํ™•์ธ ๋ฉ”์‹œ์ง€ + }; }; // ๋ ˆ์ด์•„์›ƒ ์„ค์ •