Files
aerbim-ht-monitor/frontend/components/alerts/AlertsList.tsx

207 lines
9.0 KiB
TypeScript
Raw Permalink 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.
import React, { useState, useMemo } from 'react'
import { useRouter } from 'next/navigation'
import useNavigationStore from '../../app/store/navigationStore'
import * as statusColors from '../../lib/statusColors'
interface AlertItem {
id: number
type: string
message: string
timestamp: string
acknowledged: boolean
priority: string
detector_id?: string
detector_name?: string
location?: string
object?: string
serial_number?: string
floor?: number
}
interface AlertsListProps {
alerts: AlertItem[]
onAcknowledgeToggle: (alertId: number) => void
initialSearchTerm?: string
}
const AlertsList: React.FC<AlertsListProps> = ({ alerts, onAcknowledgeToggle, initialSearchTerm = '' }) => {
const router = useRouter()
const { navigateToSensor } = useNavigationStore()
const [searchTerm, setSearchTerm] = useState(initialSearchTerm)
const filteredAlerts = useMemo(() => {
return alerts.filter(alert => {
const matchesSearch = searchTerm === '' || alert.detector_id?.toString() === searchTerm
return matchesSearch
})
}, [alerts, searchTerm])
const interSemiboldStyle = { fontFamily: 'Inter, sans-serif', fontWeight: 600 }
const interRegularStyle = { fontFamily: 'Inter, sans-serif', fontWeight: 400 }
const getStatusColor = (type: string) => {
switch (type) {
case 'critical':
return statusColors.STATUS_COLOR_CRITICAL
case 'warning':
return statusColors.STATUS_COLOR_WARNING
case 'info':
return statusColors.STATUS_COLOR_NORMAL
default:
return statusColors.STATUS_COLOR_UNKNOWN
}
}
const handleGoTo3D = async (alert: AlertItem, viewType: 'building' | 'floor') => {
// Используем доступные идентификаторы датчика
const sensorId = alert.serial_number || alert.detector_name || alert.detector_id
if (!sensorId) {
console.warn('[AlertsList] Alert missing sensor identifier:', alert)
return
}
const result = await navigateToSensor(
sensorId,
alert.floor || null,
viewType
)
if (result) {
router.push(`/navigation?focusSensorId=${encodeURIComponent(result.sensorSerialNumber)}&modelPath=${encodeURIComponent(result.modelPath)}`)
}
}
return (
<div className="space-y-6">
{/* Поиск */}
<div className="flex items-center justify-end gap-4 mb-6">
<div className="relative">
<input
type="text"
placeholder="Поиск по ID детектора..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="bg-[#161824] text-white placeholder-gray-400 px-4 py-2 rounded-lg border border-gray-600 focus:border-blue-500 focus:outline-none w-64"
/>
<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>
{/* Таблица алертов */}
<div className="bg-[#161824] rounded-[20px] p-6">
<div className="mb-4 flex items-center justify-between">
<h2 style={interSemiboldStyle} className="text-xl text-white">История тревог</h2>
<span style={interRegularStyle} className="text-sm text-gray-400">Всего: {filteredAlerts.length}</span>
</div>
<div className="overflow-x-auto">
<table className="w-full">
<thead>
<tr className="border-b border-gray-700">
<th style={interSemiboldStyle} className="text-left text-white text-sm py-3">Детектор</th>
<th style={interSemiboldStyle} className="text-left text-white text-sm py-3">Статус</th>
<th style={interSemiboldStyle} className="text-left text-white text-sm py-3">Сообщение</th>
<th style={interSemiboldStyle} className="text-left text-white text-sm py-3">Приоритет</th>
<th style={interSemiboldStyle} className="text-left text-white text-sm py-3">Подтверждено</th>
<th style={interSemiboldStyle} className="text-left text-white text-sm py-3">Время</th>
<th style={interSemiboldStyle} className="text-center text-white text-sm py-3">3D Вид</th>
</tr>
</thead>
<tbody>
{filteredAlerts.map((item) => (
<tr key={item.id} className="border-b border-gray-700 hover:bg-gray-800/50 transition-colors">
<td style={interRegularStyle} className="py-4 text-sm text-white">
<div>{item.detector_name || 'Детектор'}</div>
{item.detector_id ? (
<div className="text-gray-400">ID: {item.detector_id}</div>
) : null}
</td>
<td className="py-4">
<div className="flex items-center gap-2">
<div
className="w-3 h-3 rounded-full"
style={{ backgroundColor: getStatusColor(item.type) }}
></div>
<span style={interRegularStyle} className="text-sm text-gray-300">
{item.type === 'critical' ? 'Критический' : item.type === 'warning' ? 'Предупреждение' : 'Информация'}
</span>
</div>
</td>
<td style={interRegularStyle} className="py-4 text-sm text-white">
{item.message}
</td>
<td className="py-4">
<span
className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium text-white"
style={{
backgroundColor:
item.priority === 'high'
? statusColors.STATUS_COLOR_CRITICAL
: item.priority === 'medium'
? statusColors.STATUS_COLOR_WARNING
: statusColors.STATUS_COLOR_NORMAL,
}}
>
{item.priority === 'high' ? 'Высокий' : item.priority === 'medium' ? 'Средний' : 'Низкий'}
</span>
</td>
<td className="py-4">
<span style={interRegularStyle} className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs ${
item.acknowledged ? 'bg-green-600/20 text-green-300 ring-1 ring-green-600/40' : 'bg-red-600/20 text-red-300 ring-1 ring-red-600/40'
}`}>
{item.acknowledged ? 'Да' : 'Нет'}
</span>
<button
onClick={() => onAcknowledgeToggle(item.id)}
style={interRegularStyle}
className="ml-2 inline-flex items-center px-2 py-1 rounded text-xs bg-[#2a2e3e] text-white hover:bg-[#353a4d]"
>
{item.acknowledged ? 'Снять' : 'Подтвердить'}
</button>
</td>
<td style={interRegularStyle} className="py-4 text-sm text-gray-300">
{new Date(item.timestamp).toLocaleString('ru-RU')}
</td>
<td className="py-4">
<div className="flex items-center justify-center gap-2">
<button
onClick={() => handleGoTo3D(item, 'building')}
className="p-1.5 rounded hover:bg-blue-600/20 transition-colors group"
title="Показать на общей модели"
>
<img src="/icons/Building3D.png" alt="Здание" className="w-5 h-5 opacity-70 group-hover:opacity-100" />
</button>
<button
onClick={() => handleGoTo3D(item, 'floor')}
className="p-1.5 rounded hover:bg-blue-600/20 transition-colors group"
title="Показать на этаже"
>
<img
src="/icons/Floor3D.png"
alt="Этаж"
className="w-5 h-5 opacity-70 group-hover:opacity-100"
/>
</button>
</div>
</td>
</tr>
))}
{filteredAlerts.length === 0 && (
<tr>
<td colSpan={7} className="py-8 text-center text-gray-400">
Записей не найдено
</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
</div>
)
}
export default AlertsList