239 lines
7.9 KiB
TypeScript
239 lines
7.9 KiB
TypeScript
'use client'
|
||
|
||
import React, { useEffect, useCallback } from 'react'
|
||
import { useRouter, useSearchParams } from 'next/navigation'
|
||
import Sidebar from '../../../components/ui/Sidebar'
|
||
import useNavigationStore from '../../store/navigationStore'
|
||
import Monitoring from '../../../components/navigation/Monitoring'
|
||
import FloorNavigation from '../../../components/navigation/FloorNavigation'
|
||
import DetectorMenu from '../../../components/navigation/DetectorMenu'
|
||
import Notifications from '../../../components/notifications/Notifications'
|
||
import NotificationDetectorInfo from '../../../components/notifications/NotificationDetectorInfo'
|
||
import ModelViewer from '../../../components/model/ModelViewer'
|
||
import detectorsData from '../../../data/detectors.json'
|
||
|
||
interface DetectorType {
|
||
detector_id: number
|
||
name: string
|
||
object: string
|
||
status: string
|
||
checked: boolean
|
||
type: string
|
||
location: string
|
||
floor: number
|
||
notifications: Array<{
|
||
id: number
|
||
type: string
|
||
message: string
|
||
timestamp: string
|
||
acknowledged: boolean
|
||
priority: string
|
||
}>
|
||
}
|
||
|
||
interface NotificationType {
|
||
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()
|
||
const {
|
||
currentObject,
|
||
setCurrentObject,
|
||
showMonitoring,
|
||
showFloorNavigation,
|
||
showNotifications,
|
||
selectedDetector,
|
||
showDetectorMenu,
|
||
selectedNotification,
|
||
showNotificationDetectorInfo,
|
||
closeMonitoring,
|
||
closeFloorNavigation,
|
||
closeNotifications,
|
||
setSelectedDetector,
|
||
setShowDetectorMenu,
|
||
setSelectedNotification,
|
||
setShowNotificationDetectorInfo
|
||
} = useNavigationStore()
|
||
|
||
const urlObjectId = searchParams.get('objectId')
|
||
const urlObjectTitle = searchParams.get('objectTitle')
|
||
const objectId = currentObject.id || urlObjectId
|
||
const objectTitle = currentObject.title || urlObjectTitle
|
||
|
||
const handleModelLoaded = useCallback(() => {
|
||
}, [])
|
||
|
||
const handleModelError = useCallback((error: string) => {
|
||
console.error('Model loading error:', error)
|
||
}, [])
|
||
|
||
useEffect(() => {
|
||
if (urlObjectId && urlObjectTitle && (!currentObject.id || currentObject.id !== urlObjectId)) {
|
||
setCurrentObject(urlObjectId, urlObjectTitle)
|
||
}
|
||
}, [urlObjectId, urlObjectTitle, currentObject.id, setCurrentObject])
|
||
|
||
const handleBackClick = () => {
|
||
router.push('/dashboard')
|
||
}
|
||
|
||
const handleDetectorMenuClick = (detector: DetectorType) => {
|
||
if (selectedDetector?.detector_id === detector.detector_id && showDetectorMenu) {
|
||
setShowDetectorMenu(false)
|
||
setSelectedDetector(null)
|
||
} else {
|
||
setSelectedDetector(detector)
|
||
setShowDetectorMenu(true)
|
||
}
|
||
}
|
||
|
||
const closeDetectorMenu = () => {
|
||
setShowDetectorMenu(false)
|
||
setSelectedDetector(null)
|
||
}
|
||
|
||
const handleNotificationClick = (notification: NotificationType) => {
|
||
if (selectedNotification?.id === notification.id && showNotificationDetectorInfo) {
|
||
setShowNotificationDetectorInfo(false)
|
||
setSelectedNotification(null)
|
||
} else {
|
||
setSelectedNotification(notification)
|
||
setShowNotificationDetectorInfo(true)
|
||
}
|
||
}
|
||
|
||
const closeNotificationDetectorInfo = () => {
|
||
setShowNotificationDetectorInfo(false)
|
||
setSelectedNotification(null)
|
||
}
|
||
|
||
const getStatusText = (status: string) => {
|
||
switch (status) {
|
||
case 'active': return 'Активен'
|
||
case 'inactive': return 'Неактивен'
|
||
case 'error': return 'Ошибка'
|
||
case 'maintenance': return 'Обслуживание'
|
||
default: return 'Неизвестно'
|
||
}
|
||
}
|
||
|
||
return (
|
||
<div className="flex h-screen bg-[#0e111a]">
|
||
<Sidebar
|
||
activeItem={2}
|
||
/>
|
||
|
||
<div className="flex-1 flex flex-col relative">
|
||
|
||
{showMonitoring && (
|
||
<div className="absolute left-0 top-0 bg-[#161824] border-r border-gray-700 z-20 w-[500px]" style={{height: 'calc(100% - 73px)', top: '73px'}}>
|
||
<div className="h-full overflow-auto p-4">
|
||
<Monitoring
|
||
objectId={objectId || undefined}
|
||
onClose={closeMonitoring}
|
||
/>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{showFloorNavigation && (
|
||
<div className="absolute left-0 top-0 bg-[#161824] border-r border-gray-700 z-20 w-[500px]" style={{height: 'calc(100% - 73px)', top: '73px'}}>
|
||
<div className="h-full overflow-auto p-4">
|
||
<FloorNavigation
|
||
objectId={objectId || undefined}
|
||
detectorsData={detectorsData}
|
||
onDetectorMenuClick={handleDetectorMenuClick}
|
||
onClose={closeFloorNavigation}
|
||
/>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{showNotifications && (
|
||
<div className="absolute left-0 top-0 bg-[#161824] border-r border-gray-700 z-20 w-[500px]" style={{height: 'calc(100% - 73px)', top: '73px'}}>
|
||
<div className="h-full overflow-auto p-4">
|
||
<Notifications
|
||
objectId={objectId || undefined}
|
||
detectorsData={detectorsData}
|
||
onNotificationClick={handleNotificationClick}
|
||
onClose={closeNotifications}
|
||
/>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{showNotifications && showNotificationDetectorInfo && selectedNotification && (() => {
|
||
const detectorData = Object.values(detectorsData.detectors).find(
|
||
detector => detector.detector_id === selectedNotification.detector_id
|
||
);
|
||
return detectorData ? (
|
||
<div className="absolute left-[500px] top-0 bg-[#161824] border-r border-gray-700 z-30 w-[454px]" style={{height: 'calc(100% - 73px)', top: '73px'}}>
|
||
<div className="h-full overflow-auto p-4">
|
||
<NotificationDetectorInfo
|
||
detectorData={detectorData}
|
||
onClose={closeNotificationDetectorInfo}
|
||
/>
|
||
</div>
|
||
</div>
|
||
) : null;
|
||
})()}
|
||
|
||
{showFloorNavigation && showDetectorMenu && selectedDetector && (
|
||
<DetectorMenu
|
||
detector={selectedDetector}
|
||
isOpen={showDetectorMenu}
|
||
onClose={closeDetectorMenu}
|
||
getStatusText={getStatusText}
|
||
/>
|
||
)}
|
||
|
||
<header className="bg-[#161824] border-b border-gray-700 px-6 py-4">
|
||
<div className="flex items-center justify-between">
|
||
<div className="flex items-center gap-4">
|
||
<button
|
||
onClick={handleBackClick}
|
||
className="text-gray-400 hover:text-white transition-colors"
|
||
aria-label="Назад к дашборду"
|
||
>
|
||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
|
||
</svg>
|
||
</button>
|
||
<nav className="flex items-center gap-2 text-sm">
|
||
<span className="text-gray-400">Дашборд</span>
|
||
<span className="text-gray-600">/</span>
|
||
<span className="text-white">{objectTitle || 'Объект'}</span>
|
||
<span className="text-gray-600">/</span>
|
||
<span className="text-white">Навигация</span>
|
||
</nav>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<div className="flex-1 overflow-hidden">
|
||
<div className="h-full">
|
||
<ModelViewer
|
||
modelPath='/models/AerBIM-Monitor_ASM-HT-Viewer_Expo2017Astana_20250908_L_+1430.glb'
|
||
onModelLoaded={handleModelLoaded}
|
||
onError={handleModelError}
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default NavigationPage |