Files
aerbim-ht-monitor/frontend/components/navigation/FloorNavigation.tsx

226 lines
9.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import React, { useState } from 'react'
interface DetectorsDataType {
detectors: Record<string, DetectorType>
}
interface FloorNavigationProps {
objectId?: string
detectorsData: DetectorsDataType
onDetectorMenuClick: (detector: DetectorType) => void
onClose?: () => void
is3DReady?: boolean
}
interface DetectorType {
detector_id: number
name: string
serial_number: string
object: string
status: string
checked: boolean
type: string
detector_type: string
location: string
floor: number
notifications: Array<{
id: number
type: string
message: string
timestamp: string
acknowledged: boolean
priority: string
}>
}
const FloorNavigation: React.FC<FloorNavigationProps> = (props) => {
const { objectId, detectorsData, onDetectorMenuClick, onClose, is3DReady = true } = props
const [expandedFloors, setExpandedFloors] = useState<Set<number>>(new Set())
const [searchTerm, setSearchTerm] = useState('')
// Преобразование detectors в массив и фильтрация по objectId и поисковому запросу
const detectorsArray = Object.values(detectorsData.detectors) as DetectorType[]
let filteredDetectors = objectId
? detectorsArray.filter(detector => detector.object === objectId)
: detectorsArray
// Фильтр-поиск
if (searchTerm) {
filteredDetectors = filteredDetectors.filter(detector =>
detector.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
detector.location.toLowerCase().includes(searchTerm.toLowerCase())
)
}
// Группиовка детекторов по этажам
const detectorsByFloor = filteredDetectors.reduce((acc, detector) => {
const floor = detector.floor
if (!acc[floor]) {
acc[floor] = []
}
acc[floor].push(detector)
return acc
}, {} as Record<number, DetectorType[]>)
// Сортировка этажей
const sortedFloors = Object.keys(detectorsByFloor)
.map(Number)
.sort((a, b) => a - b)
const toggleFloor = (floor: number) => {
const newExpanded = new Set(expandedFloors)
if (newExpanded.has(floor)) {
newExpanded.delete(floor)
} else {
newExpanded.add(floor)
}
setExpandedFloors(newExpanded)
}
const getStatusColor = (status: string) => {
switch (status) {
case '#b3261e': return 'bg-red-500'
case '#fd7c22': return 'bg-orange-500'
case '#00ff00': return 'bg-green-500'
default: return 'bg-gray-500'
}
}
const getStatusText = (status: string) => {
switch (status) {
case '#b3261e': return 'Критический'
case '#fd7c22': return 'Предупреждение'
case '#00ff00': return 'Норма'
default: return 'Неизвестно'
}
}
const handleDetectorMenuClick = (detector: DetectorType) => {
// Проверяем валидность данных детектора перед передачей
if (!detector || !detector.detector_id || !detector.serial_number) {
console.warn('[FloorNavigation] Invalid detector data, skipping menu click:', detector)
return
}
onDetectorMenuClick(detector)
}
return (
<div className="w-full max-w-2xl">
<div className="bg-[rgb(22,24,36)] rounded-[12px] p-4 space-y-4">
<div className="flex items-center justify-between">
<h2 className="text-white text-2xl font-semibold">Навигация по этажам</h2>
{onClose && (
<button
onClick={onClose}
className="text-white hover:text-gray-300 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>
<div className="flex items-center gap-3">
<div className="flex-1 relative">
<input
type="text"
placeholder="Поиск детекторов..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full bg-[rgb(27,30,40)] text-white placeholder-gray-400 px-4 py-2 rounded-lg border border-gray-600 focus:border-blue-500 focus:outline-none"
/>
<svg className="absolute right-3 top-2.5 w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
<button className="bg-[rgb(27,29,41)] hover:bg-[rgb(37,39,51)] text-white px-4 py-2 rounded-lg transition-colors flex items-center gap-2">
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 100 4m0-4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 100 4m0-4v2m0-6V4" />
</svg>
Фильтр
</button>
</div>
<div className="space-y-2">
{sortedFloors.map(floor => {
const floorDetectors = detectorsByFloor[floor]
const isExpanded = expandedFloors.has(floor)
return (
<div key={floor} className="bg-[rgb(27,30,40)] rounded-lg overflow-hidden">
<button
onClick={() => toggleFloor(floor)}
className="w-full px-4 py-3 flex items-center justify-between hover:bg-[rgb(53,58,70)] transition-colors"
>
<div className="flex items-center gap-3">
<span className="text-white font-medium">{floor} этаж</span>
<span className="text-gray-400 text-sm">({floorDetectors.length} детекторов)</span>
</div>
<svg
className={`w-5 h-5 text-gray-400 transition-transform ${
isExpanded ? 'rotate-180' : ''
}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</button>
{/* суб-меню с детекторами */}
{isExpanded && (
<div className="px-4 pb-3 space-y-2">
{floorDetectors.map(detector => (
<div
key={detector.detector_id}
className="bg-[rgb(53,58,70)] rounded-md p-3 flex items-center justify-between"
>
<div className="flex-1">
<div className="text-white text-sm font-medium">{detector.name}</div>
<div className="text-gray-400 text-xs">{detector.location}</div>
</div>
<div className="flex items-center gap-2">
<div className={`w-3 h-3 rounded-full ${getStatusColor(detector.status)}`}></div>
<span className="text-xs text-gray-300">{getStatusText(detector.status)}</span>
{detector.checked && (
<svg className="w-4 h-4 text-green-400" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
</svg>
)}
<button
onClick={() => {
if (is3DReady) {
handleDetectorMenuClick(detector)
} else {
console.warn('[FloorNavigation] 3D model not ready, skipping detector focus')
}
}}
className="w-6 h-6 bg-[rgb(27,29,41)] hover:bg-[rgb(37,39,51)] rounded-full flex items-center justify-center transition-colors relative"
title={is3DReady ? "Показать детектор на 3D модели" : "3D модель недоступна"}
>
<div className="w-2 h-2 bg-white rounded-full"></div>
{!is3DReady && (
<div className="absolute -top-1 -right-1 w-3 h-3 bg-amber-500 rounded-full text-[8px] flex items-center justify-center text-black font-bold">!</div>
)}
</button>
</div>
</div>
))}
</div>
)}
</div>
)
})}
</div>
</div>
</div>
)
}
export default FloorNavigation