테이블 노드 위치정보 저장 구현

This commit is contained in:
hyeonsu
2025-09-10 17:48:55 +09:00
parent db509bb3d9
commit 72b0d2ee98
5 changed files with 116 additions and 26 deletions

View File

@@ -5324,14 +5324,15 @@ model data_relationship_bridge {
// 데이터플로우 관계도 - JSON 구조로 저장
model dataflow_diagrams {
diagram_id Int @id @default(autoincrement())
diagram_name String @db.VarChar(255)
relationships Json // 모든 관계 정보를 JSON으로 저장
company_code String @db.VarChar(50)
created_at DateTime? @default(now()) @db.Timestamp(6)
updated_at DateTime? @default(now()) @updatedAt @db.Timestamp(6)
created_by String? @db.VarChar(50)
updated_by String? @db.VarChar(50)
diagram_id Int @id @default(autoincrement())
diagram_name String @db.VarChar(255)
relationships Json // 모든 관계 정보를 JSON으로 저장
node_positions Json? // 테이블 노드의 캔버스 위치 정보 (JSON 형태)
company_code String @db.VarChar(50)
created_at DateTime? @default(now()) @db.Timestamp(6)
updated_at DateTime? @default(now()) @updatedAt @db.Timestamp(6)
created_by String? @db.VarChar(50)
updated_by String? @db.VarChar(50)
@@unique([company_code, diagram_name], map: "unique_diagram_name_per_company")
@@index([company_code], map: "idx_dataflow_diagrams_company")

View File

@@ -17,9 +17,17 @@ export const getDataflowDiagrams = async (req: Request, res: Response) => {
const page = parseInt(req.query.page as string) || 1;
const size = parseInt(req.query.size as string) || 20;
const searchTerm = req.query.searchTerm as string;
const companyCode = (req.query.companyCode as string) || req.headers["x-company-code"] as string || "*";
const companyCode =
(req.query.companyCode as string) ||
(req.headers["x-company-code"] as string) ||
"*";
const result = await getDataflowDiagramsService(companyCode, page, size, searchTerm);
const result = await getDataflowDiagramsService(
companyCode,
page,
size,
searchTerm
);
res.json({
success: true,
@@ -41,7 +49,10 @@ export const getDataflowDiagrams = async (req: Request, res: Response) => {
export const getDataflowDiagramById = async (req: Request, res: Response) => {
try {
const diagramId = parseInt(req.params.diagramId);
const companyCode = (req.query.companyCode as string) || req.headers["x-company-code"] as string || "*";
const companyCode =
(req.query.companyCode as string) ||
(req.headers["x-company-code"] as string) ||
"*";
if (isNaN(diagramId)) {
return res.status(400).json({
@@ -78,9 +89,24 @@ export const getDataflowDiagramById = async (req: Request, res: Response) => {
*/
export const createDataflowDiagram = async (req: Request, res: Response) => {
try {
const { diagram_name, relationships, company_code, created_by, updated_by } = req.body;
const companyCode = company_code || (req.query.companyCode as string) || req.headers["x-company-code"] as string || "*";
const userId = created_by || updated_by || req.headers["x-user-id"] as string || "SYSTEM";
const {
diagram_name,
relationships,
node_positions,
company_code,
created_by,
updated_by,
} = req.body;
const companyCode =
company_code ||
(req.query.companyCode as string) ||
(req.headers["x-company-code"] as string) ||
"*";
const userId =
created_by ||
updated_by ||
(req.headers["x-user-id"] as string) ||
"SYSTEM";
if (!diagram_name || !relationships) {
return res.status(400).json({
@@ -92,6 +118,7 @@ export const createDataflowDiagram = async (req: Request, res: Response) => {
const newDiagram = await createDataflowDiagramService({
diagram_name,
relationships,
node_positions,
company_code: companyCode,
created_by: userId,
updated_by: userId,
@@ -104,7 +131,7 @@ export const createDataflowDiagram = async (req: Request, res: Response) => {
});
} catch (error) {
logger.error("관계도 생성 실패:", error);
// 중복 이름 에러 처리
if (error instanceof Error && error.message.includes("unique constraint")) {
return res.status(409).json({
@@ -128,8 +155,12 @@ export const updateDataflowDiagram = async (req: Request, res: Response) => {
try {
const diagramId = parseInt(req.params.diagramId);
const { updated_by } = req.body;
const companyCode = (req.query.companyCode as string) || req.headers["x-company-code"] as string || "*";
const userId = updated_by || req.headers["x-user-id"] as string || "SYSTEM";
const companyCode =
(req.query.companyCode as string) ||
(req.headers["x-company-code"] as string) ||
"*";
const userId =
updated_by || (req.headers["x-user-id"] as string) || "SYSTEM";
if (isNaN(diagramId)) {
return res.status(400).json({
@@ -143,7 +174,11 @@ export const updateDataflowDiagram = async (req: Request, res: Response) => {
updated_by: userId,
};
const updatedDiagram = await updateDataflowDiagramService(diagramId, updateData, companyCode);
const updatedDiagram = await updateDataflowDiagramService(
diagramId,
updateData,
companyCode
);
if (!updatedDiagram) {
return res.status(404).json({
@@ -173,7 +208,10 @@ export const updateDataflowDiagram = async (req: Request, res: Response) => {
export const deleteDataflowDiagram = async (req: Request, res: Response) => {
try {
const diagramId = parseInt(req.params.diagramId);
const companyCode = (req.query.companyCode as string) || req.headers["x-company-code"] as string || "*";
const companyCode =
(req.query.companyCode as string) ||
(req.headers["x-company-code"] as string) ||
"*";
if (isNaN(diagramId)) {
return res.status(400).json({
@@ -211,9 +249,18 @@ export const deleteDataflowDiagram = async (req: Request, res: Response) => {
export const copyDataflowDiagram = async (req: Request, res: Response) => {
try {
const diagramId = parseInt(req.params.diagramId);
const { new_name, companyCode: bodyCompanyCode, userId: bodyUserId } = req.body;
const companyCode = bodyCompanyCode || (req.query.companyCode as string) || req.headers["x-company-code"] as string || "*";
const userId = bodyUserId || req.headers["x-user-id"] as string || "SYSTEM";
const {
new_name,
companyCode: bodyCompanyCode,
userId: bodyUserId,
} = req.body;
const companyCode =
bodyCompanyCode ||
(req.query.companyCode as string) ||
(req.headers["x-company-code"] as string) ||
"*";
const userId =
bodyUserId || (req.headers["x-user-id"] as string) || "SYSTEM";
if (isNaN(diagramId)) {
return res.status(400).json({
@@ -222,7 +269,12 @@ export const copyDataflowDiagram = async (req: Request, res: Response) => {
});
}
const copiedDiagram = await copyDataflowDiagramService(diagramId, companyCode, new_name, userId);
const copiedDiagram = await copyDataflowDiagramService(
diagramId,
companyCode,
new_name,
userId
);
if (!copiedDiagram) {
return res.status(404).json({

View File

@@ -7,6 +7,7 @@ const prisma = new PrismaClient();
interface CreateDataflowDiagramData {
diagram_name: string;
relationships: any; // JSON 데이터
node_positions?: any; // JSON 데이터 (노드 위치 정보)
company_code: string;
created_by: string;
updated_by: string;
@@ -15,6 +16,7 @@ interface CreateDataflowDiagramData {
interface UpdateDataflowDiagramData {
diagram_name?: string;
relationships?: any; // JSON 데이터
node_positions?: any; // JSON 데이터 (노드 위치 정보)
updated_by: string;
}
@@ -116,6 +118,7 @@ export const createDataflowDiagram = async (
data: {
diagram_name: data.diagram_name,
relationships: data.relationships,
node_positions: data.node_positions || null,
company_code: data.company_code,
created_by: data.created_by,
updated_by: data.updated_by,
@@ -164,6 +167,9 @@ export const updateDataflowDiagram = async (
data: {
...(data.diagram_name && { diagram_name: data.diagram_name }),
...(data.relationships && { relationships: data.relationships }),
...(data.node_positions !== undefined && {
node_positions: data.node_positions,
}),
updated_by: data.updated_by,
updated_at: new Date(),
},

View File

@@ -24,6 +24,7 @@ import {
DataFlowDiagram,
JsonRelationship,
CreateDiagramRequest,
NodePositions,
} from "@/lib/api/dataflow";
import SaveDiagramModal from "./SaveDiagramModal";
import { useAuth } from "@/hooks/useAuth";
@@ -313,10 +314,16 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
console.log("🔌 연결된 컬럼 정보:", connectedColumnsInfo);
// 테이블을 노드로 변환 (자동 레이아웃)
// 저장된 노드 위치 정보 가져오기
const savedNodePositions = jsonDiagram.node_positions || {};
console.log("📍 저장된 노드 위치:", savedNodePositions);
// 테이블을 노드로 변환 (저장된 위치 우선 사용, 없으면 자동 레이아웃)
const tableNodes = tableDefinitions.map((table, index) => {
const x = (index % 3) * 400 + 100; // 3열 배치
const y = Math.floor(index / 3) * 300 + 100;
// 저장된 위치가 있으면 사용, 없으면 자동 배치
const savedPosition = savedNodePositions[table.tableName];
const x = savedPosition ? savedPosition.x : (index % 3) * 400 + 100; // 3열 배치
const y = savedPosition ? savedPosition.y : Math.floor(index / 3) * 300 + 100;
return {
id: `table-${table.tableName}`,
@@ -782,6 +789,17 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
new Set([...tempRelationships.map((rel) => rel.fromTable), ...tempRelationships.map((rel) => rel.toTable)]),
).sort();
// 현재 노드 위치 추출
const nodePositions: NodePositions = {};
nodes.forEach((node) => {
if (node.data?.table?.tableName) {
nodePositions[node.data.table.tableName] = {
x: node.position.x,
y: node.position.y,
};
}
});
// 저장 요청 데이터 생성
const createRequest: CreateDiagramRequest = {
diagram_name: diagramName,
@@ -789,6 +807,7 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
relationships: tempRelationships,
tables: connectedTables,
},
node_positions: nodePositions,
};
let savedDiagram;

View File

@@ -115,6 +115,16 @@ export interface DataFlowDiagramsResponse {
hasPrev: boolean;
}
// 노드 위치 정보 타입
export interface NodePosition {
x: number;
y: number;
}
export interface NodePositions {
[tableName: string]: NodePosition;
}
// 새로운 JSON 기반 타입들
export interface JsonDataFlowDiagram {
diagram_id: number;
@@ -123,6 +133,7 @@ export interface JsonDataFlowDiagram {
relationships: JsonRelationship[];
tables: string[];
};
node_positions?: NodePositions;
company_code: string;
created_at?: string;
updated_at?: string;
@@ -147,6 +158,7 @@ export interface CreateDiagramRequest {
relationships: JsonRelationship[];
tables: string[];
};
node_positions?: NodePositions;
}
export interface JsonDataFlowDiagramsResponse {