feat: frontend MVP — детальная схема связей устройств (AntV X6)
- React 18 + TypeScript strict + AntV X6 2.x + AntD 5 + Zustand - Custom nodes: SiteNode, CrossDeviceNode, SpliceNode, DeviceNode, CardNode - 8-слойный автолейаут, порты (left/right), линии с цветами по статусу - Toolbar, дерево навигации, карточка объекта, таблица соединений - Контекстные меню, легенда, drag линий/нод, создание линий из портов - Моковые данные: 3 сайта, 10 устройств, 15 линий Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
76
frontend/src/features/schema/edges/edgeGrouping.ts
Normal file
76
frontend/src/features/schema/edges/edgeGrouping.ts
Normal file
@ -0,0 +1,76 @@
|
||||
import type { Line, Port, Device, SchemaData } from '../../../types/index.ts';
|
||||
import { STATUS_COLORS, STATUS_LABELS } from '../../../constants/statusColors.ts';
|
||||
|
||||
export interface LineGroup {
|
||||
key: string;
|
||||
deviceAId: string;
|
||||
deviceZId: string;
|
||||
lines: Line[];
|
||||
representativeLine: Line;
|
||||
count: number;
|
||||
}
|
||||
|
||||
function getDeviceIdForPort(
|
||||
portId: string,
|
||||
data: SchemaData,
|
||||
): string | null {
|
||||
const port = data.ports.find((p: Port) => p.id === portId);
|
||||
return port ? port.deviceId : null;
|
||||
}
|
||||
|
||||
export function groupLinesByDevicePair(data: SchemaData): LineGroup[] {
|
||||
const groupMap = new Map<string, Line[]>();
|
||||
|
||||
for (const line of data.lines) {
|
||||
const devA = getDeviceIdForPort(line.portAId, data);
|
||||
const devZ = getDeviceIdForPort(line.portZId, data);
|
||||
if (!devA || !devZ) continue;
|
||||
|
||||
const key = [devA, devZ].sort().join('::');
|
||||
const existing = groupMap.get(key);
|
||||
if (existing) {
|
||||
existing.push(line);
|
||||
} else {
|
||||
groupMap.set(key, [line]);
|
||||
}
|
||||
}
|
||||
|
||||
const groups: LineGroup[] = [];
|
||||
for (const [key, lines] of groupMap.entries()) {
|
||||
const [deviceAId, deviceZId] = key.split('::');
|
||||
groups.push({
|
||||
key,
|
||||
deviceAId,
|
||||
deviceZId,
|
||||
lines,
|
||||
representativeLine: lines[0],
|
||||
count: lines.length,
|
||||
});
|
||||
}
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
export function getGroupTooltip(
|
||||
group: LineGroup,
|
||||
devices: Device[],
|
||||
): string {
|
||||
const devA = devices.find((d) => d.id === group.deviceAId);
|
||||
const devZ = devices.find((d) => d.id === group.deviceZId);
|
||||
|
||||
const statusCounts = new Map<string, number>();
|
||||
for (const line of group.lines) {
|
||||
const current = statusCounts.get(line.status) ?? 0;
|
||||
statusCounts.set(line.status, current + 1);
|
||||
}
|
||||
|
||||
let tooltip = `${devA?.name ?? '?'} — ${devZ?.name ?? '?'}\n`;
|
||||
tooltip += `Линий: ${group.count}\n`;
|
||||
for (const [status, count] of statusCounts.entries()) {
|
||||
const color = STATUS_COLORS[status as keyof typeof STATUS_COLORS];
|
||||
const label = STATUS_LABELS[status as keyof typeof STATUS_LABELS];
|
||||
tooltip += ` ${label}: ${count} (${color.border})\n`;
|
||||
}
|
||||
|
||||
return tooltip;
|
||||
}
|
||||
Reference in New Issue
Block a user