Added list of detectors and sensors sub-menus

This commit is contained in:
iv_vuytsik
2025-11-19 09:48:17 +03:00
parent 46045f0b0a
commit eba7585a5b
9 changed files with 814 additions and 19 deletions

View File

@@ -0,0 +1,171 @@
'use client'
import React, { useState } from 'react'
interface DetectorsDataType {
detectors: Record<string, DetectorType>
}
interface ListOfDetectorsProps {
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 ListOfDetectors: React.FC<ListOfDetectorsProps> = ({ objectId, detectorsData, onDetectorMenuClick, onClose, is3DReady = true }) => {
const [searchTerm, setSearchTerm] = useState('')
// конвертация детекторов в array и фильтруем по 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()) ||
detector.serial_number.toLowerCase().includes(searchTerm.toLowerCase())
)
}
// Сортировка детекторов по имени
const sortedDetectors = filteredDetectors.sort((a, b) => a.name.localeCompare(b.name))
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('[ListOfDetectors] 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>
<div className="text-gray-400 text-sm">
{sortedDetectors.length} детекторов
</div>
</div>
<div className="space-y-2 h-full overflow-y-auto">
{sortedDetectors.length === 0 ? (
<div className="text-gray-400 text-center py-8">
Детекторы не найдены
</div>
) : (
sortedDetectors.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('[ListOfDetectors] 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>
)
}
export default ListOfDetectors