Files
aerbim-ht-monitor/frontend/components/navigation/ListOfDetectors.tsx
2026-01-21 03:16:52 +03:00

181 lines
6.9 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'
import * as statusColors from '../../lib/statusColors'
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 statusColors.STATUS_COLOR_CRITICAL:
return 'bg-red-500'
case statusColors.STATUS_COLOR_WARNING:
return 'bg-orange-500'
case statusColors.STATUS_COLOR_NORMAL:
return 'bg-green-500'
default:
return 'bg-gray-500'
}
}
const getStatusText = (status: string) => {
switch (status) {
case statusColors.STATUS_COLOR_CRITICAL:
return 'Критический'
case statusColors.STATUS_COLOR_WARNING:
return 'Предупреждение'
case statusColors.STATUS_COLOR_NORMAL:
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