Разработка интерфейса фронт

This commit is contained in:
iv_vuytsik
2025-09-05 03:16:17 +03:00
parent 6c2ea027a4
commit 4d6b7b48d7
35 changed files with 3806 additions and 276 deletions

View File

@@ -0,0 +1,103 @@
'use client'
import React, { useState, useEffect } from 'react'
import { useRouter, useSearchParams } from 'next/navigation'
import Sidebar from '../../../components/ui/Sidebar'
import useNavigationStore from '../../store/navigationStore'
import DetectorList from '../../../components/alerts/DetectorList'
import ExportMenu from '../../../components/ui/ExportMenu'
const AlertsPage: React.FC = () => {
const router = useRouter()
const searchParams = useSearchParams()
const { currentObject, setCurrentObject } = useNavigationStore()
const [selectedDetectors, setSelectedDetectors] = useState<number[]>([])
const urlObjectId = searchParams.get('objectId')
const urlObjectTitle = searchParams.get('objectTitle')
const objectId = currentObject.id || urlObjectId
const objectTitle = currentObject.title || urlObjectTitle
useEffect(() => {
if (urlObjectId && urlObjectTitle && (!currentObject.id || currentObject.id !== urlObjectId)) {
setCurrentObject(urlObjectId, urlObjectTitle)
}
}, [urlObjectId, urlObjectTitle, currentObject.id, setCurrentObject])
const handleBackClick = () => {
router.push('/dashboard')
}
const handleDetectorSelect = (detectorId: number, selected: boolean) => {
if (selected) {
setSelectedDetectors(prev => [...prev, detectorId])
} else {
setSelectedDetectors(prev => prev.filter(id => id !== detectorId))
}
}
const handleExport = (format: 'csv' | 'pdf') => {
// TODO: добавить функционал по экспорту
console.log(`Exporting ${selectedDetectors.length} items as ${format}`)
}
return (
<div className="flex h-screen bg-[#0e111a]">
<Sidebar
activeItem={8} // История тревог
/>
<div className="flex-1 flex flex-col">
<header className="bg-[#161824] border-b border-gray-700 px-6 py-4">
<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>
</header>
<div className="flex-1 p-6 overflow-auto">
<div className="mb-6">
<div className="flex items-center justify-between mb-6">
<h1 className="text-white text-2xl font-semibold">Уведомления и тревоги</h1>
<div className="flex items-center gap-4">
{/* Кол-во выбранных объектов */}
{selectedDetectors.length > 0 && (
<span className="text-gray-300 text-sm">
Выбрано: <span className="text-white font-medium">{selectedDetectors.length}</span>
</span>
)}
<ExportMenu onExport={handleExport} />
</div>
</div>
<DetectorList
objectId={objectId || undefined}
selectedDetectors={selectedDetectors}
onDetectorSelect={handleDetectorSelect}
/>
</div>
</div>
</div>
</div>
)
}
export default AlertsPage

View File

@@ -1,9 +1,23 @@
import React from 'react'
'use client'
const page = () => {
return (
<div>page</div>
)
import React, { useEffect } from 'react'
import { useSearchParams } from 'next/navigation'
import Dashboard from '../../../components/dashboard/Dashboard'
import useNavigationStore from '../../store/navigationStore'
const DashboardPage = () => {
const searchParams = useSearchParams()
const { currentObject, setCurrentObject } = useNavigationStore()
const urlObjectId = searchParams.get('objectId')
const urlObjectTitle = searchParams.get('objectTitle')
useEffect(() => {
if (urlObjectId && urlObjectTitle && (!currentObject.id || currentObject.id !== urlObjectId)) {
setCurrentObject(urlObjectId, urlObjectTitle)
}
}, [urlObjectId, urlObjectTitle, currentObject.id, setCurrentObject])
return <Dashboard />
}
export default page
export default DashboardPage

View File

@@ -0,0 +1,13 @@
import React from 'react'
export default function ProtectedLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div className="protected-layout">
{children}
</div>
)
}

View File

@@ -1,16 +1,9 @@
'use client'
import React, { useState } from 'react'
import ModelViewer from '@/components/ModelViewer'
import ModelViewer from '@/components/model/ModelViewer'
export default function Home() {
const [modelInfo, setModelInfo] = useState<{
meshes: unknown[]
boundingBox: {
min: { x: number; y: number; z: number }
max: { x: number; y: number; z: number }
}
} | null>(null)
const [error, setError] = useState<string | null>(null)
const handleModelLoaded = (data: {
@@ -20,14 +13,12 @@ export default function Home() {
max: { x: number; y: number; z: number }
}
}) => {
setModelInfo(data)
setError(null)
console.log('Model loaded successfully:', data)
}
const handleError = (errorMessage: string) => {
setError(errorMessage)
setModelInfo(null)
}
return (
@@ -42,19 +33,7 @@ export default function Home() {
<div className="absolute top-4 right-4 left-4 z-50 rounded-lg bg-red-600/90 p-4 text-sm text-white md:right-auto md:left-4 md:w-80">
<strong>Error:</strong> {error}
</div>
)}
{modelInfo && (
<div className="absolute top-4 right-4 z-50 max-w-xs rounded-lg bg-black/80 p-4 text-sm text-white">
<h3 className="mb-3 text-base font-semibold">EXPO Building Model</h3>
<div className="space-y-1 text-xs text-gray-300">
<div>🖱 Left click + drag: Rotate</div>
<div>🖱 Right click + drag: Pan</div>
<div>🖱 Scroll: Zoom in/out</div>
</div>
</div>
)}
)}
</div>
)
}

View File

@@ -0,0 +1,241 @@
'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/EXPO_АР_PostRecon_level.gltf'
onModelLoaded={handleModelLoaded}
onError={handleModelError}
/>
</div>
</div>
</div>
</div>
)
}
export default NavigationPage

View File

@@ -1,9 +1,126 @@
import React from 'react'
'use client'
import React, { useState, useEffect } from 'react'
import ObjectGallery from '../../../components/objects/ObjectGallery'
import { ObjectData } from '../../../components/objects/ObjectCard'
import Sidebar from '../../../components/ui/Sidebar'
import detectorsData from '../../../data/detectors.json'
// Интерфейс для данных объекта из JSON
interface RawObjectData {
name: string
title: string
description: string
image?: string
location: string
address: string
floors: number
area: number
type?: string
status?: string
zones: Array<{
zone_id: string
name: string
detectors: number[]
}>
}
// Функция для преобразования данных объекта из JSON
const transformObjectToObjectData = (objectId: string, objectData: RawObjectData): ObjectData => {
return {
object_id: objectId,
title: objectData.title || `Объект ${objectId}`,
description: objectData.description || `Описание объекта ${objectData.title || objectId}`,
image: objectData.image || '/images/default-object.jpg',
location: objectData.location || 'Не указано',
floors: objectData.floors,
area: objectData.area.toString(),
type: objectData.type || 'object',
status: objectData.status || 'active'
}
}
const ObjectsPage: React.FC = () => {
const [objects, setObjects] = useState<ObjectData[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [selectedObjectId, setSelectedObjectId] = useState<string | null>(null)
useEffect(() => {
const loadData = () => {
try {
setLoading(true)
if (detectorsData.objects) {
const transformedObjects = Object.entries(detectorsData.objects).map(
([objectId, objectData]) => transformObjectToObjectData(objectId, objectData)
)
setObjects(transformedObjects)
} else {
throw new Error('Не удалось получить данные объектов')
}
} catch (err) {
console.error('Ошибка при загрузке данных:', err)
setError(err instanceof Error ? err.message : 'Произошла неизвестная ошибка')
} finally {
setLoading(false)
}
}
loadData()
}, [])
const handleObjectSelect = (objectId: string) => {
console.log('Object selected:', objectId)
setSelectedObjectId(objectId)
}
if (loading) {
return (
<div className="flex items-center justify-center min-h-screen bg-[#0e111a]">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500 mx-auto mb-4"></div>
<p className="text-white">Загрузка объектов...</p>
</div>
</div>
)
}
if (error) {
return (
<div className="flex items-center justify-center min-h-screen bg-[#0e111a]">
<div className="text-center">
<div className="text-red-500 mb-4">
<svg className="w-12 h-12 mx-auto" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" />
</svg>
</div>
<h3 className="text-lg font-medium text-white mb-2">Ошибка загрузки</h3>
<p className="text-[#71717a] mb-4">{error}</p>
<button
onClick={() => window.location.reload()}
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-[#3193f5] hover:bg-[#2563eb] focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50 transition-colors duration-200"
>
Попробовать снова
</button>
</div>
</div>
)
}
const page = () => {
return (
<div>page</div>
<div className="flex h-screen bg-[#0e111a]">
<Sidebar activeItem={null} />
<div className="flex-1 overflow-hidden">
<ObjectGallery
objects={objects}
title="Объекты"
onObjectSelect={handleObjectSelect}
selectedObjectId={selectedObjectId}
/>
</div>
</div>
)
}
export default page
export default ObjectsPage

View File

@@ -0,0 +1,85 @@
'use client'
import React, { useEffect } from 'react'
import { useRouter, useSearchParams } from 'next/navigation'
import Sidebar from '../../../components/ui/Sidebar'
import useNavigationStore from '../../store/navigationStore'
import ReportsList from '../../../components/reports/ReportsList'
import ExportMenu from '../../../components/ui/ExportMenu'
import detectorsData from '../../../data/detectors.json'
const ReportsPage: React.FC = () => {
const router = useRouter()
const searchParams = useSearchParams()
const { currentObject, setCurrentObject } = useNavigationStore()
const urlObjectId = searchParams.get('objectId')
const urlObjectTitle = searchParams.get('objectTitle')
const objectId = currentObject.id || urlObjectId
const objectTitle = currentObject.title || urlObjectTitle
useEffect(() => {
if (urlObjectId && urlObjectTitle && (!currentObject.id || currentObject.id !== urlObjectId)) {
setCurrentObject(urlObjectId, urlObjectTitle)
}
}, [urlObjectId, urlObjectTitle, currentObject.id, setCurrentObject])
const handleBackClick = () => {
router.push('/dashboard')
}
const handleExport = (format: 'csv' | 'pdf') => {
// TODO: добавить функционал по экспорту отчетов
console.log(`Exporting reports as ${format}`)
}
return (
<div className="flex h-screen bg-[#0e111a]">
<Sidebar
activeItem={9} // Reports
/>
<div className="flex-1 flex flex-col">
<header className="bg-[#161824] border-b border-gray-700 px-6 py-4">
<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>
</header>
<div className="flex-1 p-6 overflow-auto">
<div className="mb-6">
<div className="flex items-center justify-between mb-6">
<h1 className="text-white text-2xl font-semibold">Отчеты по датчикам</h1>
<ExportMenu onExport={handleExport} />
</div>
<ReportsList
objectId={objectId || undefined}
detectorsData={detectorsData}
/>
</div>
</div>
</div>
</div>
)
}
export default ReportsPage

View File

@@ -1,42 +0,0 @@
import { NextRequest, NextResponse } from 'next/server'
import { writeFile, mkdir } from 'fs/promises'
import { join } from 'path'
export async function POST(request: NextRequest) {
try {
const body = await request.json()
console.log('API: Received request with fileName:', body.fileName)
const { fileName, data } = body
const dataDir = join(process.cwd(), 'data')
const filePath = join(dataDir, fileName)
console.log('API: Writing to:', filePath)
try {
await mkdir(dataDir, { recursive: true })
console.log('API: Data directory created/verified')
} catch (error) {
console.log('API: Data directory already exists')
}
await writeFile(filePath, JSON.stringify(data, null, 2), 'utf8')
console.log('API: File written successfully')
return NextResponse.json({
success: true,
message: 'Mesh data cached successfully',
fileName
})
} catch (error) {
console.error('API: Error caching mesh data:', error)
return NextResponse.json(
{
success: false,
error: `Failed to cache mesh data: ${error}`
},
{ status: 500 }
)
}
}

View File

@@ -0,0 +1,28 @@
import { NextResponse } from 'next/server'
import { readFile } from 'fs/promises'
import { join } from 'path'
export async function GET() {
try {
// Имитация полученных данных в формате json (данные в data/detectors.json)
const filePath = join(process.cwd(), 'frontend', 'data', 'detectors.json')
const fileContent = await readFile(filePath, 'utf8')
const allData = JSON.parse(fileContent)
return NextResponse.json({
success: true,
data: allData,
objectsCount: Object.keys(allData.objects || {}).length,
detectorsCount: Object.keys(allData.detectors || {}).length
})
} catch (error) {
console.error('Error fetching detectors data:', error)
return NextResponse.json(
{
success: false,
error: 'Failed to fetch detectors data'
},
{ status: 500 }
)
}
}

View File

@@ -43,3 +43,34 @@ body {
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
}
/* Стилизация скроллбара */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #0e111a;
border-radius: 4px;
}
::-webkit-scrollbar-thumb {
background: #374151;
border-radius: 4px;
border: 1px solid #1f2937;
}
::-webkit-scrollbar-thumb:hover {
background: #4b5563;
}
::-webkit-scrollbar-corner {
background: #0e111a;
}
/* Стилизация скроллбара - Firefox*/
* {
scrollbar-width: thin;
scrollbar-color: #374151 #0e111a;
}

View File

@@ -0,0 +1,207 @@
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
export interface DetectorType {
detector_id: number
name: string
object: string
status: string
type: string
location: string
floor: number
checked: boolean
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
}
export interface NavigationStore {
currentObject: { id: string | undefined; title: string | undefined }
navigationHistory: string[]
currentSubmenu: string | null
showMonitoring: boolean
showFloorNavigation: boolean
showNotifications: boolean
selectedDetector: DetectorType | null
showDetectorMenu: boolean
selectedNotification: NotificationType | null
showNotificationDetectorInfo: boolean
setCurrentObject: (id: string | undefined, title: string | undefined) => void
clearCurrentObject: () => void
addToHistory: (path: string) => void
goBack: () => string | null
setCurrentSubmenu: (submenu: string | null) => void
clearSubmenu: () => void
openMonitoring: () => void
closeMonitoring: () => void
openFloorNavigation: () => void
closeFloorNavigation: () => void
openNotifications: () => void
closeNotifications: () => void
setSelectedDetector: (detector: DetectorType | null) => void
setShowDetectorMenu: (show: boolean) => void
setSelectedNotification: (notification: NotificationType | null) => void
setShowNotificationDetectorInfo: (show: boolean) => void
isOnNavigationPage: () => boolean
getCurrentRoute: () => string | null
getActiveSidebarItem: () => number
}
const useNavigationStore = create<NavigationStore>()(
persist(
(set, get) => ({
currentObject: {
id: undefined,
title: undefined,
},
navigationHistory: [],
currentSubmenu: null,
showMonitoring: false,
showFloorNavigation: false,
showNotifications: false,
selectedDetector: null,
showDetectorMenu: false,
selectedNotification: null,
showNotificationDetectorInfo: false,
setCurrentObject: (id: string | undefined, title: string | undefined) =>
set({ currentObject: { id, title } }),
clearCurrentObject: () =>
set({ currentObject: { id: undefined, title: undefined } }),
addToHistory: (path: string) => {
const { navigationHistory } = get()
const newHistory = [...navigationHistory, path]
if (newHistory.length > 10) {
newHistory.shift()
}
set({ navigationHistory: newHistory })
},
goBack: () => {
const { navigationHistory } = get()
if (navigationHistory.length > 1) {
const newHistory = [...navigationHistory]
newHistory.pop()
const previousPage = newHistory.pop()
set({ navigationHistory: newHistory })
return previousPage || null
}
return null
},
setCurrentSubmenu: (submenu: string | null) =>
set({ currentSubmenu: submenu }),
clearSubmenu: () =>
set({ currentSubmenu: null }),
openMonitoring: () => set({
showMonitoring: true,
showFloorNavigation: false,
showNotifications: false,
currentSubmenu: 'monitoring',
showDetectorMenu: false,
selectedDetector: null,
showNotificationDetectorInfo: false,
selectedNotification: null
}),
closeMonitoring: () => set({
showMonitoring: false,
currentSubmenu: null
}),
openFloorNavigation: () => set({
showFloorNavigation: true,
showMonitoring: false,
showNotifications: false,
currentSubmenu: 'floors',
showNotificationDetectorInfo: false,
selectedNotification: null
}),
closeFloorNavigation: () => set({
showFloorNavigation: false,
showDetectorMenu: false,
selectedDetector: null,
currentSubmenu: null
}),
openNotifications: () => set({
showNotifications: true,
showMonitoring: false,
showFloorNavigation: false,
currentSubmenu: 'notifications',
showDetectorMenu: false,
selectedDetector: null
}),
closeNotifications: () => set({
showNotifications: false,
showNotificationDetectorInfo: false,
selectedNotification: 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 }),
isOnNavigationPage: () => {
const { navigationHistory } = get()
const currentRoute = navigationHistory[navigationHistory.length - 1]
return currentRoute === '/navigation'
},
getCurrentRoute: () => {
const { navigationHistory } = get()
return navigationHistory[navigationHistory.length - 1] || null
},
getActiveSidebarItem: () => {
const { showMonitoring, showFloorNavigation, showNotifications } = get()
if (showMonitoring) return 3 // Зоны Мониторинга
if (showFloorNavigation) return 4 // Навигация по этажам
if (showNotifications) return 5 // Уведомления
return 2 // Навигация (базовая)
}
}),
{
name: 'navigation-store',
}
)
)
export default useNavigationStore

View File

@@ -0,0 +1,31 @@
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
interface UIState {
isSidebarCollapsed: boolean
isNavigationSubMenuExpanded: boolean
setSidebarCollapsed: (collapsed: boolean) => void
toggleSidebar: () => void
setNavigationSubMenuExpanded: (expanded: boolean) => void
toggleNavigationSubMenu: () => void
}
const useUIStore = create<UIState>()(
persist(
(set, get) => ({
isSidebarCollapsed: false,
isNavigationSubMenuExpanded: false,
setSidebarCollapsed: (collapsed: boolean) => set({ isSidebarCollapsed: collapsed }),
toggleSidebar: () => set({ isSidebarCollapsed: !get().isSidebarCollapsed }),
setNavigationSubMenuExpanded: (expanded: boolean) => set({ isNavigationSubMenuExpanded: expanded }),
toggleNavigationSubMenu: () => set({ isNavigationSubMenuExpanded: !get().isNavigationSubMenuExpanded }),
}),
{
name: 'ui-store',
}
)
)
export default useUIStore