This commit is contained in:
2026-02-02 11:00:40 +03:00
parent 87a1a628d3
commit 2d0f236fa4
22 changed files with 1119 additions and 461 deletions

View File

@@ -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>
)

View File

@@ -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 && (

View File

@@ -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>

View File

@@ -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

View File

@@ -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>