Merge branch 'dev' of http://39.117.244.52:3000/kjs/ERP-node into feature/screen-management
This commit is contained in:
commit
eb6fa71cf4
|
|
@ -282,15 +282,13 @@ export async function getTableLabels(
|
|||
const tableLabels = await tableManagementService.getTableLabels(tableName);
|
||||
|
||||
if (!tableLabels) {
|
||||
const response: ApiResponse<null> = {
|
||||
success: false,
|
||||
message: "테이블 라벨 정보를 찾을 수 없습니다.",
|
||||
error: {
|
||||
code: "TABLE_LABELS_NOT_FOUND",
|
||||
details: `테이블 ${tableName}의 라벨 정보가 존재하지 않습니다.`,
|
||||
},
|
||||
// 라벨이 없으면 빈 객체를 성공으로 반환 (404 에러 대신)
|
||||
const response: ApiResponse<{}> = {
|
||||
success: true,
|
||||
message: "테이블 라벨 정보를 조회했습니다.",
|
||||
data: {},
|
||||
};
|
||||
res.status(404).json(response);
|
||||
res.status(200).json(response);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -350,15 +348,13 @@ export async function getColumnLabels(
|
|||
);
|
||||
|
||||
if (!columnLabels) {
|
||||
const response: ApiResponse<null> = {
|
||||
success: false,
|
||||
message: "컬럼 라벨 정보를 찾을 수 없습니다.",
|
||||
error: {
|
||||
code: "COLUMN_LABELS_NOT_FOUND",
|
||||
details: `컬럼 ${tableName}.${columnName}의 라벨 정보가 존재하지 않습니다.`,
|
||||
},
|
||||
// 라벨이 없으면 빈 객체를 성공으로 반환 (404 에러 대신)
|
||||
const response: ApiResponse<{}> = {
|
||||
success: true,
|
||||
message: "컬럼 라벨 정보를 조회했습니다.",
|
||||
data: {},
|
||||
};
|
||||
res.status(404).json(response);
|
||||
res.status(200).json(response);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -185,6 +185,9 @@ export const ConnectionSetupModal: React.FC<ConnectionSetupModalProps> = ({
|
|||
// 모달이 열릴 때 기본값 설정
|
||||
useEffect(() => {
|
||||
if (isOpen && connection) {
|
||||
// 모달이 열릴 때마다 캐시 초기화 (라벨 업데이트 반영)
|
||||
setTableColumnsCache({});
|
||||
|
||||
const fromTableName = connection.fromNode.tableName;
|
||||
const toTableName = connection.toNode.tableName;
|
||||
const fromDisplayName = connection.fromNode.displayName;
|
||||
|
|
@ -283,8 +286,8 @@ export const ConnectionSetupModal: React.FC<ConnectionSetupModalProps> = ({
|
|||
}, [selectedFromColumns, selectedToColumns]);
|
||||
|
||||
// 테이블 컬럼 로드 함수 (캐시 활용)
|
||||
const loadTableColumns = async (tableName: string): Promise<ColumnInfo[]> => {
|
||||
if (tableColumnsCache[tableName]) {
|
||||
const loadTableColumns = async (tableName: string, forceReload = false): Promise<ColumnInfo[]> => {
|
||||
if (tableColumnsCache[tableName] && !forceReload) {
|
||||
return tableColumnsCache[tableName];
|
||||
}
|
||||
|
||||
|
|
@ -553,26 +556,9 @@ export const ConnectionSetupModal: React.FC<ConnectionSetupModalProps> = ({
|
|||
return true; // DELETE는 필드 매핑 검증 생략
|
||||
}
|
||||
|
||||
// INSERT 액션의 경우 모든 TO 테이블 컬럼이 매핑되거나 기본값이 있어야 함
|
||||
// INSERT 액션의 경우 최소 하나의 매핑이 있으면 됨 (모든 컬럼 매핑 필수 조건 제거)
|
||||
if (action.actionType === "insert") {
|
||||
// TO 테이블의 모든 컬럼을 찾기
|
||||
const toTableName = action.fieldMappings[0]?.targetTable;
|
||||
if (!toTableName) return false;
|
||||
|
||||
const toTableColumns = tableColumnsCache[toTableName] || [];
|
||||
if (toTableColumns.length === 0) return false;
|
||||
|
||||
// 모든 TO 컬럼이 매핑되거나 기본값이 있는지 확인
|
||||
return toTableColumns.every((column) => {
|
||||
const mapping = action.fieldMappings.find((m) => m.targetField === column.columnName);
|
||||
if (!mapping) return false;
|
||||
|
||||
// 소스 매핑 또는 기본값 중 하나는 있어야 함
|
||||
const hasSource = mapping.sourceTable && mapping.sourceField;
|
||||
const hasDefault = mapping.defaultValue && mapping.defaultValue.trim();
|
||||
|
||||
return hasSource || hasDefault;
|
||||
});
|
||||
return true; // 필드 매핑이 있으면 충분함
|
||||
}
|
||||
|
||||
return action.fieldMappings.every((mapping) => {
|
||||
|
|
|
|||
|
|
@ -284,8 +284,12 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
|||
description: "", // 새로 추가된 노드는 description 없이 통일
|
||||
columns: Array.isArray(table.columns)
|
||||
? table.columns.map((col) => ({
|
||||
name: col.columnName || "unknown",
|
||||
type: col.dataType || "varchar", // 기존과 동일한 기본값 사용
|
||||
columnName: col.columnName || "unknown",
|
||||
name: col.columnName || "unknown", // 호환성을 위해 유지
|
||||
displayName: col.displayName, // 한국어 라벨
|
||||
columnLabel: col.columnLabel, // 한국어 라벨
|
||||
type: col.dataType || "varchar",
|
||||
dataType: col.dataType || "varchar",
|
||||
description: col.description || "",
|
||||
}))
|
||||
: [],
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ const SaveDiagramModal: React.FC<SaveDiagramModalProps> = ({
|
|||
<div className="flex flex-wrap gap-2">
|
||||
{connectedTables.map((table) => (
|
||||
<Badge key={table} variant="outline" className="text-xs">
|
||||
📋 {table}
|
||||
{table}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -162,7 +162,7 @@ const SaveDiagramModal: React.FC<SaveDiagramModalProps> = ({
|
|||
</span>
|
||||
</div>
|
||||
<div className="mt-1 text-xs text-gray-600">
|
||||
{relationship.fromColumns.join(", ")} → {relationship.toColumns.join(", ")}
|
||||
{relationship.fromTable} → {relationship.toTable}
|
||||
</div>
|
||||
</div>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
|
|
|
|||
|
|
@ -4,9 +4,13 @@ import React from "react";
|
|||
import { Handle, Position } from "@xyflow/react";
|
||||
|
||||
interface TableColumn {
|
||||
name: string;
|
||||
type: string;
|
||||
description: string;
|
||||
columnName: string;
|
||||
name?: string; // 호환성을 위해 유지
|
||||
columnLabel?: string;
|
||||
displayName?: string;
|
||||
dataType?: string;
|
||||
type?: string; // 호환성을 위해 유지
|
||||
description?: string;
|
||||
}
|
||||
|
||||
interface Table {
|
||||
|
|
@ -43,21 +47,24 @@ export const TableNode: React.FC<{ data: TableNodeData }> = ({ data }) => {
|
|||
<div className="flex-1 overflow-hidden p-2" onMouseEnter={onScrollAreaEnter} onMouseLeave={onScrollAreaLeave}>
|
||||
<div className="space-y-1">
|
||||
{table.columns.map((column) => {
|
||||
const isSelected = selectedColumns.includes(column.name);
|
||||
const columnKey = column.columnName || column.name || "";
|
||||
const columnDisplayName = column.displayName || column.columnLabel || column.name || column.columnName;
|
||||
const columnType = column.dataType || column.type || "";
|
||||
const isSelected = selectedColumns.includes(columnKey);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={column.name}
|
||||
key={columnKey}
|
||||
className={`relative cursor-pointer rounded px-2 py-1 text-xs transition-colors ${
|
||||
isSelected ? "bg-blue-100 text-blue-800 ring-2 ring-blue-500" : "text-gray-700 hover:bg-gray-100"
|
||||
}`}
|
||||
onClick={() => onColumnClick(table.tableName, column.name)}
|
||||
onClick={() => onColumnClick(table.tableName, columnKey)}
|
||||
>
|
||||
{/* 핸들 제거됨 - 컬럼 클릭으로만 연결 생성 */}
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="font-mono font-medium">{column.name}</span>
|
||||
<span className="text-gray-500">{column.type}</span>
|
||||
<span className="font-mono font-medium">{columnDisplayName}</span>
|
||||
<span className="text-gray-500">{columnType}</span>
|
||||
</div>
|
||||
{column.description && <div className="mt-0.5 text-gray-500">{column.description}</div>}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -172,7 +172,7 @@ export const ConditionRenderer: React.FC<ConditionRendererProps> = ({
|
|||
<SelectContent>
|
||||
{fromTableColumns.map((column) => (
|
||||
<SelectItem key={column.columnName} value={column.columnName}>
|
||||
{column.columnName}
|
||||
{column.displayName || column.columnLabel || column.columnName}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
|
|
|
|||
|
|
@ -214,13 +214,13 @@ export const ActionConditionRenderer: React.FC<ActionConditionRendererProps> = (
|
|||
{condition.tableType === "from" &&
|
||||
fromTableColumns.map((column) => (
|
||||
<SelectItem key={column.columnName} value={column.columnName}>
|
||||
{column.columnName}
|
||||
{column.displayName || column.columnLabel || column.columnName}
|
||||
</SelectItem>
|
||||
))}
|
||||
{condition.tableType === "to" &&
|
||||
toTableColumns.map((column) => (
|
||||
<SelectItem key={column.columnName} value={column.columnName}>
|
||||
{column.columnName}
|
||||
{column.displayName || column.columnLabel || column.columnName}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ export const ActionFieldMappings: React.FC<ActionFieldMappingsProps> = ({
|
|||
tableColumnsCache[mapping.sourceTable]?.map((column) => (
|
||||
<SelectItem key={column.columnName} value={column.columnName}>
|
||||
<div className="truncate" title={column.columnName}>
|
||||
{column.columnName}
|
||||
{column.displayName || column.columnLabel || column.columnName}
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
|
|
@ -200,7 +200,7 @@ export const ActionFieldMappings: React.FC<ActionFieldMappingsProps> = ({
|
|||
tableColumnsCache[mapping.targetTable]?.map((column) => (
|
||||
<SelectItem key={column.columnName} value={column.columnName}>
|
||||
<div className="truncate" title={column.columnName}>
|
||||
{column.columnName}
|
||||
{column.displayName || column.columnLabel || column.columnName}
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ export const ActionSplitConfig: React.FC<ActionSplitConfigProps> = ({
|
|||
<SelectContent>
|
||||
{fromTableColumns.map((column) => (
|
||||
<SelectItem key={column.columnName} value={column.columnName}>
|
||||
{column.columnName}
|
||||
{column.displayName || column.columnLabel || column.columnName}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
|
|
@ -117,7 +117,7 @@ export const ActionSplitConfig: React.FC<ActionSplitConfigProps> = ({
|
|||
<SelectContent>
|
||||
{toTableColumns.map((column) => (
|
||||
<SelectItem key={column.columnName} value={column.columnName}>
|
||||
{column.columnName}
|
||||
{column.displayName || column.columnLabel || column.columnName}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
|
|
|
|||
|
|
@ -198,7 +198,9 @@ export const ColumnTableSection: React.FC<ColumnTableSectionProps> = ({
|
|||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="truncate font-medium">{column.columnName}</span>
|
||||
<span className="truncate font-medium">
|
||||
{column.displayName || column.columnLabel || column.columnName}
|
||||
</span>
|
||||
{isSelected && <span className="flex-shrink-0 text-blue-500">●</span>}
|
||||
{isMapped && <span className="flex-shrink-0 text-green-500">✓</span>}
|
||||
{oppositeSelectedColumn && !isTypeCompatible && (
|
||||
|
|
@ -264,7 +266,9 @@ export const ColumnTableSection: React.FC<ColumnTableSectionProps> = ({
|
|||
>
|
||||
<div className="flex min-w-0 flex-col justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="truncate font-medium">{column.columnName}</span>
|
||||
<span className="truncate font-medium">
|
||||
{column.displayName || column.columnLabel || column.columnName}
|
||||
</span>
|
||||
{isSelected && <span className="flex-shrink-0 text-green-500">●</span>}
|
||||
{oppositeSelectedColumn && !isTypeCompatible && (
|
||||
<span className="flex-shrink-0 text-red-500" title="데이터 타입이 호환되지 않음">
|
||||
|
|
|
|||
|
|
@ -236,14 +236,10 @@ export const InsertFieldMappingPanel: React.FC<InsertFieldMappingPanelProps> = (
|
|||
return (
|
||||
<div className="mt-4">
|
||||
{/* 헤더 섹션 */}
|
||||
<Card className="mb-6 from-blue-50 to-green-50 py-2">
|
||||
<CardContent className="pt-0">
|
||||
<p className="text-sm leading-relaxed text-gray-700">
|
||||
양쪽 테이블의 컬럼을 클릭하여 매핑하거나, 대상 컬럼에 기본값을 입력하세요. 같은 데이터 타입의 컬럼만 매핑
|
||||
가능합니다. 하나의 FROM 컬럼은 하나의 TO 컬럼에만 매핑 가능합니다.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<p className="mb-4 text-sm leading-relaxed text-gray-700">
|
||||
양쪽 테이블의 컬럼을 클릭하여 매핑하거나, 대상 컬럼에 기본값을 입력하세요. 같은 데이터 타입의 컬럼만 매핑
|
||||
가능합니다. 하나의 FROM 컬럼은 하나의 TO 컬럼에만 매핑 가능합니다.
|
||||
</p>
|
||||
|
||||
<div className="grid grid-cols-2 gap-6">
|
||||
<ColumnTableSection
|
||||
|
|
@ -290,7 +286,7 @@ export const InsertFieldMappingPanel: React.FC<InsertFieldMappingPanelProps> = (
|
|||
</div>
|
||||
|
||||
{/* 빠른 필터 액션 */}
|
||||
<Card className="mt-6 py-2">
|
||||
<Card className="mt-6 border-none py-2 shadow-none">
|
||||
<CardContent className="flex flex-wrap gap-2 p-3">
|
||||
<span className="self-center text-sm font-medium text-gray-700">빠른 필터:</span>
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ export const SimpleKeySettings: React.FC<SimpleKeySettingsProps> = ({
|
|||
}}
|
||||
className="rounded"
|
||||
/>
|
||||
<span>{column.columnName}</span>
|
||||
<span>{column.displayName || column.columnLabel || column.columnName}</span>
|
||||
<span className="text-xs text-gray-500">({column.dataType})</span>
|
||||
</label>
|
||||
))}
|
||||
|
|
@ -112,7 +112,7 @@ export const SimpleKeySettings: React.FC<SimpleKeySettingsProps> = ({
|
|||
}}
|
||||
className="rounded"
|
||||
/>
|
||||
<span>{column.columnName}</span>
|
||||
<span>{column.displayName || column.columnLabel || column.columnName}</span>
|
||||
<span className="text-xs text-gray-500">({column.dataType})</span>
|
||||
</label>
|
||||
))}
|
||||
|
|
@ -132,11 +132,15 @@ export const SimpleKeySettings: React.FC<SimpleKeySettingsProps> = ({
|
|||
<Label className="text-xs font-medium text-gray-600">선택된 From 컬럼</Label>
|
||||
<div className="mt-1 flex flex-wrap gap-1">
|
||||
{selectedFromColumns.length > 0 ? (
|
||||
selectedFromColumns.map((column) => (
|
||||
<Badge key={column} variant="outline" className="text-xs">
|
||||
{column}
|
||||
</Badge>
|
||||
))
|
||||
selectedFromColumns.map((column) => {
|
||||
const columnInfo = fromTableColumns.find((col) => col.columnName === column);
|
||||
const displayName = columnInfo?.displayName || columnInfo?.columnLabel || column;
|
||||
return (
|
||||
<Badge key={column} variant="outline" className="text-xs">
|
||||
{displayName}
|
||||
</Badge>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<span className="text-xs text-gray-400">선택된 컬럼 없음</span>
|
||||
)}
|
||||
|
|
@ -147,11 +151,15 @@ export const SimpleKeySettings: React.FC<SimpleKeySettingsProps> = ({
|
|||
<Label className="text-xs font-medium text-gray-600">선택된 To 컬럼</Label>
|
||||
<div className="mt-1 flex flex-wrap gap-1">
|
||||
{selectedToColumns.length > 0 ? (
|
||||
selectedToColumns.map((column) => (
|
||||
<Badge key={column} variant="secondary" className="text-xs">
|
||||
{column}
|
||||
</Badge>
|
||||
))
|
||||
selectedToColumns.map((column) => {
|
||||
const columnInfo = toTableColumns.find((col) => col.columnName === column);
|
||||
const displayName = columnInfo?.displayName || columnInfo?.columnLabel || column;
|
||||
return (
|
||||
<Badge key={column} variant="secondary" className="text-xs">
|
||||
{displayName}
|
||||
</Badge>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<span className="text-xs text-gray-400">선택된 컬럼 없음</span>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -376,7 +376,13 @@ export class DataFlowAPI {
|
|||
}
|
||||
|
||||
// 페이지네이션된 응답에서 columns 배열만 추출
|
||||
return response.data.data?.columns || [];
|
||||
const columns = response.data.data?.columns || [];
|
||||
|
||||
// 이미 displayName에 라벨이 포함되어 있으므로 추가 처리 불필요
|
||||
return columns.map((column) => ({
|
||||
...column,
|
||||
columnLabel: column.displayName || column.columnName, // displayName을 columnLabel로도 설정
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error("컬럼 정보 조회 오류:", error);
|
||||
throw error;
|
||||
|
|
@ -390,11 +396,40 @@ export class DataFlowAPI {
|
|||
try {
|
||||
const columns = await this.getTableColumns(tableName);
|
||||
|
||||
// 테이블 라벨 정보 조회
|
||||
let tableLabel = tableName;
|
||||
let tableDescription = `${tableName} 테이블`;
|
||||
|
||||
try {
|
||||
const response = await apiClient.get(`/table-management/tables/${tableName}/labels`);
|
||||
if (response.data.success && response.data.data) {
|
||||
tableLabel = response.data.data.tableLabel || tableName;
|
||||
tableDescription = response.data.data.description || `${tableName} 테이블`;
|
||||
}
|
||||
} catch (error) {
|
||||
// 라벨 정보가 없으면 기본값 사용 (404 등의 에러는 무시)
|
||||
const axiosError = error as { response?: { status?: number } };
|
||||
if (axiosError?.response?.status !== 404) {
|
||||
console.warn(`테이블 라벨 조회 중 예상치 못한 오류: ${tableName}`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// TableNode가 기대하는 컬럼 구조로 변환
|
||||
const formattedColumns = columns.map((column) => ({
|
||||
columnName: column.columnName,
|
||||
name: column.columnName, // TableNode에서 사용하는 필드
|
||||
displayName: column.displayName, // 한국어 라벨
|
||||
columnLabel: column.displayName, // 동일한 값으로 설정
|
||||
type: column.dataType, // TableNode에서 사용하는 필드
|
||||
dataType: column.dataType,
|
||||
description: column.description || "",
|
||||
}));
|
||||
|
||||
return {
|
||||
tableName,
|
||||
displayName: tableName,
|
||||
description: `${tableName} 테이블`,
|
||||
columns,
|
||||
displayName: tableLabel,
|
||||
description: tableDescription,
|
||||
columns: formattedColumns,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("테이블 및 컬럼 정보 조회 오류:", error);
|
||||
|
|
|
|||
Loading…
Reference in New Issue