diff --git a/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutConfigPanel.tsx b/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutConfigPanel.tsx index 5ca50ffb..49a3e3ab 100644 --- a/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutConfigPanel.tsx +++ b/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutConfigPanel.tsx @@ -11,7 +11,7 @@ import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, Command import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Button } from "@/components/ui/button"; // Accordion 제거 - 단순 섹션으로 변경 -import { Check, ChevronsUpDown, ArrowRight, Plus, X, ArrowUp, ArrowDown } from "lucide-react"; +import { Check, ChevronsUpDown, X, Database, GripVertical } from "lucide-react"; import { cn } from "@/lib/utils"; import { SplitPanelLayoutConfig } from "./types"; import { TableInfo, ColumnInfo } from "@/types/screen"; @@ -200,8 +200,6 @@ export const SplitPanelLayoutConfigPanel: React.FC { const [rightTableOpen, setRightTableOpen] = useState(false); - const [leftColumnOpen, setLeftColumnOpen] = useState(false); - const [rightColumnOpen, setRightColumnOpen] = useState(false); const [loadedTableColumns, setLoadedTableColumns] = useState>({}); const [loadingColumns, setLoadingColumns] = useState>({}); const [allTables, setAllTables] = useState([]); // 조인 모드용 전체 테이블 목록 @@ -262,61 +260,6 @@ export const SplitPanelLayoutConfigPanel: React.FC { - const leftTableName = config.leftPanel?.tableName || screenTableName; - if (leftTableName && loadedTableColumns[leftTableName] && config.leftPanel?.showAdd) { - const currentAddModalColumns = config.leftPanel?.addModalColumns || []; - const updatedColumns = ensurePrimaryKeysInAddModal(leftTableName, currentAddModalColumns); - - // PK가 추가되었으면 업데이트 - if (updatedColumns.length !== currentAddModalColumns.length) { - console.log(`🔄 좌측 패널: PK 컬럼 자동 추가 (${leftTableName})`); - updateLeftPanel({ addModalColumns: updatedColumns }); - } - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [config.leftPanel?.tableName, screenTableName, loadedTableColumns, config.leftPanel?.showAdd]); - - // 좌측 패널 하위 항목 추가 모달 PK 자동 추가 - useEffect(() => { - const leftTableName = config.leftPanel?.tableName || screenTableName; - if (leftTableName && loadedTableColumns[leftTableName] && config.leftPanel?.showItemAddButton) { - const currentAddModalColumns = config.leftPanel?.itemAddConfig?.addModalColumns || []; - const updatedColumns = ensurePrimaryKeysInAddModal(leftTableName, currentAddModalColumns); - - // PK가 추가되었으면 업데이트 - if (updatedColumns.length !== currentAddModalColumns.length) { - console.log(`🔄 좌측 패널 하위 항목 추가: PK 컬럼 자동 추가 (${leftTableName})`); - updateLeftPanel({ - itemAddConfig: { - ...config.leftPanel?.itemAddConfig, - addModalColumns: updatedColumns, - parentColumn: config.leftPanel?.itemAddConfig?.parentColumn || "", - sourceColumn: config.leftPanel?.itemAddConfig?.sourceColumn || "", - }, - }); - } - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [config.leftPanel?.tableName, screenTableName, loadedTableColumns, config.leftPanel?.showItemAddButton]); - - // 우측 패널 테이블 컬럼 로드 완료 시 PK 자동 추가 - useEffect(() => { - const rightTableName = config.rightPanel?.tableName; - if (rightTableName && loadedTableColumns[rightTableName] && config.rightPanel?.showAdd) { - const currentAddModalColumns = config.rightPanel?.addModalColumns || []; - const updatedColumns = ensurePrimaryKeysInAddModal(rightTableName, currentAddModalColumns); - - // PK가 추가되었으면 업데이트 - if (updatedColumns.length !== currentAddModalColumns.length) { - console.log(`🔄 우측 패널: PK 컬럼 자동 추가 (${rightTableName})`); - updateRightPanel({ addModalColumns: updatedColumns }); - } - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [config.rightPanel?.tableName, loadedTableColumns, config.rightPanel?.showAdd]); - // 테이블 컬럼 로드 함수 const loadTableColumns = async (tableName: string) => { if (loadedTableColumns[tableName] || loadingColumns[tableName]) { @@ -443,50 +386,6 @@ export const SplitPanelLayoutConfigPanel: React.FC = [], - ) => { - const tableColumns = loadedTableColumns[tableName]; - if (!tableColumns) { - console.warn(`⚠️ 테이블 ${tableName}의 컬럼 정보가 로드되지 않음`); - return existingColumns; - } - - // PK 컬럼 찾기 - const pkColumns = tableColumns.filter((col) => col.isPrimaryKey); - console.log( - `🔑 테이블 ${tableName}의 PK 컬럼:`, - pkColumns.map((c) => c.columnName), - ); - - // 자동으로 처리되는 컬럼 (백엔드에서 자동 추가) - const autoHandledColumns = ["company_code", "company_name"]; - - // 기존 컬럼 이름 목록 - const existingColumnNames = existingColumns.map((col) => col.name); - - // PK 컬럼을 맨 앞에 추가 (이미 있거나 자동 처리되는 컬럼은 제외) - const pkColumnsToAdd = pkColumns - .filter((col) => !existingColumnNames.includes(col.columnName)) - .filter((col) => !autoHandledColumns.includes(col.columnName)) // 자동 처리 컬럼 제외 - .map((col) => ({ - name: col.columnName, - label: col.columnLabel || col.columnName, - required: true, // PK는 항상 필수 - })); - - if (pkColumnsToAdd.length > 0) { - console.log( - `✅ PK 컬럼 ${pkColumnsToAdd.length}개 자동 추가:`, - pkColumnsToAdd.map((c) => c.name), - ); - } - - return [...pkColumnsToAdd, ...existingColumns]; - }; - const updateLeftPanel = (updates: Partial) => { const newConfig = { ...config, @@ -612,17 +511,7 @@ export const SplitPanelLayoutConfigPanel: React.FC -

패널 상단 헤더의 높이 (기본: 48px)

- - -
- -
-

- {config.leftPanel?.tableName || screenTableName || "테이블이 지정되지 않음"} -

-

좌측 패널은 현재 화면의 테이블 데이터를 표시합니다

-
+

패널 상단 헤더의 높이 (기본: 48px)

@@ -651,811 +540,168 @@ export const SplitPanelLayoutConfigPanel: React.FC
-
- - updateLeftPanel({ showSearch: checked })} - /> -
+ {/* 좌측 패널 표시 컬럼 설정 - 체크박스 방식 */} +
+ -
- - updateLeftPanel({ showAdd: checked })} - /> -
- -
- - updateLeftPanel({ showItemAddButton: checked })} - /> -
- - {/* 항목별 + 버튼 설정 (하위 항목 추가) */} - {config.leftPanel?.showItemAddButton && ( -
- -

- + 버튼 클릭 시 선택된 항목의 하위 항목을 추가합니다 (예: 부서 → 하위 부서) -

- - {/* 현재 항목의 값을 가져올 컬럼 (sourceColumn) */} -
- -

선택된 항목의 어떤 컬럼 값을 사용할지 (예: dept_code)

- - - - - - - - 컬럼을 찾을 수 없습니다. - - {leftTableColumns - .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}) - - ))} - - - - -
- - {/* 상위 항목 ID를 저장할 컬럼 (parentColumn) */} -
- -

- 하위 항목에서 상위 항목 ID를 저장할 컬럼 (예: parent_dept_code) -

- - - - - - - - 컬럼을 찾을 수 없습니다. - - {leftTableColumns - .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}) - - ))} - - - - -
- - {/* 하위 항목 추가 모달 컬럼 설정 */} -
-
- - -
-

하위 항목 추가 시 입력받을 필드를 선택하세요

- -
- {(config.leftPanel?.itemAddConfig?.addModalColumns || []).length === 0 ? ( -
-

설정된 컬럼이 없습니다

-
- ) : ( - (config.leftPanel?.itemAddConfig?.addModalColumns || []).map((col, index) => { - 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?.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}) - - ))} - - - - -
-
- -
- -
- ); - }) - )} -
-
-
- )} - - {/* 좌측 패널 표시 컬럼 설정 */} -
-
- - -
-

- 좌측 패널에 표시할 컬럼을 선택하세요. 선택하지 않으면 모든 컬럼이 표시됩니다. -

- - {/* 선택된 컬럼 목록 */} -
- {(config.leftPanel?.columns || []).length === 0 ? ( -
-

설정된 컬럼이 없습니다

-

컬럼을 추가하지 않으면 모든 컬럼이 표시됩니다

-
+ {/* 컬럼 체크박스 목록 */} +
+ {leftTableColumns.length === 0 ? ( +

컬럼 로딩 중...

) : ( - (config.leftPanel?.columns || []).map((col, index) => { - const isTableMode = config.leftPanel?.displayMode === "table"; - - return ( -
-
- {/* 순서 변경 버튼 */} -
- - -
+ } else { + const newColumn = { + name: column.columnName, + label: column.columnLabel || column.columnName, + width: 100, + }; + updateLeftPanel({ columns: [...currentColumns, newColumn] }); + } + }} + className="pointer-events-none h-3.5 w-3.5 shrink-0" + /> + + {column.columnLabel || column.columnName} +
+ ); + }) + )} +
-
- - - - - - - - 컬럼을 찾을 수 없습니다. -
- {/* 기본 테이블 컬럼 */} - - {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}) - - ))} - + {/* 선택된 컬럼 상세 설정 */} + {(config.leftPanel?.columns || []).length > 0 && ( +
+ +
+ {(config.leftPanel?.columns || []).map((col, index) => { + const column = leftTableColumns.find((c) => c.columnName === col.name); + const isTableMode = config.leftPanel?.displayMode === "table"; - {/* 🆕 엔티티 참조 테이블 컬럼 */} - {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="pl-6 text-xs" - > - - {column.columnLabel || column.columnName} - - ({column.columnName}) - - - ); - })} - - ))} -
- - - -
+ // 숫자 타입 판별 + 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() || "")); + + return ( +
+ + + { + const newColumns = [...(config.leftPanel?.columns || [])]; + newColumns[index] = { ...newColumns[index], label: e.target.value }; + updateLeftPanel({ columns: newColumns }); + }} + placeholder="제목" + className="h-6 flex-1 text-xs" + /> + { + const newColumns = [...(config.leftPanel?.columns || [])]; + newColumns[index] = { ...newColumns[index], width: parseInt(e.target.value) || 100 }; + updateLeftPanel({ columns: newColumns }); + }} + placeholder="너비" + className="h-6 w-14 text-xs" + /> + {/* 숫자 타입: 천단위 구분자 체크박스 */} + {isNumeric && ( + + )} -
- - {/* 테이블 모드 전용 옵션 */} - {isTableMode && ( -
-
- - { - const newColumns = [...(config.leftPanel?.columns || [])]; - newColumns[index] = { - ...newColumns[index], - width: parseInt(e.target.value) || 100, - }; - updateLeftPanel({ columns: newColumns }); - }} - 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" - /> -
-
-
- ); - })()} -
- ); - }) - )} -
-
- - {/* 좌측 패널 추가 모달 컬럼 설정 */} - {config.leftPanel?.showAdd && ( -
-
- - -
-

추가 버튼 클릭 시 모달에 표시될 입력 필드를 선택하세요

- -
- {(config.leftPanel?.addModalColumns || []).length === 0 ? ( -
-

설정된 컬럼이 없습니다

-
- ) : ( - (config.leftPanel?.addModalColumns || []).map((col, index) => { - // 현재 컬럼이 PK인지 확인 - 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}) - - ))} - - - - -
-
- -
-
); - }) - )} + })} +
-
- )} + )} +
{/* 좌측 패널 데이터 필터링 */} @@ -1509,7 +755,7 @@ export const SplitPanelLayoutConfigPanel: React.FC -

패널 상단 헤더의 높이 (기본: 48px)

+

패널 상단 헤더의 높이 (기본: 48px)

{/* 관계 타입에 따라 테이블 선택 UI 변경 */} @@ -1633,964 +879,224 @@ export const SplitPanelLayoutConfigPanel: React.FC )} - {/* 컬럼 매핑 - 조인 모드에서만 표시 */} + {/* 엔티티 설정 선택 - 조인 모드에서만 표시 */} {relationshipType !== "detail" && ( -
-
-
- -

좌측 테이블의 컬럼을 우측 테이블의 컬럼과 연결합니다

-
- -
- -

복합키: 여러 컬럼으로 조인 (예: item_code + lot_number)

- - {/* 복합키가 설정된 경우 */} - {(config.rightPanel?.relation?.keys || []).length > 0 ? ( - <> - {(config.rightPanel?.relation?.keys || []).map((key, index) => ( -
-
- 조인 키 {index + 1} - -
-
-
- - +
+ +

+ 우측 테이블에서 좌측 테이블을 참조하는 엔티티 컬럼을 선택하세요 +

+ { - const newKeys = [...(config.rightPanel?.relation?.keys || [])]; - newKeys[index] = { ...newKeys[index], rightColumn: value }; - updateRightPanel({ - relation: { ...config.rightPanel?.relation, keys: newKeys }, - }); - }} - > - - - - - {rightTableColumns.map((column) => ( - - {column.columnName} - - ))} - - -
-
+ + ))} + {rightTableColumns.filter((col) => { + const inputType = col.input_type?.toLowerCase() || col.webType?.toLowerCase() || ""; + return inputType === "entity" || inputType === "code"; + }).length === 0 && ( +
+ 엔티티 타입 컬럼이 없습니다. +
+ 테이블 타입관리에서 엔티티 설정을 추가하세요.
- ))} - - ) : ( - /* 단일키 (하위 호환성) */ - <> -
- - - - - - - - - 컬럼을 찾을 수 없습니다. - - {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 || ""}) - - ))} - - - - -
- + )} + + + {config.rightPanel?.relation?.foreignKey && ( +

선택된 컬럼의 엔티티 설정이 자동으로 적용됩니다.

)}
)} -
- - updateRightPanel({ showSearch: checked })} - /> -
+ {/* 우측 패널 표시 컬럼 설정 - 체크박스 방식 */} +
+ -
- - updateRightPanel({ showAdd: checked })} - /> -
- - {/* 우측 패널 표시 컬럼 설정 */} -
-
- - -
-

- 우측 패널에 표시할 컬럼을 선택하세요. 선택하지 않으면 모든 컬럼이 표시됩니다. -

- - {/* 선택된 컬럼 목록 */} -
- {(config.rightPanel?.columns || []).length === 0 ? ( -
-

설정된 컬럼이 없습니다

-

컬럼을 추가하지 않으면 모든 컬럼이 표시됩니다

-
+ {/* 컬럼 체크박스 목록 */} +
+ {rightTableColumns.length === 0 ? ( +

테이블을 선택해주세요

) : ( - (config.rightPanel?.columns || []).map((col, index) => { - const isTableMode = config.rightPanel?.displayMode === "table"; - - return ( -
-
- {/* 순서 변경 버튼 */} -
- - -
+ } else { + const newColumn = { + name: column.columnName, + label: column.columnLabel || column.columnName, + width: 100, + }; + updateRightPanel({ columns: [...currentColumns, newColumn] }); + } + }} + className="pointer-events-none h-3.5 w-3.5 shrink-0" + /> + + {column.columnLabel || column.columnName} +
+ ); + }) + )} +
-
- - - - - - - - 컬럼을 찾을 수 없습니다. -
- {/* 기본 테이블 컬럼 */} - - {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}) - - ))} - + {/* 선택된 컬럼 상세 설정 */} + {(config.rightPanel?.columns || []).length > 0 && ( +
+ +
+ {(config.rightPanel?.columns || []).map((col, index) => { + const column = rightTableColumns.find((c) => c.columnName === col.name); - {/* 🆕 엔티티 참조 테이블 컬럼 */} - {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="pl-6 text-xs" - > - - {column.columnLabel || column.columnName} - - ({column.columnName}) - - - ); - })} - - ))} -
- - - -
- -
- - {/* 테이블 모드 전용 옵션 */} - {isTableMode && ( -
-
- - { - const newColumns = [...(config.rightPanel?.columns || [])]; - newColumns[index] = { - ...newColumns[index], - width: parseInt(e.target.value) || 100, - }; - updateRightPanel({ columns: newColumns }); - }} - className="h-7 text-xs" - /> -
-
- - -
-
- -
-
- )} - - {/* 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 ( -
- - - {/* 미리보기 */} -
-

미리보기:

-

- {(() => { - 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 || ""} -

-
- )} -
- ); - })()} -
- ); - }) - )} -
-
- - {/* 우측 패널 추가 모달 컬럼 설정 */} - {config.rightPanel?.showAdd && ( -
-
- - -
-

추가 버튼 클릭 시 모달에 표시될 입력 필드를 선택하세요

- -
- {(config.rightPanel?.addModalColumns || []).length === 0 ? ( -
-

설정된 컬럼이 없습니다

-
- ) : ( - (config.rightPanel?.addModalColumns || []).map((col, index) => { - // 현재 컬럼이 PK인지 확인 - 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}) - - ))} - - - - -
-
- -
+ )}
); - }) - )} -
- - {/* 중계 테이블 설정 */} -
- -

중계 테이블을 사용하여 다대다 관계를 구현합니다

- -
- - { - const addConfig = config.rightPanel?.addConfig || {}; - updateRightPanel({ - addConfig: { - ...addConfig, - targetTable: e.target.value, - }, - }); - }} - placeholder="예: user_dept" - className="mt-1 h-8 text-xs" - /> -

데이터가 실제로 저장될 중계 테이블명

-
- -
- - { - const addConfig = config.rightPanel?.addConfig || {}; - updateRightPanel({ - addConfig: { - ...addConfig, - leftPanelColumn: e.target.value, - }, - }); - }} - placeholder="예: dept_code" - className="mt-1 h-8 text-xs" - /> -

좌측 패널에서 선택한 항목의 어떤 컬럼값을 가져올지

-
- -
- - { - const addConfig = config.rightPanel?.addConfig || {}; - updateRightPanel({ - addConfig: { - ...addConfig, - targetColumn: e.target.value, - }, - }); - }} - placeholder="예: dept_code" - className="mt-1 h-8 text-xs" - /> -

중계 테이블의 어떤 컬럼에 좌측값을 저장할지

-
- -
- -