import type { SchemaData } from '../../../types/index'; import { DeviceCategory } from '../../../types/index'; import type { GraphNodeConfig, GraphEdgeConfig, GraphBuildResult } from '../../../types/graph'; import { createPortItem } from '../ports/port-config'; import { createEdgeConfig } from '../edges/edge-config'; import { autoLayout, type LayoutResult } from '../layout/auto-layout'; import { resolvePortSides } from './port-side-resolver'; import { CROSS_WIDTH, CROSS_HEIGHT, SPLICE_SIZE, DEVICE_MIN_WIDTH, DEVICE_MIN_HEIGHT, DEVICE_HEADER_HEIGHT, CARD_WIDTH, CARD_HEIGHT, } from '../../../constants/sizes'; function getDeviceShape(category: DeviceCategory, deviceName: string, marking: string): string { if ( category === DeviceCategory.CrossOptical || category === DeviceCategory.CrossCopper ) { return 'cross-device-node'; } if ( deviceName.toLowerCase().includes('муфта') || marking.toLowerCase().includes('мток') ) { return 'splice-node'; } return 'device-node'; } function getDeviceSize( category: DeviceCategory, deviceName: string, marking: string, portCount: number, cardCount: number, ): { width: number; height: number } { if ( category === DeviceCategory.CrossOptical || category === DeviceCategory.CrossCopper ) { return { width: CROSS_WIDTH, height: CROSS_HEIGHT }; } if ( deviceName.toLowerCase().includes('муфта') || marking.toLowerCase().includes('мток') ) { return { width: SPLICE_SIZE, height: SPLICE_SIZE }; } const portHeight = Math.max(portCount * 22, 60); const cardsHeight = cardCount > 0 ? cardCount * (CARD_HEIGHT + 6) + 8 : 0; const bodyHeight = cardCount > 0 && portCount > 0 ? cardsHeight + portHeight : Math.max(portHeight, cardsHeight); return { width: DEVICE_MIN_WIDTH, height: Math.max(DEVICE_MIN_HEIGHT, DEVICE_HEADER_HEIGHT + bodyHeight + 10), }; } export function buildGraphData( data: SchemaData, routerType: 'manhattan' | 'normal' = 'manhattan', ): GraphBuildResult { const layout: LayoutResult = autoLayout(data); const nodes: GraphNodeConfig[] = []; const edges: GraphEdgeConfig[] = []; // Resolve port sides based on device positions const devicePositions = new Map(); for (const [id, pos] of layout.nodePositions.entries()) { devicePositions.set(id, { x: pos.x, y: pos.y }); } const portSideMap = resolvePortSides(data, devicePositions); // 1. Create site nodes for (const site of data.sites) { const sitePos = layout.sitePositions.get(site.id); if (!sitePos) continue; nodes.push({ id: site.id, shape: 'site-node', x: sitePos.x, y: sitePos.y, width: sitePos.width, height: sitePos.height, data: { name: site.name, address: site.address, erpCode: site.erpCode, code1C: site.code1C, status: site.status, entityType: 'site', entityId: site.id, }, zIndex: 1, }); } // 2. Create device nodes with ports for (const device of data.devices) { const pos = layout.nodePositions.get(device.id); if (!pos) continue; const devicePorts = data.ports.filter( (p) => p.deviceId === device.id && !p.cardId, ); const shape = getDeviceShape(device.category, device.name, device.marking); const deviceCards = data.cards.filter((c) => c.deviceId === device.id && c.visible); const size = getDeviceSize(device.category, device.name, device.marking, devicePorts.length, deviceCards.length); const hasCards = deviceCards.length > 0; const cardsEndY = hasCards ? DEVICE_HEADER_HEIGHT + deviceCards.length * (CARD_HEIGHT + 6) + 8 : 0; const leftDevicePorts = devicePorts.filter( (p) => (portSideMap.get(p.id) ?? p.side) === 'left', ); const rightDevicePorts = devicePorts.filter( (p) => (portSideMap.get(p.id) ?? p.side) === 'right', ); const portItems = devicePorts.map((port) => { const resolvedSide = portSideMap.get(port.id) ?? port.side; const label = port.slotName ? `${port.slotName}:${port.name}` : port.name; let portArgs: { x: number; y: number } | undefined; if (hasCards) { const sameSidePorts = resolvedSide === 'left' ? leftDevicePorts : rightDevicePorts; const indexInSide = sameSidePorts.indexOf(port); const portSpacing = 22; portArgs = { x: resolvedSide === 'left' ? 0 : size.width, y: cardsEndY + portSpacing * (indexInSide + 0.5), }; } return createPortItem(port.id, resolvedSide, label, port.labelColor || undefined, portArgs); }); const leftPosition = hasCards ? 'absolute' : 'left'; const rightPosition = hasCards ? 'absolute' : 'right'; const node: GraphNodeConfig = { id: device.id, shape, x: pos.x, y: pos.y, width: size.width, height: size.height, data: { name: device.name, networkName: device.networkName, ipAddress: device.ipAddress, marking: device.marking, id1: device.id1, id2: device.id2, group: device.group, category: device.category, status: device.status, entityType: 'device', entityId: device.id, }, ports: { groups: { left: { position: leftPosition, attrs: { circle: { r: 6, magnet: true, stroke: '#8c8c8c', strokeWidth: 1, fill: '#fff', }, }, label: { position: { name: 'top', args: { y: -8 } }, }, }, right: { position: rightPosition, attrs: { circle: { r: 6, magnet: true, stroke: '#8c8c8c', strokeWidth: 1, fill: '#fff', }, }, label: { position: { name: 'top', args: { y: -8 } }, }, }, }, items: portItems, }, parent: device.siteId, zIndex: 2, }; nodes.push(node); } // 3. Create card nodes (embedded inside devices) for (const card of data.cards) { if (!card.visible) continue; const parentDevice = data.devices.find((d) => d.id === card.deviceId); if (!parentDevice) continue; const parentPos = layout.nodePositions.get(card.deviceId); if (!parentPos) continue; const cardPorts = data.ports.filter((p) => p.cardId === card.id); const cardIndex = data.cards.filter( (c) => c.deviceId === card.deviceId && c.visible, ).indexOf(card); const cardX = parentPos.x + 10; const cardY = parentPos.y + DEVICE_HEADER_HEIGHT + 8 + cardIndex * (CARD_HEIGHT + 6); const portItems = cardPorts.map((port) => { const resolvedSide = portSideMap.get(port.id) ?? port.side; const label = `${port.slotName}:${port.name}`; return createPortItem(port.id, resolvedSide, label, port.labelColor || undefined); }); nodes.push({ id: card.id, shape: 'card-node', x: cardX, y: cardY, width: CARD_WIDTH, height: CARD_HEIGHT, data: { slotName: card.slotName, networkName: card.networkName, status: card.status, entityType: 'card', entityId: card.id, }, ports: { groups: { left: { position: 'left', attrs: { circle: { r: 5, magnet: true, stroke: '#8c8c8c', strokeWidth: 1, fill: '#fff', }, }, label: { position: { name: 'top', args: { y: -6 } }, }, }, right: { position: 'right', attrs: { circle: { r: 5, magnet: true, stroke: '#8c8c8c', strokeWidth: 1, fill: '#fff', }, }, label: { position: { name: 'top', args: { y: -6 } }, }, }, }, items: portItems, }, parent: card.deviceId, zIndex: 3, }); } // 4. Create edges from lines for (const line of data.lines) { const portA = data.ports.find((p) => p.id === line.portAId); const portZ = data.ports.find((p) => p.id === line.portZId); if (!portA || !portZ) continue; const sourceCellId = portA.cardId ?? portA.deviceId; const targetCellId = portZ.cardId ?? portZ.deviceId; edges.push( createEdgeConfig( line.id, sourceCellId, line.portAId, targetCellId, line.portZId, line.status, line.medium, line.lineStyle, line.name, routerType, ), ); } return { nodes, edges }; }