From eba7585a5b31b91a123e34113b293be2bf259986 Mon Sep 17 00:00:00 2001 From: iv_vuytsik Date: Wed, 19 Nov 2025 09:48:17 +0300 Subject: [PATCH] Added list of detectors and sensors sub-menus --- .../account/serializers/alert_serializers.py | 2 + frontend/app/(protected)/navigation/page.tsx | 120 +++++++++-- frontend/app/store/navigationStore.ts | 95 ++++++++- frontend/components/model/ModelViewer.tsx | 2 +- frontend/components/navigation/AlertMenu.tsx | 199 ++++++++++++++++++ .../components/navigation/ListOfDetectors.tsx | 171 +++++++++++++++ frontend/components/navigation/Monitoring.tsx | 1 - frontend/components/navigation/Sensors.tsx | 180 ++++++++++++++++ frontend/components/ui/Sidebar.tsx | 63 +++++- 9 files changed, 814 insertions(+), 19 deletions(-) create mode 100644 frontend/components/navigation/AlertMenu.tsx create mode 100644 frontend/components/navigation/ListOfDetectors.tsx create mode 100644 frontend/components/navigation/Sensors.tsx diff --git a/backend/api/account/serializers/alert_serializers.py b/backend/api/account/serializers/alert_serializers.py index 2f1fd49..111f371 100644 --- a/backend/api/account/serializers/alert_serializers.py +++ b/backend/api/account/serializers/alert_serializers.py @@ -22,12 +22,14 @@ class AlertSerializer(serializers.ModelSerializer): def get_object(self, obj) -> Optional[str]: zone = obj.sensor.zones.first() return zone.object.title if zone else None + @extend_schema_field(OpenApiTypes.STR) def get_metric_value(self, obj) -> str: if obj.metric.value is not None: unit = obj.sensor.signal_format.unit if obj.sensor.signal_format else '' return f"{obj.metric.value} {unit}".strip() return obj.metric.raw_value + @extend_schema_field(OpenApiTypes.STR) def get_detector_type(self, obj) -> str: sensor_type = getattr(obj, 'sensor_type', None) diff --git a/frontend/app/(protected)/navigation/page.tsx b/frontend/app/(protected)/navigation/page.tsx index 36a7edb..2bb17ed 100644 --- a/frontend/app/(protected)/navigation/page.tsx +++ b/frontend/app/(protected)/navigation/page.tsx @@ -7,6 +7,9 @@ import useNavigationStore from '../../store/navigationStore' import Monitoring from '../../../components/navigation/Monitoring' import FloorNavigation from '../../../components/navigation/FloorNavigation' import DetectorMenu from '../../../components/navigation/DetectorMenu' +import ListOfDetectors from '../../../components/navigation/ListOfDetectors' +import Sensors from '../../../components/navigation/Sensors' +import AlertMenu from '../../../components/navigation/AlertMenu' import Notifications from '../../../components/notifications/Notifications' import NotificationDetectorInfo from '../../../components/notifications/NotificationDetectorInfo' import dynamic from 'next/dynamic' @@ -56,6 +59,20 @@ interface NotificationType { priority: string } +interface AlertType { + id: number + detector_id: number + detector_name: string + type: string + status: string + message: string + timestamp: string + location: string + object: string + acknowledged: boolean + priority: string +} + const NavigationPage: React.FC = () => { const router = useRouter() const searchParams = useSearchParams() @@ -65,17 +82,25 @@ const NavigationPage: React.FC = () => { showMonitoring, showFloorNavigation, showNotifications, + showListOfDetectors, + showSensors, selectedDetector, showDetectorMenu, selectedNotification, showNotificationDetectorInfo, + selectedAlert, + showAlertMenu, closeMonitoring, closeFloorNavigation, closeNotifications, + closeListOfDetectors, + closeSensors, setSelectedDetector, setShowDetectorMenu, setSelectedNotification, - setShowNotificationDetectorInfo + setShowNotificationDetectorInfo, + setSelectedAlert, + setShowAlertMenu } = useNavigationStore() const [detectorsData, setDetectorsData] = useState<{ detectors: Record }>({ detectors: {} }) @@ -181,6 +206,32 @@ const NavigationPage: React.FC = () => { setSelectedNotification(null) } + const closeAlertMenu = () => { + setShowAlertMenu(false) + setSelectedAlert(null) + } + + const handleAlertClick = (alert: AlertType) => { + console.log('[NavigationPage] Alert clicked, focusing on detector in 3D scene:', alert) + + const detector = Object.values(detectorsData.detectors).find( + d => d.detector_id === alert.detector_id + ) + + if (detector) { + console.log('[NavigationPage] Found detector for alert:', detector) + + setSelectedAlert(alert) + setShowAlertMenu(true) + + setSelectedDetector(detector) + + console.log('[NavigationPage] Showing AlertMenu for alert:', alert.detector_name) + } else { + console.warn('[NavigationPage] Could not find detector for alert:', alert.detector_id) + } + } + const getStatusText = (status: string) => { const s = (status || '').toLowerCase() switch (s) { @@ -253,6 +304,40 @@ const NavigationPage: React.FC = () => { )} + {showListOfDetectors && ( +
+
+ + {detectorsError && ( +
{detectorsError}
+ )} +
+
+ )} + + {showSensors && ( +
+
+ + {detectorsError && ( +
{detectorsError}
+ )} +
+
+ )} + {showNotifications && showNotificationDetectorInfo && selectedNotification && (() => { const detectorData = Object.values(detectorsData.detectors).find( detector => detector.detector_id === selectedNotification.detector_id @@ -334,18 +419,29 @@ const NavigationPage: React.FC = () => { modelPath={selectedModelPath} onModelLoaded={handleModelLoaded} onError={handleModelError} - focusSensorId={selectedDetector?.serial_number ?? null} + focusSensorId={selectedDetector?.serial_number ?? selectedAlert?.detector_id?.toString() ?? null} renderOverlay={({ anchor }) => ( - selectedDetector && showDetectorMenu && anchor ? ( - - ) : null + <> + {selectedAlert && showAlertMenu && anchor ? ( + + ) : selectedDetector && showDetectorMenu && anchor ? ( + + ) : null} + )} /> )} diff --git a/frontend/app/store/navigationStore.ts b/frontend/app/store/navigationStore.ts index 383d659..ea8dc30 100644 --- a/frontend/app/store/navigationStore.ts +++ b/frontend/app/store/navigationStore.ts @@ -36,6 +36,20 @@ interface NotificationType { priority: string } +interface AlertType { + id: number + detector_id: number + detector_name: string + type: string + status: string + message: string + timestamp: string + location: string + object: string + acknowledged: boolean + priority: string +} + export interface NavigationStore { currentObject: { id: string | undefined; title: string | undefined } navigationHistory: string[] @@ -44,11 +58,15 @@ export interface NavigationStore { showMonitoring: boolean showFloorNavigation: boolean showNotifications: boolean + showListOfDetectors: boolean + showSensors: boolean selectedDetector: DetectorType | null showDetectorMenu: boolean selectedNotification: NotificationType | null showNotificationDetectorInfo: boolean + selectedAlert: AlertType | null + showAlertMenu: boolean setCurrentObject: (id: string | undefined, title: string | undefined) => void clearCurrentObject: () => void @@ -64,11 +82,20 @@ export interface NavigationStore { closeFloorNavigation: () => void openNotifications: () => void closeNotifications: () => void + openListOfDetectors: () => void + closeListOfDetectors: () => void + openSensors: () => void + closeSensors: () => void + + // Close all menus and submenus + closeAllMenus: () => void setSelectedDetector: (detector: DetectorType | null) => void setShowDetectorMenu: (show: boolean) => void setSelectedNotification: (notification: NotificationType | null) => void setShowNotificationDetectorInfo: (show: boolean) => void + setSelectedAlert: (alert: AlertType | null) => void + setShowAlertMenu: (show: boolean) => void isOnNavigationPage: () => boolean getCurrentRoute: () => string | null @@ -88,11 +115,15 @@ const useNavigationStore = create()( showMonitoring: false, showFloorNavigation: false, showNotifications: false, + showListOfDetectors: false, + showSensors: false, selectedDetector: null, showDetectorMenu: false, selectedNotification: null, showNotificationDetectorInfo: false, + selectedAlert: null, + showAlertMenu: false, setCurrentObject: (id: string | undefined, title: string | undefined) => set({ currentObject: { id, title } }), @@ -131,6 +162,7 @@ const useNavigationStore = create()( showMonitoring: true, showFloorNavigation: false, showNotifications: false, + showListOfDetectors: false, currentSubmenu: 'monitoring', showDetectorMenu: false, selectedDetector: null, @@ -147,6 +179,7 @@ const useNavigationStore = create()( showFloorNavigation: true, showMonitoring: false, showNotifications: false, + showListOfDetectors: false, currentSubmenu: 'floors', showNotificationDetectorInfo: false, selectedNotification: null @@ -163,6 +196,7 @@ const useNavigationStore = create()( showNotifications: true, showMonitoring: false, showFloorNavigation: false, + showListOfDetectors: false, currentSubmenu: 'notifications', showDetectorMenu: false, selectedDetector: null @@ -174,11 +208,68 @@ const useNavigationStore = create()( selectedNotification: null, currentSubmenu: null }), + + openListOfDetectors: () => set({ + showListOfDetectors: true, + showMonitoring: false, + showFloorNavigation: false, + showNotifications: false, + currentSubmenu: 'detectors', + showDetectorMenu: false, + selectedDetector: null, + showNotificationDetectorInfo: false, + selectedNotification: null + }), + + closeListOfDetectors: () => set({ + showListOfDetectors: false, + showDetectorMenu: false, + selectedDetector: null, + currentSubmenu: null + }), + + openSensors: () => set({ + showSensors: true, + showMonitoring: false, + showFloorNavigation: false, + showNotifications: false, + showListOfDetectors: false, + currentSubmenu: 'sensors', + showDetectorMenu: false, + selectedDetector: null, + showNotificationDetectorInfo: false, + selectedNotification: null + }), + + closeSensors: () => set({ + showSensors: false, + showDetectorMenu: false, + selectedDetector: null, + currentSubmenu: null + }), + + // Close all menus and submenus + closeAllMenus: () => set({ + showMonitoring: false, + showFloorNavigation: false, + showNotifications: false, + showListOfDetectors: false, + showSensors: false, + showDetectorMenu: false, + selectedDetector: null, + showNotificationDetectorInfo: false, + selectedNotification: null, + showAlertMenu: false, + selectedAlert: null, + currentSubmenu: null + }), setSelectedDetector: (detector: DetectorType | null) => set({ selectedDetector: detector }), setShowDetectorMenu: (show: boolean) => set({ showDetectorMenu: show }), setSelectedNotification: (notification: NotificationType | null) => set({ selectedNotification: notification }), setShowNotificationDetectorInfo: (show: boolean) => set({ showNotificationDetectorInfo: show }), + setSelectedAlert: (alert: AlertType | null) => set({ selectedAlert: alert }), + setShowAlertMenu: (show: boolean) => set({ showAlertMenu: show }), isOnNavigationPage: () => { const { navigationHistory } = get() @@ -192,10 +283,12 @@ const useNavigationStore = create()( }, getActiveSidebarItem: () => { - const { showMonitoring, showFloorNavigation, showNotifications } = get() + const { showMonitoring, showFloorNavigation, showNotifications, showListOfDetectors, showSensors } = get() if (showMonitoring) return 3 // Зоны Мониторинга if (showFloorNavigation) return 4 // Навигация по этажам if (showNotifications) return 5 // Уведомления + if (showListOfDetectors) return 7 // Список датчиков + if (showSensors) return 8 // Сенсоры return 2 // Навигация (базовая) } }), diff --git a/frontend/components/model/ModelViewer.tsx b/frontend/components/model/ModelViewer.tsx index 6fede8c..adca0c8 100644 --- a/frontend/components/model/ModelViewer.tsx +++ b/frontend/components/model/ModelViewer.tsx @@ -319,7 +319,7 @@ const ModelViewer: React.FC = ({ const meta: any = (m as any)?.metadata const extras: any = meta?.gltf?.extras ?? meta?.extras ?? (m as any)?.extras - const sid = extras?.Sensor_ID ?? extras?.sensor_id ?? extras?.SERIAL_NUMBER ?? extras?.serial_number + const sid = extras?.Sensor_ID ?? extras?.sensor_id ?? extras?.SERIAL_NUMBER ?? extras?.serial_number ?? extras?.detector_id if (sid != null) { return String(sid).trim() === sensorId } diff --git a/frontend/components/navigation/AlertMenu.tsx b/frontend/components/navigation/AlertMenu.tsx new file mode 100644 index 0000000..284ce89 --- /dev/null +++ b/frontend/components/navigation/AlertMenu.tsx @@ -0,0 +1,199 @@ +'use client' + +import React from 'react' + +interface AlertType { + id: number + detector_id: number + detector_name: string + type: string + status: string + message: string + timestamp: string + location: string + object: string + acknowledged: boolean + priority: string +} + +interface AlertMenuProps { + alert: AlertType + isOpen: boolean + onClose: () => void + getStatusText: (status: string) => string + compact?: boolean + anchor?: { left: number; top: number } | null +} + +const AlertMenu: React.FC = ({ alert, isOpen, onClose, getStatusText, compact = false, anchor = null }) => { + if (!isOpen) return null + + const formatDate = (dateString: string) => { + const date = new Date(dateString) + return date.toLocaleString('ru-RU', { + day: '2-digit', + month: '2-digit', + year: 'numeric', + hour: '2-digit', + minute: '2-digit' + }) + } + + const getPriorityColor = (priority: string) => { + switch (priority.toLowerCase()) { + case 'high': return 'text-red-400' + case 'medium': return 'text-orange-400' + case 'low': return 'text-green-400' + default: return 'text-gray-400' + } + } + + const getPriorityText = (priority: string) => { + switch (priority.toLowerCase()) { + case 'high': return 'Высокий' + case 'medium': return 'Средний' + case 'low': return 'Низкий' + default: return priority + } + } + + if (compact && anchor) { + return ( +
+
+
+
+
Датч.{alert.detector_name}
+
{getStatusText(alert.status)}
+
+ +
+ +
+
+
+
Приоритет
+
+ {getPriorityText(alert.priority)} +
+
+
+
Время
+
{formatDate(alert.timestamp)}
+
+
+ +
+
Сообщение
+
{alert.message}
+
+ +
+
Местоположение
+
{alert.location}
+
+
+ +
+ + +
+
+
+ ) + } + + return ( +
+
+
+

+ Датч.{alert.detector_name} +

+
+ + +
+
+ + {/* Табличка с информацией об алерте */} +
+
+
+
Маркировка по проекту
+
{alert.detector_name}
+
+
+
Приоритет
+
+ {getPriorityText(alert.priority)} +
+
+
+ +
+
+
Местоположение
+
{alert.location}
+
+
+
Статус
+
{getStatusText(alert.status)}
+
+
+ +
+
+
Время события
+
{formatDate(alert.timestamp)}
+
+
+
Тип алерта
+
{alert.type}
+
+
+ +
+
Сообщение
+
{alert.message}
+
+
+ + +
+
+ ) +} + +export default AlertMenu \ No newline at end of file diff --git a/frontend/components/navigation/ListOfDetectors.tsx b/frontend/components/navigation/ListOfDetectors.tsx new file mode 100644 index 0000000..bd22612 --- /dev/null +++ b/frontend/components/navigation/ListOfDetectors.tsx @@ -0,0 +1,171 @@ +'use client' + +import React, { useState } from 'react' + +interface DetectorsDataType { + detectors: Record +} + +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 = ({ 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 ( +
+
+
+

Список датчиков

+ {onClose && ( + + )} +
+ +
+
+ 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" + /> + + + +
+
+ {sortedDetectors.length} детекторов +
+
+ +
+ {sortedDetectors.length === 0 ? ( +
+ Детекторы не найдены +
+ ) : ( + sortedDetectors.map(detector => ( +
+
+
{detector.name}
+
{detector.location}
+
+
+
+ {getStatusText(detector.status)} + {detector.checked && ( + + + + )} + +
+
+ )) + )} +
+
+
+ ) +} + +export default ListOfDetectors \ No newline at end of file diff --git a/frontend/components/navigation/Monitoring.tsx b/frontend/components/navigation/Monitoring.tsx index fa8679b..590d348 100644 --- a/frontend/components/navigation/Monitoring.tsx +++ b/frontend/components/navigation/Monitoring.tsx @@ -8,7 +8,6 @@ interface MonitoringProps { } const Monitoring: React.FC = ({ onClose, onSelectModel }) => { - const [objectImageError, setObjectImageError] = useState(false); const [models, setModels] = useState<{ title: string; path: string }[]>([]); const [loadError, setLoadError] = useState(null); diff --git a/frontend/components/navigation/Sensors.tsx b/frontend/components/navigation/Sensors.tsx new file mode 100644 index 0000000..b26031c --- /dev/null +++ b/frontend/components/navigation/Sensors.tsx @@ -0,0 +1,180 @@ +'use client' + +import React, { useState } from 'react' + +interface AlertType { + id: number + detector_id: number + detector_name: string + type: string + status: string + message: string + timestamp: string + location: string + object: string + acknowledged: boolean + priority: string +} + +interface DetectorType { + detector_id: number + name: string + serial_number: string + object: string + status: string + type: string + detector_type: string + location: string + floor: number + checked: boolean + notifications: Array<{ + id: number + type: string + message: string + timestamp: string + acknowledged: boolean + priority: string + }> +} + +interface DetectorsDataType { + detectors: Record +} + +interface SensorsProps { + objectId?: string + detectorsData: DetectorsDataType + onAlertClick: (alert: AlertType) => void + onClose?: () => void + is3DReady?: boolean +} + +const Sensors: React.FC = ({ objectId, detectorsData, onAlertClick, onClose, is3DReady = true }) => { + const [searchTerm, setSearchTerm] = useState('') + + // Получаем все уведомления (как в компоненте Notifications) + const alerts = React.useMemo(() => { + const notifications: AlertType[] = []; + Object.values(detectorsData.detectors).forEach(detector => { + if (detector.notifications && detector.notifications.length > 0) { + detector.notifications.forEach(notification => { + notifications.push({ + ...notification, + detector_id: detector.detector_id, + detector_name: detector.name, + location: detector.location, + object: detector.object, + status: detector.status + }); + }); + } + }); + + // Фильтруем по objectId + const filteredAlerts = objectId + ? notifications.filter(alert => alert.object && alert.object.toString() === objectId.toString()) + : notifications + + // Дополнительная фильтрация по поиску + const finalAlerts = filteredAlerts.filter(alert => { + return searchTerm === '' || + (alert.detector_name && alert.detector_name.toLowerCase().includes(searchTerm.toLowerCase())) || + (alert.message && alert.message.toLowerCase().includes(searchTerm.toLowerCase())) || + (alert.location && alert.location.toLowerCase().includes(searchTerm.toLowerCase())) + }) + + // Сортируем по timestamp (новые сверху) + return finalAlerts.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()) + }, [detectorsData, objectId, searchTerm]) + + const getStatusColor = (type: string) => { + switch (type.toLowerCase()) { + case 'critical': return 'bg-red-500' + case 'warning': return 'bg-orange-500' + case 'info': return 'bg-green-500' + default: return 'bg-gray-500' + } + } + + + + return ( +
+
+
+

Сенсоры - Уведомления

+ +
+ +
+
+ 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" + /> + + + +
+ +
+ +
+ {alerts.length === 0 ? ( +
+

Уведомления не найдены

+
+ ) : ( + alerts.map(alert => ( +
+
+
+
+
{alert.detector_name}
+
{alert.message}
+
+
+ +
+ )) + )} +
+
+
+ ) +} + +export default Sensors \ No newline at end of file diff --git a/frontend/components/ui/Sidebar.tsx b/frontend/components/ui/Sidebar.tsx index d26927f..0e75d6a 100644 --- a/frontend/components/ui/Sidebar.tsx +++ b/frontend/components/ui/Sidebar.tsx @@ -185,12 +185,19 @@ const Sidebar: React.FC = ({ openMonitoring, openFloorNavigation, openNotifications, + openListOfDetectors, + openSensors, closeMonitoring, closeFloorNavigation, closeNotifications, + closeListOfDetectors, + closeSensors, + closeAllMenus, showMonitoring, showFloorNavigation, - showNotifications + showNotifications, + showListOfDetectors, + showSensors } = useNavigationStore() useEffect(() => { @@ -222,10 +229,14 @@ const Sidebar: React.FC = ({ case 3: // Monitoring if (pathname !== '/navigation') { router.push('/navigation') - setTimeout(() => openMonitoring(), 100) + setTimeout(() => { + closeAllMenus() + openMonitoring() + }, 100) } else if (showMonitoring) { closeMonitoring() } else { + closeAllMenus() openMonitoring() } handled = true @@ -233,10 +244,14 @@ const Sidebar: React.FC = ({ case 4: // Floor Navigation if (pathname !== '/navigation') { router.push('/navigation') - setTimeout(() => openFloorNavigation(), 100) + setTimeout(() => { + closeAllMenus() + openFloorNavigation() + }, 100) } else if (showFloorNavigation) { closeFloorNavigation() } else { + closeAllMenus() openFloorNavigation() } handled = true @@ -244,14 +259,48 @@ const Sidebar: React.FC = ({ case 5: // Notifications if (pathname !== '/navigation') { router.push('/navigation') - setTimeout(() => openNotifications(), 100) + setTimeout(() => { + closeAllMenus() + openNotifications() + }, 100) } else if (showNotifications) { closeNotifications() } else { + closeAllMenus() openNotifications() } handled = true break + case 6: // Sensors + if (pathname !== '/navigation') { + router.push('/navigation') + setTimeout(() => { + closeAllMenus() + openSensors() + }, 100) + } else if (showSensors) { + closeSensors() + } else { + closeAllMenus() + openSensors() + } + handled = true + break + case 7: // List of Detectors + if (pathname !== '/navigation') { + router.push('/navigation') + setTimeout(() => { + closeAllMenus() + openListOfDetectors() + }, 100) + } else if (showListOfDetectors) { + closeListOfDetectors() + } else { + closeAllMenus() + openListOfDetectors() + } + handled = true + break default: // Для остального используем routes if (navigationService) { @@ -330,6 +379,8 @@ const Sidebar: React.FC = ({ closeMonitoring() closeFloorNavigation() closeNotifications() + closeListOfDetectors() + closeSensors() } toggleNavigationSubMenu() }} @@ -344,6 +395,8 @@ const Sidebar: React.FC = ({ closeMonitoring() closeFloorNavigation() closeNotifications() + closeListOfDetectors() + closeSensors() } toggleNavigationSubMenu() } @@ -408,6 +461,8 @@ const Sidebar: React.FC = ({ closeMonitoring() closeFloorNavigation() closeNotifications() + closeListOfDetectors() + closeSensors() } // Убираем сайд-бар toggleSidebar()