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:
Alina
2026-02-17 22:02:25 +03:00
commit ef816cdcf4
48 changed files with 8738 additions and 0 deletions

View File

@ -0,0 +1,86 @@
import { Modal, Space, Tag } from 'antd';
import { useSchemaStore } from '../../store/schemaStore.ts';
import { STATUS_COLORS, STATUS_LABELS } from '../../constants/statusColors.ts';
import { EntityStatus, LineStyle, Medium } from '../../types/index.ts';
export function LegendModal() {
const visible = useSchemaStore((s) => s.legendVisible);
const setVisible = useSchemaStore((s) => s.setLegendVisible);
return (
<Modal
title="Легенда"
open={visible}
onCancel={() => setVisible(false)}
footer={null}
width={480}
>
<div style={{ marginBottom: 20 }}>
<h4 style={{ marginBottom: 8 }}>Цвета статусов</h4>
<Space wrap>
{Object.values(EntityStatus).map((status) => {
const colors = STATUS_COLORS[status];
const label = STATUS_LABELS[status];
return (
<Tag
key={status}
style={{
borderColor: colors.border,
backgroundColor: colors.fill,
color: colors.text,
}}
>
{label}
</Tag>
);
})}
</Space>
</div>
<div style={{ marginBottom: 20 }}>
<h4 style={{ marginBottom: 8 }}>Типы линий</h4>
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
{Object.values(LineStyle).map((style) => {
const dasharray =
style === LineStyle.Solid
? ''
: style === LineStyle.Dashed
? '8 4'
: '2 4';
return (
<div key={style} style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<svg width={60} height={20}>
<line
x1={0}
y1={10}
x2={60}
y2={10}
stroke="#333"
strokeWidth={2}
strokeDasharray={dasharray}
/>
</svg>
<span style={{ fontSize: 12 }}>{style}</span>
</div>
);
})}
</div>
</div>
<div>
<h4 style={{ marginBottom: 8 }}>Среда передачи</h4>
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
{Object.values(Medium).map((medium) => (
<div key={medium} style={{ fontSize: 12 }}>
<strong>{medium}</strong>
{medium === Medium.Optical && ' — оптическое волокно'}
{medium === Medium.Copper && ' — медный кабель'}
{medium === Medium.Wireless && ' — беспроводная связь'}
{medium === Medium.Unknown && ' — неизвестная среда'}
</div>
))}
</div>
</div>
</Modal>
);
}