изменение логики работы поиска датчиков из дашборд и истории тревог по конкретному этажу

This commit is contained in:
2026-02-05 22:21:25 +03:00
parent 6caf1c9dbb
commit bad3b63911
13 changed files with 2861 additions and 793 deletions

View File

@@ -0,0 +1,317 @@
'use client'
import React, { useEffect, useRef, useState } from 'react'
import { AbstractMesh, Vector3 } from '@babylonjs/core'
interface Canvas2DPlanProps {
meshes: AbstractMesh[]
sensorStatusMap: Record<string, string>
onClose: () => void
onSensorClick?: (sensorId: string) => void
}
interface Sensor2D {
id: string
x: number
y: number
status: string
}
const Canvas2DPlan: React.FC<Canvas2DPlanProps> = ({
meshes,
sensorStatusMap,
onClose,
onSensorClick,
}) => {
const canvasRef = useRef<HTMLCanvasElement>(null)
const [sensors, setSensors] = useState<Sensor2D[]>([])
const [hoveredSensor, setHoveredSensor] = useState<string | null>(null)
const [scale, setScale] = useState(10)
const [offset, setOffset] = useState({ x: 0, y: 0 })
const [isDragging, setIsDragging] = useState(false)
const [dragStart, setDragStart] = useState({ x: 0, y: 0 })
// Извлечение датчиков из mesh'ей
useEffect(() => {
const extractedSensors: Sensor2D[] = []
console.log('[Canvas2DPlan] Extracting sensors from meshes:', meshes.length)
console.log('[Canvas2DPlan] sensorStatusMap:', sensorStatusMap)
let meshesWithMetadata = 0
let meshesWithSensorID = 0
let meshesInStatusMap = 0
meshes.forEach((mesh, index) => {
if (mesh.metadata) {
meshesWithMetadata++
if (index < 3) {
console.log(`[Canvas2DPlan] Sample mesh[${index}] metadata:`, mesh.metadata)
}
}
const sensorId = mesh.metadata?.Sensor_ID
if (sensorId) {
meshesWithSensorID++
if (index < 3) {
console.log(`[Canvas2DPlan] Sample mesh[${index}] Sensor_ID:`, sensorId, 'in map?', !!sensorStatusMap[sensorId])
}
}
if (sensorId && sensorStatusMap[sensorId]) {
meshesInStatusMap++
const position = mesh.getAbsolutePosition()
extractedSensors.push({
id: sensorId,
x: position.x,
y: position.z, // Используем Z как Y для вида сверху
status: sensorStatusMap[sensorId],
})
}
})
console.log('[Canvas2DPlan] Meshes with metadata:', meshesWithMetadata)
console.log('[Canvas2DPlan] Meshes with Sensor_ID:', meshesWithSensorID)
console.log('[Canvas2DPlan] Meshes in statusMap:', meshesInStatusMap)
console.log('[Canvas2DPlan] Extracted sensors:', extractedSensors.length, extractedSensors)
setSensors(extractedSensors)
// Автоматическое центрирование
if (extractedSensors.length > 0 && canvasRef.current) {
const minX = Math.min(...extractedSensors.map((s) => s.x))
const maxX = Math.max(...extractedSensors.map((s) => s.x))
const minY = Math.min(...extractedSensors.map((s) => s.y))
const maxY = Math.max(...extractedSensors.map((s) => s.y))
const centerX = (minX + maxX) / 2
const centerY = (minY + maxY) / 2
const canvas = canvasRef.current
setOffset({
x: canvas.width / 2 - centerX * scale,
y: canvas.height / 2 - centerY * scale,
})
}
}, [meshes, sensorStatusMap, scale])
// Рендеринг canvas
useEffect(() => {
const canvas = canvasRef.current
if (!canvas) return
const ctx = canvas.getContext('2d')
if (!ctx) return
// Очистка
ctx.clearRect(0, 0, canvas.width, canvas.height)
// Фон
ctx.fillStyle = '#0e111a'
ctx.fillRect(0, 0, canvas.width, canvas.height)
// Сетка
ctx.strokeStyle = '#1a1d2e'
ctx.lineWidth = 1
const gridSize = 50
for (let x = 0; x < canvas.width; x += gridSize) {
ctx.beginPath()
ctx.moveTo(x, 0)
ctx.lineTo(x, canvas.height)
ctx.stroke()
}
for (let y = 0; y < canvas.height; y += gridSize) {
ctx.beginPath()
ctx.moveTo(0, y)
ctx.lineTo(canvas.width, y)
ctx.stroke()
}
// Рисуем датчики
sensors.forEach((sensor) => {
const x = sensor.x * scale + offset.x
const y = sensor.y * scale + offset.y
// Определяем цвет по статусу
let color = '#6b7280' // gray
if (sensor.status === 'critical') color = '#ef4444' // red
else if (sensor.status === 'warning') color = '#f59e0b' // amber
else if (sensor.status === 'normal') color = '#10b981' // green
// Внешний круг (подсветка при hover)
if (hoveredSensor === sensor.id) {
ctx.fillStyle = color + '40'
ctx.beginPath()
ctx.arc(x, y, 20, 0, Math.PI * 2)
ctx.fill()
}
// Основной круг датчика
ctx.fillStyle = color
ctx.beginPath()
ctx.arc(x, y, 8, 0, Math.PI * 2)
ctx.fill()
// Обводка
ctx.strokeStyle = '#ffffff'
ctx.lineWidth = 2
ctx.stroke()
// Подпись
ctx.fillStyle = '#ffffff'
ctx.font = '12px Inter, sans-serif'
ctx.textAlign = 'center'
ctx.fillText(sensor.id, x, y - 15)
})
// Легенда
const legendX = 20
const legendY = canvas.height - 80
ctx.fillStyle = '#161824cc'
ctx.fillRect(legendX - 10, legendY - 10, 180, 70)
const statuses = [
{ label: 'Критический', color: '#ef4444' },
{ label: 'Предупреждение', color: '#f59e0b' },
{ label: 'Нормальный', color: '#10b981' },
]
statuses.forEach((status, index) => {
const y = legendY + index * 20
ctx.fillStyle = status.color
ctx.beginPath()
ctx.arc(legendX, y, 6, 0, Math.PI * 2)
ctx.fill()
ctx.fillStyle = '#ffffff'
ctx.font = '12px Inter, sans-serif'
ctx.textAlign = 'left'
ctx.fillText(status.label, legendX + 15, y + 4)
})
}, [sensors, scale, offset, hoveredSensor])
// Обработка клика
const handleCanvasClick = (e: React.MouseEvent<HTMLCanvasElement>) => {
const canvas = canvasRef.current
if (!canvas) return
const rect = canvas.getBoundingClientRect()
const clickX = e.clientX - rect.left
const clickY = e.clientY - rect.top
// Проверяем клик по датчику
for (const sensor of sensors) {
const x = sensor.x * scale + offset.x
const y = sensor.y * scale + offset.y
const distance = Math.sqrt((clickX - x) ** 2 + (clickY - y) ** 2)
if (distance <= 10) {
onSensorClick?.(sensor.id)
return
}
}
}
// Обработка hover
const handleCanvasMove = (e: React.MouseEvent<HTMLCanvasElement>) => {
if (isDragging) {
const dx = e.clientX - dragStart.x
const dy = e.clientY - dragStart.y
setOffset((prev) => ({ x: prev.x + dx, y: prev.y + dy }))
setDragStart({ x: e.clientX, y: e.clientY })
return
}
const canvas = canvasRef.current
if (!canvas) return
const rect = canvas.getBoundingClientRect()
const mouseX = e.clientX - rect.left
const mouseY = e.clientY - rect.top
let foundSensor: string | null = null
for (const sensor of sensors) {
const x = sensor.x * scale + offset.x
const y = sensor.y * scale + offset.y
const distance = Math.sqrt((mouseX - x) ** 2 + (mouseY - y) ** 2)
if (distance <= 10) {
foundSensor = sensor.id
break
}
}
setHoveredSensor(foundSensor)
}
// Обработка zoom
const handleWheel = (e: React.WheelEvent<HTMLCanvasElement>) => {
e.preventDefault()
const delta = e.deltaY > 0 ? 0.9 : 1.1
setScale((prev) => Math.max(1, Math.min(50, prev * delta)))
}
// Обработка drag
const handleMouseDown = (e: React.MouseEvent<HTMLCanvasElement>) => {
setIsDragging(true)
setDragStart({ x: e.clientX, y: e.clientY })
}
const handleMouseUp = () => {
setIsDragging(false)
}
return (
<div className="fixed inset-0 z-[100] bg-black/80 flex items-center justify-center p-4">
<div className="relative bg-[#161824] rounded-lg shadow-2xl border border-white/10 p-6 w-full h-full max-w-[1400px] max-h-[900px] flex flex-col">
{/* Заголовок */}
<div className="flex items-center justify-between mb-4">
<h2 className="text-xl font-semibold text-white">2D План-схема</h2>
<button
onClick={onClose}
className="text-gray-400 hover:text-white transition-colors"
>
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
</div>
{/* Canvas */}
<div className="flex-1 min-h-0">
<canvas
ref={canvasRef}
width={1200}
height={700}
onClick={handleCanvasClick}
onMouseMove={handleCanvasMove}
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}
onMouseLeave={handleMouseUp}
onWheel={handleWheel}
className="border border-white/10 rounded cursor-move w-full h-full"
style={{ cursor: isDragging ? 'grabbing' : 'grab', maxHeight: '700px' }}
/>
</div>
{/* Подсказка */}
<div className="mt-4 text-sm text-gray-400 text-center">
<p>Колесико мыши - масштаб | Перетаскивание - перемещение | Клик по датчику - подробности</p>
</div>
</div>
</div>
)
}
export default Canvas2DPlan