first
This commit is contained in:
@@ -71,61 +71,145 @@ const LoginPage = () => {
|
||||
return <Loader />
|
||||
}
|
||||
|
||||
const interSemiboldStyle = { fontFamily: 'Inter, sans-serif', fontWeight: 600 }
|
||||
const interRegularStyle = { fontFamily: 'Inter, sans-serif', fontWeight: 400 }
|
||||
|
||||
return (
|
||||
<div className="relative flex h-screen flex-col items-center justify-center gap-8 py-8">
|
||||
<div className="mb-4 flex items-center justify-center gap-4">
|
||||
<div className="relative min-h-screen w-full flex flex-col items-center justify-center gap-8 py-8 overflow-hidden">
|
||||
<style>{`
|
||||
@keyframes float {
|
||||
0%, 100% { transform: translateY(0px) rotate(0deg); }
|
||||
50% { transform: translateY(-20px) rotate(180deg); }
|
||||
}
|
||||
@keyframes glow {
|
||||
0%, 100% { opacity: 0.3; }
|
||||
50% { opacity: 0.8; }
|
||||
}
|
||||
@keyframes rotate {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
@keyframes slideIn {
|
||||
0% { transform: translateX(-100%); opacity: 0; }
|
||||
100% { transform: translateX(0); opacity: 1; }
|
||||
}
|
||||
.float-animation {
|
||||
animation: float 6s ease-in-out infinite;
|
||||
}
|
||||
.glow-animation {
|
||||
animation: glow 3s ease-in-out infinite;
|
||||
}
|
||||
.rotate-animation {
|
||||
animation: rotate 20s linear infinite;
|
||||
}
|
||||
.slide-in {
|
||||
animation: slideIn 0.8s ease-out;
|
||||
}
|
||||
`}</style>
|
||||
|
||||
{/* Фоновый градиент - многоуровневый */}
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-[#050810] via-[#0f1729] to-[#1a1f28] z-0"></div>
|
||||
|
||||
{/* Второй слой градиента */}
|
||||
<div className="absolute inset-0 bg-gradient-to-tr from-transparent via-[#1a3a52]/20 to-transparent z-0"></div>
|
||||
|
||||
{/* Основные светящиеся орбиты */}
|
||||
<div className="absolute top-1/4 left-1/4 w-96 h-96 bg-gradient-to-br from-blue-600/20 to-cyan-500/10 rounded-full blur-3xl glow-animation" style={{ animationDelay: '0s' }}></div>
|
||||
<div className="absolute bottom-1/4 right-1/4 w-96 h-96 bg-gradient-to-tl from-blue-500/20 to-cyan-500/10 rounded-full blur-3xl glow-animation" style={{ animationDelay: '1s' }}></div>
|
||||
<div className="absolute top-1/2 right-1/3 w-80 h-80 bg-gradient-to-bl from-cyan-500/15 to-blue-400/10 rounded-full blur-3xl glow-animation" style={{ animationDelay: '2s' }}></div>
|
||||
|
||||
{/* Дополнительные акцентные элементы */}
|
||||
<div className="absolute top-0 right-0 w-72 h-72 bg-blue-500/5 rounded-full blur-2xl float-animation"></div>
|
||||
<div className="absolute bottom-0 left-0 w-72 h-72 bg-cyan-500/5 rounded-full blur-2xl float-animation" style={{ animationDelay: '3s' }}></div>
|
||||
|
||||
{/* Сетка с градиентом */}
|
||||
<div className="absolute inset-0 opacity-5 z-0" style={{
|
||||
backgroundImage: `
|
||||
linear-gradient(0deg, transparent 24%, rgba(59, 147, 245, 0.08) 25%, rgba(59, 147, 245, 0.08) 26%, transparent 27%, transparent 74%, rgba(59, 147, 245, 0.08) 75%, rgba(59, 147, 245, 0.08) 76%, transparent 77%, transparent),
|
||||
linear-gradient(90deg, transparent 24%, rgba(59, 147, 245, 0.08) 25%, rgba(59, 147, 245, 0.08) 26%, transparent 27%, transparent 74%, rgba(59, 147, 245, 0.08) 75%, rgba(59, 147, 245, 0.08) 76%, transparent 77%, transparent)
|
||||
`,
|
||||
backgroundSize: '60px 60px'
|
||||
}}></div>
|
||||
|
||||
{/* Диагональные линии */}
|
||||
<div className="absolute inset-0 opacity-3 z-0" style={{
|
||||
backgroundImage: `
|
||||
repeating-linear-gradient(45deg, transparent, transparent 35px, rgba(59, 147, 245, 0.1) 35px, rgba(59, 147, 245, 0.1) 70px)
|
||||
`
|
||||
}}></div>
|
||||
|
||||
{/* Центральный светящийся элемент */}
|
||||
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-1 h-1 bg-cyan-400 rounded-full shadow-2xl" style={{
|
||||
boxShadow: '0 0 60px 20px rgba(34, 211, 238, 0.15), 0 0 100px 40px rgba(59, 147, 245, 0.08)'
|
||||
}}></div>
|
||||
|
||||
{/* Верхний логотип */}
|
||||
<div className="relative z-10 mb-4 flex items-center justify-center gap-4 slide-in">
|
||||
<Image src="/icons/logo.png" alt="AerBIM Logo" width={438} height={60} />
|
||||
</div>
|
||||
<div className="bg-cards z-10 mx-4 flex w-full max-w-xl flex-col gap-4 rounded-2xl p-6 shadow-lg md:mx-8">
|
||||
|
||||
{/* Карточка формы с улучшенным стилем */}
|
||||
<div className="relative z-10 mx-4 flex w-full max-w-md flex-col gap-6 rounded-[20px] bg-[#161824]/80 p-8 shadow-2xl border border-cyan-500/20 backdrop-blur-xl slide-in" style={{ animationDelay: '0.2s' }}>
|
||||
{/* Верхний градиент на карточке */}
|
||||
<div className="absolute top-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-cyan-500/50 to-transparent rounded-t-[20px]"></div>
|
||||
|
||||
<form
|
||||
className="flex flex-col items-center justify-between gap-8 md:flex-row md:gap-4"
|
||||
className="flex flex-col gap-6"
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<div className="flex w-full flex-col gap-4">
|
||||
<h1 className="text-2xl font-bold">Авторизация</h1>
|
||||
<TextInput
|
||||
value={values.login}
|
||||
name="login"
|
||||
handleChange={handleChange}
|
||||
placeholder="ivan_ivanov"
|
||||
style="register"
|
||||
label="Ваш логин"
|
||||
/>
|
||||
<div className="flex flex-col gap-6">
|
||||
<h1 style={interSemiboldStyle} className="text-3xl text-white">
|
||||
Авторизация
|
||||
</h1>
|
||||
|
||||
<TextInput
|
||||
value={values.password}
|
||||
name="password"
|
||||
handleChange={handleChange}
|
||||
placeholder="Не менее 8 символов"
|
||||
style="register"
|
||||
label="Ваш пароль"
|
||||
isPassword={true}
|
||||
isVisible={isVisible}
|
||||
togglePasswordVisibility={togglePasswordVisibility}
|
||||
/>
|
||||
<RoleSelector
|
||||
value={values.role}
|
||||
name="role"
|
||||
handleChange={handleChange}
|
||||
label="Ваша роль"
|
||||
placeholder="Выберите вашу роль"
|
||||
/>
|
||||
<div className="flex flex-col gap-4">
|
||||
<TextInput
|
||||
value={values.login}
|
||||
name="login"
|
||||
handleChange={handleChange}
|
||||
placeholder="ivan_ivanov"
|
||||
style="register"
|
||||
label="Ваш логин"
|
||||
/>
|
||||
|
||||
<div className="flex w-full items-center justify-center pt-6">
|
||||
<Button
|
||||
text="Войти"
|
||||
className="bg-blue mt-3 flex w-full items-center justify-center py-4 text-base font-semibold text-white shadow-lg transition-all duration-200 hover:opacity-90"
|
||||
type="submit"
|
||||
<TextInput
|
||||
value={values.password}
|
||||
name="password"
|
||||
handleChange={handleChange}
|
||||
placeholder="Не менее 8 символов"
|
||||
style="register"
|
||||
label="Ваш пароль"
|
||||
isPassword={true}
|
||||
isVisible={isVisible}
|
||||
togglePasswordVisibility={togglePasswordVisibility}
|
||||
/>
|
||||
|
||||
<RoleSelector
|
||||
value={values.role}
|
||||
name="role"
|
||||
handleChange={handleChange}
|
||||
label="Ваша роль"
|
||||
placeholder="Выберите вашу роль"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="mt-4 w-full py-3 px-4 bg-gradient-to-r from-[#3193f5] to-[#1e7ce8] hover:from-[#2563eb] hover:to-[#1a5fd6] text-white font-semibold rounded-xl transition-all duration-200 shadow-lg hover:shadow-2xl hover:shadow-blue-500/50"
|
||||
style={interSemiboldStyle}
|
||||
>
|
||||
Войти
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<p className="text-center text-base font-medium">
|
||||
<span className="hover:text-blue transition-colors duration-200 hover:underline">
|
||||
<Link href="/password-recovery">Забыли логин/пароль?</Link>
|
||||
</span>
|
||||
</p>
|
||||
<div className="border-t border-cyan-500/20 pt-4">
|
||||
<p style={interRegularStyle} className="text-center text-sm text-gray-400">
|
||||
<span className="hover:text-cyan-400 transition-colors duration-200 hover:underline cursor-pointer">
|
||||
<Link href="/password-recovery">Забыли логин/пароль?</Link>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
import Sidebar from '../../../components/ui/Sidebar'
|
||||
import AnimatedBackground from '../../../components/ui/AnimatedBackground'
|
||||
import useNavigationStore from '../../store/navigationStore'
|
||||
import DetectorList from '../../../components/alerts/DetectorList'
|
||||
import AlertsList from '../../../components/alerts/AlertsList'
|
||||
@@ -141,12 +142,15 @@ const AlertsPage: React.FC = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-screen bg-[#0e111a]">
|
||||
<Sidebar
|
||||
activeItem={8} // История тревог
|
||||
/>
|
||||
<div className="relative flex h-screen bg-[#0e111a] overflow-hidden">
|
||||
<AnimatedBackground />
|
||||
<div className="relative z-20">
|
||||
<Sidebar
|
||||
activeItem={8} // История тревог
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 flex flex-col">
|
||||
<div className="relative z-10 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
|
||||
@@ -171,7 +175,7 @@ const AlertsPage: React.FC = () => {
|
||||
<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>
|
||||
<h1 style={{ fontFamily: 'Inter, sans-serif', fontWeight: 600 }} className="text-white text-2xl">Уведомления и тревоги</h1>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
{selectedDetectors.length > 0 && (
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import React, { useEffect, useCallback, useState } from 'react'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
import Sidebar from '../../../components/ui/Sidebar'
|
||||
import AnimatedBackground from '../../../components/ui/AnimatedBackground'
|
||||
import useNavigationStore from '../../store/navigationStore'
|
||||
import Monitoring from '../../../components/navigation/Monitoring'
|
||||
import FloorNavigation from '../../../components/navigation/FloorNavigation'
|
||||
@@ -117,6 +118,8 @@ const NavigationPage: React.FC = () => {
|
||||
map[String(d.serial_number).trim()] = d.status
|
||||
}
|
||||
})
|
||||
console.log('[NavigationPage] sensorStatusMap created with', Object.keys(map).length, 'sensors')
|
||||
console.log('[NavigationPage] Sample sensor IDs in map:', Object.keys(map).slice(0, 5))
|
||||
return map
|
||||
}, [detectorsData])
|
||||
|
||||
@@ -127,21 +130,22 @@ const NavigationPage: React.FC = () => {
|
||||
}, [selectedDetector, selectedAlert]);
|
||||
|
||||
// Управление выделением всех сенсоров при открытии/закрытии меню Sensors
|
||||
// ИСПРАВЛЕНО: Подсветка датчиков остается включенной всегда, независимо от состояния панели Sensors
|
||||
useEffect(() => {
|
||||
console.log('[NavigationPage] showSensors changed:', showSensors, 'modelReady:', isModelReady)
|
||||
if (showSensors && isModelReady) {
|
||||
// При открытии меню Sensors - выделяем все сенсоры (только если модель готова)
|
||||
console.log('[NavigationPage] Setting highlightAllSensors to TRUE')
|
||||
if (isModelReady) {
|
||||
// Всегда включаем подсветку всех сенсоров когда модель готова
|
||||
console.log('[NavigationPage] Setting highlightAllSensors to TRUE (always enabled)')
|
||||
setHighlightAllSensors(true)
|
||||
setFocusedSensorId(null)
|
||||
} else if (!showSensors) {
|
||||
// При закрытии меню Sensors - сбрасываем выделение
|
||||
console.log('[NavigationPage] Setting highlightAllSensors to FALSE')
|
||||
setHighlightAllSensors(false)
|
||||
// Сбрасываем фокус только если панель Sensors закрыта
|
||||
if (!showSensors) {
|
||||
setFocusedSensorId(null)
|
||||
}
|
||||
}
|
||||
}, [showSensors, isModelReady])
|
||||
|
||||
// Дополнительный эффект для задержки выделения сенсоров при открытии меню
|
||||
// ИСПРАВЛЕНО: Задержка применяется только при открытии панели Sensors
|
||||
useEffect(() => {
|
||||
if (showSensors && isModelReady) {
|
||||
const timer = setTimeout(() => {
|
||||
@@ -155,9 +159,10 @@ const NavigationPage: React.FC = () => {
|
||||
|
||||
const urlObjectId = searchParams.get('objectId')
|
||||
const urlObjectTitle = searchParams.get('objectTitle')
|
||||
const urlModelPath = searchParams.get('modelPath')
|
||||
const objectId = currentObject.id || urlObjectId
|
||||
const objectTitle = currentObject.title || urlObjectTitle
|
||||
const [selectedModelPath, setSelectedModelPath] = useState<string>('')
|
||||
const [selectedModelPath, setSelectedModelPath] = useState<string>(urlModelPath || '')
|
||||
|
||||
const handleModelLoaded = useCallback(() => {
|
||||
setIsModelReady(true)
|
||||
@@ -174,8 +179,12 @@ const NavigationPage: React.FC = () => {
|
||||
if (selectedModelPath) {
|
||||
setIsModelReady(false);
|
||||
setModelError(null);
|
||||
// Сохраняем выбранную модель в URL для восстановления при возврате
|
||||
const params = new URLSearchParams(searchParams.toString());
|
||||
params.set('modelPath', selectedModelPath);
|
||||
window.history.replaceState(null, '', `?${params.toString()}`);
|
||||
}
|
||||
}, [selectedModelPath]);
|
||||
}, [selectedModelPath, searchParams]);
|
||||
|
||||
useEffect(() => {
|
||||
if (urlObjectId && (!currentObject.id || currentObject.id !== urlObjectId)) {
|
||||
@@ -183,6 +192,13 @@ const NavigationPage: React.FC = () => {
|
||||
}
|
||||
}, [urlObjectId, urlObjectTitle, currentObject.id, currentObject.title, setCurrentObject])
|
||||
|
||||
// Восстановление выбранной модели из URL при загрузке страницы
|
||||
useEffect(() => {
|
||||
if (urlModelPath && !selectedModelPath) {
|
||||
setSelectedModelPath(urlModelPath);
|
||||
}
|
||||
}, [urlModelPath, selectedModelPath])
|
||||
|
||||
useEffect(() => {
|
||||
const loadDetectors = async () => {
|
||||
try {
|
||||
@@ -195,6 +211,8 @@ const NavigationPage: React.FC = () => {
|
||||
if (!res.ok) throw new Error(typeof payload === 'string' ? payload : (payload?.error || 'Не удалось получить детекторов'))
|
||||
const data = payload?.data ?? payload
|
||||
const detectors = (data?.detectors ?? {}) as Record<string, DetectorType>
|
||||
console.log('[NavigationPage] Received detectors count:', Object.keys(detectors).length)
|
||||
console.log('[NavigationPage] Sample detector keys:', Object.keys(detectors).slice(0, 5))
|
||||
setDetectorsData({ detectors })
|
||||
} catch (e: any) {
|
||||
console.error('Ошибка загрузки детекторов:', e)
|
||||
@@ -240,10 +258,8 @@ const NavigationPage: React.FC = () => {
|
||||
setSelectedDetector(null)
|
||||
setFocusedSensorId(null)
|
||||
setSelectedAlert(null)
|
||||
// При закрытии меню детектора из Sensors - выделяем все сенсоры снова
|
||||
if (showSensors) {
|
||||
setHighlightAllSensors(true)
|
||||
}
|
||||
// При закрытии меню детектора - выделяем все сенсоры снова
|
||||
setHighlightAllSensors(true)
|
||||
}
|
||||
|
||||
const handleNotificationClick = (notification: NotificationType) => {
|
||||
@@ -266,10 +282,8 @@ const NavigationPage: React.FC = () => {
|
||||
setSelectedAlert(null)
|
||||
setFocusedSensorId(null)
|
||||
setSelectedDetector(null)
|
||||
// При закрытии меню алерта из Sensors - выделяем все сенсоры снова
|
||||
if (showSensors) {
|
||||
setHighlightAllSensors(true)
|
||||
}
|
||||
// При закрытии меню алерта - выделяем все сенсоры снова
|
||||
setHighlightAllSensors(true)
|
||||
}
|
||||
|
||||
const handleAlertClick = (alert: AlertType) => {
|
||||
@@ -378,12 +392,15 @@ const NavigationPage: React.FC = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-screen bg-[#0e111a]">
|
||||
<Sidebar
|
||||
activeItem={2}
|
||||
/>
|
||||
<div className="relative flex h-screen bg-[#0e111a] overflow-hidden">
|
||||
<AnimatedBackground />
|
||||
<div className="relative z-20">
|
||||
<Sidebar
|
||||
activeItem={2}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 flex flex-col relative">
|
||||
<div className="relative z-10 flex-1 flex flex-col">
|
||||
|
||||
{showMonitoring && (
|
||||
<div className="absolute left-0 top-[73px] bottom-0 bg-[#161824] border-r border-gray-700 z-20 w-[500px]">
|
||||
@@ -439,7 +456,7 @@ const NavigationPage: React.FC = () => {
|
||||
detectorsData={detectorsData}
|
||||
onDetectorMenuClick={handleDetectorMenuClick}
|
||||
onClose={closeListOfDetectors}
|
||||
is3DReady={isModelReady && !modelError}
|
||||
is3DReady={selectedModelPath ? !modelError : false}
|
||||
/>
|
||||
{detectorsError && (
|
||||
<div className="mt-2 text-sm text-red-400">{detectorsError}</div>
|
||||
@@ -456,7 +473,7 @@ const NavigationPage: React.FC = () => {
|
||||
detectorsData={detectorsData}
|
||||
onAlertClick={handleAlertClick}
|
||||
onClose={closeSensors}
|
||||
is3DReady={isModelReady && !modelError}
|
||||
is3DReady={selectedModelPath ? !modelError : false}
|
||||
/>
|
||||
{detectorsError && (
|
||||
<div className="mt-2 text-sm text-red-400">{detectorsError}</div>
|
||||
|
||||
@@ -4,7 +4,9 @@ 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 AnimatedBackground from '../../../components/ui/AnimatedBackground'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import Image from 'next/image'
|
||||
|
||||
// Универсальная функция для преобразования объекта из бэкенда в ObjectData
|
||||
const transformRawToObjectData = (raw: any): ObjectData => {
|
||||
@@ -126,19 +128,83 @@ const ObjectsPage: React.FC = () => {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<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 className="relative flex h-screen bg-[#0e111a] overflow-hidden">
|
||||
<AnimatedBackground />
|
||||
|
||||
<div className="relative z-20">
|
||||
<Sidebar activeItem={null} />
|
||||
</div>
|
||||
|
||||
<div className="relative z-10 flex-1 overflow-y-auto">
|
||||
{/* Приветствие и информация */}
|
||||
<div className="min-h-screen flex flex-col items-center justify-start pt-20 px-8">
|
||||
{/* Логотип */}
|
||||
<div className="mb-8 flex justify-center">
|
||||
<div className="relative w-64 h-20">
|
||||
<Image
|
||||
src="/icons/logo.png"
|
||||
alt="AerBIM Logo"
|
||||
width={438}
|
||||
height={60}
|
||||
className="object-contain"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Приветствие */}
|
||||
<h1 className="text-5xl font-bold text-white mb-4 text-center animate-fade-in" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||
Добро пожаловать!
|
||||
</h1>
|
||||
|
||||
<p className="text-xl text-gray-300 mb-8 text-center animate-fade-in" style={{ animationDelay: '0.2s', fontFamily: 'Inter, sans-serif' }}>
|
||||
Система мониторинга AerBIM Monitor
|
||||
</p>
|
||||
|
||||
{/* Версия системы */}
|
||||
<div className="mb-16 p-4 rounded-lg bg-gradient-to-r from-blue-500/10 to-cyan-500/10 border border-blue-500/20 inline-block animate-fade-in" style={{ animationDelay: '0.4s' }}>
|
||||
<p className="text-sm text-gray-400" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||
Версия системы: <span className="text-cyan-400 font-semibold">3.0.0</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Блок с галереей объектов */}
|
||||
<div className="w-full max-w-6xl p-8 rounded-xl bg-gradient-to-r from-blue-500/10 to-cyan-500/10 border border-blue-500/20 backdrop-blur-sm">
|
||||
{/* Заголовок галереи */}
|
||||
<h2 className="text-3xl font-bold text-white mb-8 text-center" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||
Выберите объект для работы
|
||||
</h2>
|
||||
|
||||
{/* Галерея объектов */}
|
||||
<ObjectGallery
|
||||
objects={objects}
|
||||
title=""
|
||||
onObjectSelect={handleObjectSelect}
|
||||
selectedObjectId={selectedObjectId}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style jsx>{`
|
||||
@keyframes fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-fade-in {
|
||||
animation: fade-in 0.8s ease-out forwards;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ObjectsPage
|
||||
export default ObjectsPage
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
import Sidebar from '../../../components/ui/Sidebar'
|
||||
import AnimatedBackground from '../../../components/ui/AnimatedBackground'
|
||||
import useNavigationStore from '../../store/navigationStore'
|
||||
import ReportsList from '../../../components/reports/ReportsList'
|
||||
import ExportMenu from '../../../components/ui/ExportMenu'
|
||||
@@ -107,12 +108,15 @@ const ReportsPage: React.FC = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-screen bg-[#0e111a]">
|
||||
<Sidebar
|
||||
activeItem={9} // Отчёты
|
||||
/>
|
||||
<div className="relative flex h-screen bg-[#0e111a] overflow-hidden">
|
||||
<AnimatedBackground />
|
||||
<div className="relative z-20">
|
||||
<Sidebar
|
||||
activeItem={9} // Отчёты
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 flex flex-col">
|
||||
<div className="relative z-10 flex-1 flex flex-col">
|
||||
<header className="border-b border-gray-700 bg-[#161824] px-6 py-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
@@ -142,7 +146,7 @@ const ReportsPage: React.FC = () => {
|
||||
<div className="flex-1 overflow-auto p-6">
|
||||
<div className="mb-6">
|
||||
<div className="mb-6 flex items-center justify-between">
|
||||
<h1 className="text-2xl font-semibold text-white">Отчеты по датчикам</h1>
|
||||
<h1 style={{ fontFamily: 'Inter, sans-serif', fontWeight: 600 }} className="text-2xl text-white">Отчеты по датчикам</h1>
|
||||
|
||||
<ExportMenu onExport={handleExport} />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user