digramId를 사용해 제어 관계 그룹화
This commit is contained in:
parent
7bcd405a04
commit
5043b11149
|
|
@ -5108,6 +5108,7 @@ model code_info {
|
|||
// 테이블 간 관계 정의
|
||||
model table_relationships {
|
||||
relationship_id Int @id @default(autoincrement())
|
||||
diagram_id Int // 관계도 그룹 식별자
|
||||
relationship_name String @db.VarChar(200)
|
||||
from_table_name String @db.VarChar(100)
|
||||
from_column_name String @db.VarChar(100)
|
||||
|
|
@ -5127,8 +5128,10 @@ model table_relationships {
|
|||
bridges data_relationship_bridge[]
|
||||
|
||||
@@index([company_code], map: "idx_table_relationships_company_code")
|
||||
@@index([diagram_id], map: "idx_table_relationships_diagram_id")
|
||||
@@index([from_table_name], map: "idx_table_relationships_from_table")
|
||||
@@index([to_table_name], map: "idx_table_relationships_to_table")
|
||||
@@index([company_code, diagram_id], map: "idx_table_relationships_company_diagram")
|
||||
}
|
||||
|
||||
// 테이블 간 데이터 관계 중계 테이블 - 실제 데이터 연결 정보 저장
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export async function createTableRelationship(
|
|||
logger.info("=== 테이블 관계 생성 시작 ===");
|
||||
|
||||
const {
|
||||
diagramId,
|
||||
relationshipName,
|
||||
fromTableName,
|
||||
fromColumnName,
|
||||
|
|
@ -52,6 +53,7 @@ export async function createTableRelationship(
|
|||
|
||||
const dataflowService = new DataflowService();
|
||||
const relationship = await dataflowService.createTableRelationship({
|
||||
diagramId: diagramId ? parseInt(diagramId) : undefined,
|
||||
relationshipName,
|
||||
fromTableName,
|
||||
fromColumnName,
|
||||
|
|
@ -828,7 +830,62 @@ export async function deleteDiagram(
|
|||
}
|
||||
|
||||
/**
|
||||
* relationship_id로 관계도 관계 조회
|
||||
* diagram_id로 관계도 관계 조회
|
||||
*/
|
||||
export async function getDiagramRelationshipsByDiagramId(
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<void> {
|
||||
try {
|
||||
const { diagramId } = req.params;
|
||||
const companyCode = (req.user as any)?.company_code || "*";
|
||||
|
||||
if (!diagramId) {
|
||||
const response: ApiResponse<null> = {
|
||||
success: false,
|
||||
message: "관계도 ID가 필요합니다.",
|
||||
error: {
|
||||
code: "MISSING_DIAGRAM_ID",
|
||||
details: "diagramId 파라미터가 필요합니다.",
|
||||
},
|
||||
};
|
||||
res.status(400).json(response);
|
||||
return;
|
||||
}
|
||||
|
||||
const dataflowService = new DataflowService();
|
||||
const relationships =
|
||||
await dataflowService.getDiagramRelationshipsByDiagramId(
|
||||
companyCode,
|
||||
parseInt(diagramId)
|
||||
);
|
||||
|
||||
const response: ApiResponse<any[]> = {
|
||||
success: true,
|
||||
message: "관계도 관계 목록을 성공적으로 조회했습니다.",
|
||||
data: relationships,
|
||||
};
|
||||
|
||||
res.status(200).json(response);
|
||||
} catch (error) {
|
||||
logger.error("관계도 관계 조회 실패:", error);
|
||||
const response: ApiResponse<null> = {
|
||||
success: false,
|
||||
message: "관계도 관계 조회에 실패했습니다.",
|
||||
error: {
|
||||
code: "DIAGRAM_RELATIONSHIPS_FETCH_FAILED",
|
||||
details:
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "알 수 없는 오류가 발생했습니다.",
|
||||
},
|
||||
};
|
||||
res.status(500).json(response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* relationship_id로 관계도 관계 조회 (하위 호환성 유지)
|
||||
*/
|
||||
export async function getDiagramRelationshipsByRelationshipId(
|
||||
req: AuthenticatedRequest,
|
||||
|
|
@ -852,10 +909,11 @@ export async function getDiagramRelationshipsByRelationshipId(
|
|||
}
|
||||
|
||||
const dataflowService = new DataflowService();
|
||||
const relationships = await dataflowService.getDiagramRelationshipsByRelationshipId(
|
||||
companyCode,
|
||||
parseInt(relationshipId)
|
||||
);
|
||||
const relationships =
|
||||
await dataflowService.getDiagramRelationshipsByRelationshipId(
|
||||
companyCode,
|
||||
parseInt(relationshipId)
|
||||
);
|
||||
|
||||
const response: ApiResponse<any[]> = {
|
||||
success: true,
|
||||
|
|
@ -871,7 +929,10 @@ export async function getDiagramRelationshipsByRelationshipId(
|
|||
message: "관계도 관계 조회에 실패했습니다.",
|
||||
error: {
|
||||
code: "DIAGRAM_RELATIONSHIPS_FETCH_FAILED",
|
||||
details: error instanceof Error ? error.message : "알 수 없는 오류가 발생했습니다.",
|
||||
details:
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "알 수 없는 오류가 발생했습니다.",
|
||||
},
|
||||
};
|
||||
res.status(500).json(response);
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
getTableData,
|
||||
getDataFlowDiagrams,
|
||||
getDiagramRelationships,
|
||||
getDiagramRelationshipsByDiagramId,
|
||||
getDiagramRelationshipsByRelationshipId,
|
||||
copyDiagram,
|
||||
deleteDiagram,
|
||||
|
|
@ -92,10 +93,22 @@ router.get("/table-data/:tableName", getTableData);
|
|||
router.get("/diagrams", getDataFlowDiagrams);
|
||||
|
||||
/**
|
||||
* 특정 관계도의 모든 관계 조회
|
||||
* GET /api/dataflow/diagrams/:diagramName/relationships
|
||||
* 특정 관계도의 모든 관계 조회 (diagram_id로)
|
||||
* GET /api/dataflow/diagrams/:diagramId/relationships
|
||||
*/
|
||||
router.get("/diagrams/:diagramName/relationships", getDiagramRelationships);
|
||||
router.get(
|
||||
"/diagrams/:diagramId/relationships",
|
||||
getDiagramRelationshipsByDiagramId
|
||||
);
|
||||
|
||||
/**
|
||||
* 특정 관계도의 모든 관계 조회 (diagramName으로 - 하위 호환성)
|
||||
* GET /api/dataflow/diagrams/name/:diagramName/relationships
|
||||
*/
|
||||
router.get(
|
||||
"/diagrams/name/:diagramName/relationships",
|
||||
getDiagramRelationships
|
||||
);
|
||||
|
||||
/**
|
||||
* 관계도 복사
|
||||
|
|
@ -109,7 +122,7 @@ router.post("/diagrams/:diagramName/copy", copyDiagram);
|
|||
*/
|
||||
router.delete("/diagrams/:diagramName", deleteDiagram);
|
||||
|
||||
// relationship_id로 관계도 관계 조회
|
||||
// relationship_id로 관계도 관계 조회 (하위 호환성)
|
||||
router.get(
|
||||
"/relationships/:relationshipId/diagram",
|
||||
getDiagramRelationshipsByRelationshipId
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ const prisma = new PrismaClient();
|
|||
|
||||
// 테이블 관계 생성 데이터 타입
|
||||
interface CreateTableRelationshipData {
|
||||
diagramId?: number; // 기존 관계도에 추가하는 경우
|
||||
relationshipName: string;
|
||||
fromTableName: string;
|
||||
fromColumnName: string;
|
||||
|
|
@ -38,9 +39,31 @@ export class DataflowService {
|
|||
try {
|
||||
logger.info("DataflowService: 테이블 관계 생성 시작", data);
|
||||
|
||||
// 중복 관계 확인
|
||||
// diagram_id 결정 로직
|
||||
let diagramId = data.diagramId;
|
||||
|
||||
if (!diagramId) {
|
||||
// 새로운 관계도인 경우, 새로운 diagram_id 생성
|
||||
// 현재 최대 diagram_id + 1
|
||||
const maxDiagramId = await prisma.table_relationships.findFirst({
|
||||
where: {
|
||||
company_code: data.companyCode,
|
||||
},
|
||||
orderBy: {
|
||||
diagram_id: "desc",
|
||||
},
|
||||
select: {
|
||||
diagram_id: true,
|
||||
},
|
||||
});
|
||||
|
||||
diagramId = (maxDiagramId?.diagram_id || 0) + 1;
|
||||
}
|
||||
|
||||
// 중복 관계 확인 (같은 diagram_id 내에서)
|
||||
const existingRelationship = await prisma.table_relationships.findFirst({
|
||||
where: {
|
||||
diagram_id: diagramId,
|
||||
from_table_name: data.fromTableName,
|
||||
from_column_name: data.fromColumnName,
|
||||
to_table_name: data.toTableName,
|
||||
|
|
@ -56,9 +79,10 @@ export class DataflowService {
|
|||
);
|
||||
}
|
||||
|
||||
// 새 관계 생성 (중계 테이블은 별도로 생성하지 않음)
|
||||
// 새 관계 생성
|
||||
const relationship = await prisma.table_relationships.create({
|
||||
data: {
|
||||
diagram_id: diagramId,
|
||||
relationship_name: data.relationshipName,
|
||||
from_table_name: data.fromTableName,
|
||||
from_column_name: data.fromColumnName,
|
||||
|
|
@ -74,7 +98,7 @@ export class DataflowService {
|
|||
});
|
||||
|
||||
logger.info(
|
||||
`DataflowService: 테이블 관계 생성 완료 - ID: ${relationship.relationship_id}`
|
||||
`DataflowService: 테이블 관계 생성 완료 - ID: ${relationship.relationship_id}, Diagram ID: ${relationship.diagram_id}`
|
||||
);
|
||||
return relationship;
|
||||
} catch (error) {
|
||||
|
|
@ -731,7 +755,7 @@ export class DataflowService {
|
|||
}
|
||||
|
||||
/**
|
||||
* 관계도 그룹 목록 조회 (관계도 이름별로 그룹화)
|
||||
* 관계도 그룹 목록 조회 (diagram_id별로 그룹화)
|
||||
*/
|
||||
async getDataFlowDiagrams(
|
||||
companyCode: string,
|
||||
|
|
@ -744,7 +768,7 @@ export class DataflowService {
|
|||
`DataflowService: 관계도 목록 조회 시작 - ${companyCode}, page: ${page}, size: ${size}, search: ${searchTerm}`
|
||||
);
|
||||
|
||||
// 관계도 이름별로 그룹화하여 조회
|
||||
// diagram_id별로 그룹화하여 조회
|
||||
const whereCondition = {
|
||||
company_code: companyCode,
|
||||
is_active: "Y",
|
||||
|
|
@ -772,11 +796,12 @@ export class DataflowService {
|
|||
}),
|
||||
};
|
||||
|
||||
// 관계도별로 그룹화된 데이터 조회 (관계도 이름을 기준으로)
|
||||
// diagram_id별로 그룹화된 데이터 조회
|
||||
const relationships = await prisma.table_relationships.findMany({
|
||||
where: whereCondition,
|
||||
select: {
|
||||
relationship_id: true,
|
||||
diagram_id: true,
|
||||
relationship_name: true,
|
||||
from_table_name: true,
|
||||
to_table_name: true,
|
||||
|
|
@ -787,19 +812,19 @@ export class DataflowService {
|
|||
updated_date: true,
|
||||
updated_by: true,
|
||||
},
|
||||
orderBy: [{ relationship_name: "asc" }, { created_date: "desc" }],
|
||||
orderBy: [{ diagram_id: "asc" }, { created_date: "desc" }],
|
||||
});
|
||||
|
||||
// 관계도 이름별로 그룹화
|
||||
const diagramMap = new Map<string, any>();
|
||||
// diagram_id별로 그룹화
|
||||
const diagramMap = new Map<number, any>();
|
||||
|
||||
relationships.forEach((rel) => {
|
||||
const diagramName = rel.relationship_name;
|
||||
const diagramId = rel.diagram_id;
|
||||
|
||||
if (!diagramMap.has(diagramName)) {
|
||||
diagramMap.set(diagramName, {
|
||||
relationshipId: rel.relationship_id, // 첫 번째 관계의 ID를 대표 ID로 사용
|
||||
diagramName: diagramName,
|
||||
if (!diagramMap.has(diagramId)) {
|
||||
diagramMap.set(diagramId, {
|
||||
diagramId: diagramId,
|
||||
diagramName: rel.relationship_name, // 첫 번째 관계의 이름을 사용
|
||||
connectionType: rel.connection_type,
|
||||
relationshipType: rel.relationship_type,
|
||||
tableCount: new Set<string>(),
|
||||
|
|
@ -812,7 +837,7 @@ export class DataflowService {
|
|||
});
|
||||
}
|
||||
|
||||
const diagram = diagramMap.get(diagramName);
|
||||
const diagram = diagramMap.get(diagramId);
|
||||
diagram.tableCount.add(rel.from_table_name);
|
||||
diagram.tableCount.add(rel.to_table_name);
|
||||
diagram.relationshipCount++;
|
||||
|
|
@ -893,7 +918,7 @@ export class DataflowService {
|
|||
}
|
||||
|
||||
/**
|
||||
* 관계도 복사
|
||||
* 관계도 복사 (diagram_id 기반)
|
||||
*/
|
||||
async copyDiagram(
|
||||
companyCode: string,
|
||||
|
|
@ -936,11 +961,27 @@ export class DataflowService {
|
|||
newDiagramName = `${originalDiagramName} (${counter})`;
|
||||
}
|
||||
|
||||
// 새로운 diagram_id 생성
|
||||
const maxDiagramId = await prisma.table_relationships.findFirst({
|
||||
where: {
|
||||
company_code: companyCode,
|
||||
},
|
||||
orderBy: {
|
||||
diagram_id: "desc",
|
||||
},
|
||||
select: {
|
||||
diagram_id: true,
|
||||
},
|
||||
});
|
||||
|
||||
const newDiagramId = (maxDiagramId?.diagram_id || 0) + 1;
|
||||
|
||||
// 트랜잭션으로 모든 관계 복사
|
||||
const copiedRelationships = await prisma.$transaction(
|
||||
originalRelationships.map((rel) =>
|
||||
prisma.table_relationships.create({
|
||||
data: {
|
||||
diagram_id: newDiagramId,
|
||||
relationship_name: newDiagramName,
|
||||
from_table_name: rel.from_table_name,
|
||||
from_column_name: rel.from_column_name,
|
||||
|
|
@ -959,7 +1000,7 @@ export class DataflowService {
|
|||
);
|
||||
|
||||
logger.info(
|
||||
`DataflowService: 관계도 복사 완료 - ${originalDiagramName} → ${newDiagramName}, ${copiedRelationships.length}개 관계 복사`
|
||||
`DataflowService: 관계도 복사 완료 - ${originalDiagramName} → ${newDiagramName} (diagram_id: ${newDiagramId}), ${copiedRelationships.length}개 관계 복사`
|
||||
);
|
||||
|
||||
return newDiagramName;
|
||||
|
|
@ -1002,7 +1043,46 @@ export class DataflowService {
|
|||
}
|
||||
|
||||
/**
|
||||
* relationship_id로 해당 관계도의 모든 관계 조회
|
||||
* diagram_id로 해당 관계도의 모든 관계 조회
|
||||
*/
|
||||
async getDiagramRelationshipsByDiagramId(
|
||||
companyCode: string,
|
||||
diagramId: number
|
||||
) {
|
||||
try {
|
||||
logger.info(
|
||||
`DataflowService: diagram_id로 관계도 관계 조회 - ${diagramId}`
|
||||
);
|
||||
|
||||
// diagram_id로 모든 관계 조회
|
||||
const relationships = await prisma.table_relationships.findMany({
|
||||
where: {
|
||||
diagram_id: diagramId,
|
||||
company_code: companyCode,
|
||||
is_active: "Y",
|
||||
},
|
||||
orderBy: [{ relationship_id: "asc" }],
|
||||
});
|
||||
|
||||
logger.info(
|
||||
`DataflowService: diagram_id로 관계도 관계 조회 완료 - ${relationships.length}개 관계`
|
||||
);
|
||||
|
||||
return relationships.map((rel) => ({
|
||||
...rel,
|
||||
settings: rel.settings as any,
|
||||
}));
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`DataflowService: diagram_id로 관계도 관계 조회 실패 - ${diagramId}`,
|
||||
error
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* relationship_id로 해당 관계도의 모든 관계 조회 (하위 호환성 유지)
|
||||
*/
|
||||
async getDiagramRelationshipsByRelationshipId(
|
||||
companyCode: string,
|
||||
|
|
@ -1013,7 +1093,7 @@ export class DataflowService {
|
|||
`DataflowService: relationship_id로 관계도 관계 조회 - ${relationshipId}`
|
||||
);
|
||||
|
||||
// 먼저 해당 relationship_id의 관계도명을 찾음
|
||||
// 먼저 해당 relationship_id의 diagram_id를 찾음
|
||||
const targetRelationship = await prisma.table_relationships.findFirst({
|
||||
where: {
|
||||
relationship_id: relationshipId,
|
||||
|
|
@ -1021,7 +1101,7 @@ export class DataflowService {
|
|||
is_active: "Y",
|
||||
},
|
||||
select: {
|
||||
relationship_name: true,
|
||||
diagram_id: true,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -1029,24 +1109,11 @@ export class DataflowService {
|
|||
throw new Error("해당 관계 ID를 찾을 수 없습니다.");
|
||||
}
|
||||
|
||||
// 같은 관계도명을 가진 모든 관계 조회
|
||||
const relationships = await prisma.table_relationships.findMany({
|
||||
where: {
|
||||
relationship_name: targetRelationship.relationship_name,
|
||||
company_code: companyCode,
|
||||
is_active: "Y",
|
||||
},
|
||||
orderBy: [{ relationship_id: "asc" }],
|
||||
});
|
||||
|
||||
logger.info(
|
||||
`DataflowService: relationship_id로 관계도 관계 조회 완료 - ${relationships.length}개 관계`
|
||||
// diagram_id로 모든 관계 조회
|
||||
return this.getDiagramRelationshipsByDiagramId(
|
||||
companyCode,
|
||||
targetRelationship.diagram_id
|
||||
);
|
||||
|
||||
return relationships.map((rel) => ({
|
||||
...rel,
|
||||
settings: rel.settings as any,
|
||||
}));
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`DataflowService: relationship_id로 관계도 관계 조회 실패 - ${relationshipId}`,
|
||||
|
|
|
|||
|
|
@ -11,19 +11,19 @@ import { toast } from "sonner";
|
|||
export default function DataFlowEditPage() {
|
||||
const params = useParams();
|
||||
const router = useRouter();
|
||||
const [relationshipId, setRelationshipId] = useState<string>("");
|
||||
const [diagramId, setDiagramId] = useState<number>(0);
|
||||
const [diagramName, setDiagramName] = useState<string>("");
|
||||
|
||||
useEffect(() => {
|
||||
if (params.relationshipId) {
|
||||
// URL에서 relationship_id 설정
|
||||
const id = params.relationshipId as string;
|
||||
setRelationshipId(id);
|
||||
if (params.diagramId) {
|
||||
// URL에서 diagram_id 설정
|
||||
const id = parseInt(params.diagramId as string);
|
||||
setDiagramId(id);
|
||||
|
||||
// relationship_id로 관계도명 조회
|
||||
// diagram_id로 관계도명 조회
|
||||
const fetchDiagramName = async () => {
|
||||
try {
|
||||
const relationships = await DataFlowAPI.getDiagramRelationshipsByRelationshipId(id);
|
||||
const relationships = await DataFlowAPI.getDiagramRelationshipsByDiagramId(id);
|
||||
if (relationships.length > 0) {
|
||||
setDiagramName(relationships[0].relationship_name);
|
||||
} else {
|
||||
|
|
@ -37,13 +37,13 @@ export default function DataFlowEditPage() {
|
|||
|
||||
fetchDiagramName();
|
||||
}
|
||||
}, [params.relationshipId]);
|
||||
}, [params.diagramId]);
|
||||
|
||||
const handleBackToList = () => {
|
||||
router.push("/admin/dataflow");
|
||||
};
|
||||
|
||||
if (!relationshipId || !diagramName) {
|
||||
if (!diagramId || !diagramName) {
|
||||
return (
|
||||
<div className="flex h-64 items-center justify-center">
|
||||
<div className="text-center">
|
||||
|
|
@ -74,11 +74,7 @@ export default function DataFlowEditPage() {
|
|||
|
||||
{/* 데이터플로우 디자이너 */}
|
||||
<div className="rounded-lg border border-gray-200 bg-white">
|
||||
<DataFlowDesigner
|
||||
selectedDiagram={diagramName}
|
||||
relationshipId={relationshipId}
|
||||
onBackToList={handleBackToList}
|
||||
/>
|
||||
<DataFlowDesigner selectedDiagram={diagramName} diagramId={diagramId} onBackToList={handleBackToList} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -72,8 +72,10 @@ export default function DataFlowPage() {
|
|||
|
||||
const handleSave = (relationships: TableRelationship[]) => {
|
||||
console.log("저장된 관계:", relationships);
|
||||
// 저장 후 목록으로 돌아가기
|
||||
goToStep("list");
|
||||
// 저장 후 목록으로 돌아가기 - 다음 렌더링 사이클로 지연
|
||||
setTimeout(() => {
|
||||
goToStep("list");
|
||||
}, 0);
|
||||
};
|
||||
|
||||
const handleDiagramSelect = (diagram: DataFlowDiagram) => {
|
||||
|
|
@ -83,7 +85,7 @@ export default function DataFlowPage() {
|
|||
const handleDesignDiagram = (diagram: DataFlowDiagram | null) => {
|
||||
if (diagram) {
|
||||
// 기존 관계도 편집 - 새로운 URL로 이동
|
||||
router.push(`/admin/dataflow/edit/${diagram.relationshipId}`);
|
||||
router.push(`/admin/dataflow/edit/${diagram.diagramId}`);
|
||||
} else {
|
||||
// 새 관계도 생성 - 현재 페이지에서 처리
|
||||
setSelectedDiagram(null);
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ interface ConnectionSetupModalProps {
|
|||
isOpen: boolean;
|
||||
connection: ConnectionInfo | null;
|
||||
companyCode: string;
|
||||
diagramId?: number;
|
||||
onConfirm: (relationship: TableRelationship) => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
|
@ -78,6 +79,7 @@ export const ConnectionSetupModal: React.FC<ConnectionSetupModalProps> = ({
|
|||
isOpen,
|
||||
connection,
|
||||
companyCode,
|
||||
diagramId,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
}) => {
|
||||
|
|
@ -188,15 +190,17 @@ export const ConnectionSetupModal: React.FC<ConnectionSetupModalProps> = ({
|
|||
toast.loading("관계를 생성하고 있습니다...", { id: "create-relationship" });
|
||||
|
||||
// 단일 관계 데이터 준비 (모든 선택된 컬럼 정보 포함)
|
||||
const relationshipData: Omit<TableRelationship, "relationship_id"> = {
|
||||
relationship_name: config.relationshipName,
|
||||
from_table_name: connection.fromNode.tableName,
|
||||
from_column_name: fromColumns.join(","), // 여러 컬럼을 콤마로 구분
|
||||
to_table_name: connection.toNode.tableName,
|
||||
to_column_name: toColumns.join(","), // 여러 컬럼을 콤마로 구분
|
||||
relationship_type: config.relationshipType,
|
||||
connection_type: config.connectionType,
|
||||
company_code: companyCode,
|
||||
// API 요청용 데이터 (camelCase)
|
||||
const apiRequestData = {
|
||||
...(diagramId && diagramId > 0 ? { diagramId: diagramId } : {}), // diagramId가 유효할 때만 추가
|
||||
relationshipName: config.relationshipName,
|
||||
fromTableName: connection.fromNode.tableName,
|
||||
fromColumnName: fromColumns.join(","), // 여러 컬럼을 콤마로 구분
|
||||
toTableName: connection.toNode.tableName,
|
||||
toColumnName: toColumns.join(","), // 여러 컬럼을 콤마로 구분
|
||||
relationshipType: config.relationshipType,
|
||||
connectionType: config.connectionType,
|
||||
companyCode: companyCode,
|
||||
settings: {
|
||||
...settings,
|
||||
multiColumnMapping: {
|
||||
|
|
@ -211,11 +215,10 @@ export const ConnectionSetupModal: React.FC<ConnectionSetupModalProps> = ({
|
|||
to: toColumns.length,
|
||||
},
|
||||
},
|
||||
is_active: "Y",
|
||||
};
|
||||
|
||||
// API 호출
|
||||
const createdRelationship = await DataFlowAPI.createRelationship(relationshipData);
|
||||
const createdRelationship = await DataFlowAPI.createRelationship(apiRequestData as any);
|
||||
|
||||
toast.success("관계가 성공적으로 생성되었습니다!", { id: "create-relationship" });
|
||||
|
||||
|
|
|
|||
|
|
@ -20,10 +20,10 @@ import { ConnectionSetupModal } from "./ConnectionSetupModal";
|
|||
import { TableDefinition, TableRelationship, DataFlowAPI, DataFlowDiagram } from "@/lib/api/dataflow";
|
||||
|
||||
// 고유 ID 생성 함수
|
||||
const generateUniqueId = (prefix: string, relationshipId?: number): string => {
|
||||
const generateUniqueId = (prefix: string, diagramId?: number): string => {
|
||||
const timestamp = Date.now();
|
||||
const random = Math.random().toString(36).substr(2, 9);
|
||||
return `${prefix}-${relationshipId || timestamp}-${random}`;
|
||||
return `${prefix}-${diagramId || timestamp}-${random}`;
|
||||
};
|
||||
|
||||
// 테이블 노드 데이터 타입 정의
|
||||
|
|
@ -53,7 +53,8 @@ interface DataFlowDesignerProps {
|
|||
companyCode?: string;
|
||||
onSave?: (relationships: TableRelationship[]) => void;
|
||||
selectedDiagram?: DataFlowDiagram | string | null;
|
||||
relationshipId?: string;
|
||||
diagramId?: number;
|
||||
relationshipId?: string; // 하위 호환성 유지
|
||||
onBackToList?: () => void;
|
||||
}
|
||||
|
||||
|
|
@ -61,7 +62,8 @@ interface DataFlowDesignerProps {
|
|||
|
||||
export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
||||
companyCode = "*",
|
||||
relationshipId,
|
||||
diagramId,
|
||||
relationshipId, // 하위 호환성 유지
|
||||
onSave,
|
||||
selectedDiagram,
|
||||
onBackToList, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
|
|
@ -86,6 +88,7 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
|||
};
|
||||
} | null>(null);
|
||||
const [relationships, setRelationships] = useState<TableRelationship[]>([]); // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
const [currentDiagramId, setCurrentDiagramId] = useState<number | null>(null); // 현재 화면의 diagram_id
|
||||
const toastShownRef = useRef(false); // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
|
||||
// 키보드 이벤트 핸들러 (Del 키로 선택된 노드 삭제)
|
||||
|
|
@ -169,15 +172,21 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
|||
|
||||
// 선택된 관계도의 관계 로드
|
||||
const loadSelectedDiagramRelationships = useCallback(async () => {
|
||||
if (!relationshipId) return;
|
||||
const currentDiagramId = diagramId || (relationshipId ? parseInt(relationshipId) : null);
|
||||
if (!currentDiagramId || isNaN(currentDiagramId)) return;
|
||||
|
||||
try {
|
||||
console.log("🔍 관계도 로드 시작 (relationshipId):", relationshipId);
|
||||
console.log("🔍 관계도 로드 시작 (diagramId):", currentDiagramId);
|
||||
toast.loading("관계도를 불러오는 중...", { id: "load-diagram" });
|
||||
|
||||
// relationshipId로 해당 관계도의 모든 관계 조회
|
||||
const diagramRelationships = await DataFlowAPI.getDiagramRelationshipsByRelationshipId(relationshipId);
|
||||
// diagramId로 해당 관계도의 모든 관계 조회
|
||||
const diagramRelationships = await DataFlowAPI.getDiagramRelationshipsByDiagramId(currentDiagramId);
|
||||
console.log("📋 관계도 관계 데이터:", diagramRelationships);
|
||||
|
||||
if (!Array.isArray(diagramRelationships)) {
|
||||
throw new Error("관계도 데이터 형식이 올바르지 않습니다.");
|
||||
}
|
||||
|
||||
console.log("📋 첫 번째 관계 상세:", diagramRelationships[0]);
|
||||
console.log(
|
||||
"📋 관계 객체 키들:",
|
||||
|
|
@ -185,11 +194,18 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
|||
);
|
||||
setRelationships(diagramRelationships);
|
||||
|
||||
// 현재 diagram_id 설정 (기존 관계도 편집 시)
|
||||
if (diagramRelationships.length > 0) {
|
||||
setCurrentDiagramId(diagramRelationships[0].diagram_id || null);
|
||||
}
|
||||
|
||||
// 관계도의 모든 테이블 추출
|
||||
const tableNames = new Set<string>();
|
||||
diagramRelationships.forEach((rel) => {
|
||||
tableNames.add(rel.from_table_name);
|
||||
tableNames.add(rel.to_table_name);
|
||||
if (rel && rel.from_table_name && rel.to_table_name) {
|
||||
tableNames.add(rel.from_table_name);
|
||||
tableNames.add(rel.to_table_name);
|
||||
}
|
||||
});
|
||||
console.log("📊 추출된 테이블 이름들:", Array.from(tableNames));
|
||||
|
||||
|
|
@ -222,10 +238,21 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
|||
} = {};
|
||||
|
||||
diagramRelationships.forEach((rel) => {
|
||||
if (!rel || !rel.from_table_name || !rel.to_table_name || !rel.from_column_name || !rel.to_column_name) {
|
||||
console.warn("⚠️ 관계 데이터가 불완전합니다:", rel);
|
||||
return;
|
||||
}
|
||||
|
||||
const fromTable = rel.from_table_name;
|
||||
const toTable = rel.to_table_name;
|
||||
const fromColumns = rel.from_column_name.split(",").map((col) => col.trim());
|
||||
const toColumns = rel.to_column_name.split(",").map((col) => col.trim());
|
||||
const fromColumns = rel.from_column_name
|
||||
.split(",")
|
||||
.map((col) => col.trim())
|
||||
.filter((col) => col);
|
||||
const toColumns = rel.to_column_name
|
||||
.split(",")
|
||||
.map((col) => col.trim())
|
||||
.filter((col) => col);
|
||||
|
||||
// 소스 테이블의 컬럼들을 source로 표시
|
||||
if (!connectedColumnsInfo[fromTable]) connectedColumnsInfo[fromTable] = {};
|
||||
|
|
@ -283,12 +310,28 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
|||
const relationshipEdges: Edge[] = [];
|
||||
|
||||
diagramRelationships.forEach((rel) => {
|
||||
if (!rel || !rel.from_table_name || !rel.to_table_name || !rel.from_column_name || !rel.to_column_name) {
|
||||
console.warn("⚠️ 에지 생성 시 관계 데이터가 불완전합니다:", rel);
|
||||
return;
|
||||
}
|
||||
|
||||
const fromTable = rel.from_table_name;
|
||||
const toTable = rel.to_table_name;
|
||||
const fromColumns = rel.from_column_name.split(",").map((col) => col.trim());
|
||||
const toColumns = rel.to_column_name.split(",").map((col) => col.trim());
|
||||
const fromColumns = rel.from_column_name
|
||||
.split(",")
|
||||
.map((col) => col.trim())
|
||||
.filter((col) => col);
|
||||
const toColumns = rel.to_column_name
|
||||
.split(",")
|
||||
.map((col) => col.trim())
|
||||
.filter((col) => col);
|
||||
|
||||
// 각 from 컬럼을 각 to 컬럼에 연결 (1:1 매핑이거나 many:many인 경우)
|
||||
if (fromColumns.length === 0 || toColumns.length === 0) {
|
||||
console.warn("⚠️ 컬럼 정보가 없습니다:", { fromColumns, toColumns });
|
||||
return;
|
||||
}
|
||||
|
||||
const maxConnections = Math.max(fromColumns.length, toColumns.length);
|
||||
|
||||
for (let i = 0; i < maxConnections; i++) {
|
||||
|
|
@ -296,7 +339,7 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
|||
const toColumn = toColumns[i] || toColumns[0]; // 컬럼이 부족하면 첫 번째 컬럼 재사용
|
||||
|
||||
relationshipEdges.push({
|
||||
id: generateUniqueId("edge", rel.relationship_id),
|
||||
id: generateUniqueId("edge", rel.diagram_id),
|
||||
source: `table-${fromTable}`,
|
||||
target: `table-${toTable}`,
|
||||
sourceHandle: `${fromTable}-${fromColumn}-source`,
|
||||
|
|
@ -329,7 +372,7 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
|||
console.error("선택된 관계도 로드 실패:", error);
|
||||
toast.error("관계도를 불러오는데 실패했습니다.", { id: "load-diagram" });
|
||||
}
|
||||
}, [relationshipId, setNodes, setEdges, selectedColumns, handleColumnClick]);
|
||||
}, [diagramId, relationshipId, setNodes, setEdges, selectedColumns, handleColumnClick]);
|
||||
|
||||
// 기존 관계 로드 (새 관계도 생성 시)
|
||||
const loadExistingRelationships = useCallback(async () => {
|
||||
|
|
@ -356,7 +399,7 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
|||
const toColumn = toColumns[i] || toColumns[0];
|
||||
|
||||
existingEdges.push({
|
||||
id: generateUniqueId("edge", rel.relationship_id),
|
||||
id: generateUniqueId("edge", rel.diagram_id),
|
||||
source: `table-${fromTable}`,
|
||||
target: `table-${toTable}`,
|
||||
sourceHandle: `${fromTable}-${fromColumn}-source`,
|
||||
|
|
@ -391,13 +434,13 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
|||
// 컴포넌트 마운트 시 관계 로드
|
||||
useEffect(() => {
|
||||
if (companyCode) {
|
||||
if (relationshipId) {
|
||||
if (diagramId || relationshipId) {
|
||||
loadSelectedDiagramRelationships();
|
||||
} else {
|
||||
loadExistingRelationships();
|
||||
}
|
||||
}
|
||||
}, [companyCode, relationshipId, loadExistingRelationships, loadSelectedDiagramRelationships]);
|
||||
}, [companyCode, diagramId, relationshipId, loadExistingRelationships, loadSelectedDiagramRelationships]);
|
||||
|
||||
// 노드 선택 변경 핸들러
|
||||
const onSelectionChange = useCallback(({ nodes }: { nodes: Node<TableNodeData>[] }) => {
|
||||
|
|
@ -561,6 +604,7 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
|||
setSelectedColumns({});
|
||||
setSelectionOrder([]);
|
||||
setSelectedNodes([]);
|
||||
setCurrentDiagramId(null); // 현재 diagram_id도 초기화
|
||||
}, [setNodes, setEdges]);
|
||||
|
||||
// 현재 추가된 테이블명 목록 가져오기
|
||||
|
|
@ -587,7 +631,7 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
|||
const toColumn = toColumns[i] || toColumns[0];
|
||||
|
||||
newEdges.push({
|
||||
id: generateUniqueId("edge", relationship.relationship_id),
|
||||
id: generateUniqueId("edge", relationship.diagram_id),
|
||||
source: pendingConnection.fromNode.id,
|
||||
target: pendingConnection.toNode.id,
|
||||
sourceHandle: `${fromTable}-${fromColumn}-source`,
|
||||
|
|
@ -615,17 +659,19 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
|||
setRelationships((prev) => [...prev, relationship]);
|
||||
setPendingConnection(null);
|
||||
|
||||
console.log("관계 생성 완료:", relationship);
|
||||
// 저장 콜백 호출 (필요한 경우)
|
||||
if (onSave) {
|
||||
// 현재 모든 관계를 수집하여 전달
|
||||
setRelationships((currentRelationships) => {
|
||||
onSave([...currentRelationships, relationship]);
|
||||
return currentRelationships;
|
||||
});
|
||||
// 첫 번째 관계 생성 시 currentDiagramId 설정 (새 관계도 생성 시)
|
||||
if (!currentDiagramId && relationship.diagram_id) {
|
||||
setCurrentDiagramId(relationship.diagram_id);
|
||||
}
|
||||
|
||||
console.log("관계 생성 완료:", relationship);
|
||||
// 관계 생성 완료 후 자동으로 목록 새로고침을 위한 콜백 (선택적)
|
||||
// 렌더링 중 상태 업데이트 방지를 위해 제거
|
||||
// if (onSave) {
|
||||
// onSave([...relationships, relationship]);
|
||||
// }
|
||||
},
|
||||
[pendingConnection, setEdges, onSave],
|
||||
[pendingConnection, setEdges, currentDiagramId],
|
||||
);
|
||||
|
||||
// 연결 설정 취소
|
||||
|
|
@ -684,6 +730,10 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
|||
<span>연결:</span>
|
||||
<span className="font-medium">{edges.length}개</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>관계도 ID:</span>
|
||||
<span className="font-medium">{currentDiagramId || "미설정"}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -806,6 +856,7 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
|
|||
isOpen={!!pendingConnection}
|
||||
connection={pendingConnection}
|
||||
companyCode={companyCode}
|
||||
diagramId={currentDiagramId || diagramId || (relationshipId ? parseInt(relationshipId) : undefined)}
|
||||
onConfirm={handleConfirmConnection}
|
||||
onCancel={handleCancelConnection}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -232,9 +232,9 @@ export default function DataFlowList({ onDiagramSelect, selectedDiagram, onDesig
|
|||
<TableBody>
|
||||
{diagrams.map((diagram) => (
|
||||
<TableRow
|
||||
key={diagram.diagramName}
|
||||
key={diagram.diagramId}
|
||||
className={`cursor-pointer hover:bg-gray-50 ${
|
||||
selectedDiagram?.diagramName === diagram.diagramName ? "border-blue-200 bg-blue-50" : ""
|
||||
selectedDiagram?.diagramId === diagram.diagramId ? "border-blue-200 bg-blue-50" : ""
|
||||
}`}
|
||||
onClick={() => handleDiagramSelect(diagram)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ const getApiBaseUrl = (): string => {
|
|||
port: currentPort,
|
||||
});
|
||||
|
||||
// 로컬 개발환경: localhost:9771 → localhost:8080
|
||||
if ((currentHost === "localhost" || currentHost === "127.0.0.1") && currentPort === "9771") {
|
||||
// 로컬 개발환경: localhost:9771 또는 localhost:3000 → localhost:8080
|
||||
if ((currentHost === "localhost" || currentHost === "127.0.0.1") && (currentPort === "9771" || currentPort === "3000")) {
|
||||
console.log("🏠 로컬 개발 환경 감지 → localhost:8080/api");
|
||||
return "http://localhost:8080/api";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ export interface TableInfo {
|
|||
|
||||
export interface TableRelationship {
|
||||
relationship_id?: number;
|
||||
diagram_id?: number; // 새 관계도 생성 시에는 optional
|
||||
relationship_name: string;
|
||||
from_table_name: string;
|
||||
from_column_name: string;
|
||||
|
|
@ -89,7 +90,7 @@ export interface TableDataResponse {
|
|||
|
||||
// 관계도 정보 인터페이스
|
||||
export interface DataFlowDiagram {
|
||||
relationshipId: number;
|
||||
diagramId: number;
|
||||
diagramName: string;
|
||||
connectionType: string;
|
||||
relationshipType: string;
|
||||
|
|
@ -173,7 +174,9 @@ export class DataFlowAPI {
|
|||
/**
|
||||
* 테이블 관계 생성
|
||||
*/
|
||||
static async createRelationship(relationship: Omit<TableRelationship, "relationshipId">): Promise<TableRelationship> {
|
||||
static async createRelationship(
|
||||
relationship: any, // 백엔드 API 형식 (camelCase)
|
||||
): Promise<TableRelationship> {
|
||||
try {
|
||||
const response = await apiClient.post<ApiResponse<TableRelationship>>(
|
||||
"/dataflow/table-relationships",
|
||||
|
|
@ -435,11 +438,11 @@ export class DataFlowAPI {
|
|||
}
|
||||
}
|
||||
|
||||
// 특정 관계도의 모든 관계 조회 (relationship_id로)
|
||||
static async getDiagramRelationshipsByRelationshipId(relationshipId: string): Promise<TableRelationship[]> {
|
||||
// 특정 관계도의 모든 관계 조회 (diagram_id로)
|
||||
static async getDiagramRelationshipsByDiagramId(diagramId: number): Promise<TableRelationship[]> {
|
||||
try {
|
||||
const response = await apiClient.get<ApiResponse<TableRelationship[]>>(
|
||||
`/dataflow/relationships/${relationshipId}/diagram`,
|
||||
`/dataflow/diagrams/${diagramId}/relationships`,
|
||||
);
|
||||
|
||||
if (!response.data.success) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue