AEB-55 / Добавлены лого и иконки

This commit is contained in:
iv_vuytsik
2025-09-11 08:12:18 +03:00
parent 4d6b7b48d7
commit ac5d0d0b00
25 changed files with 78 additions and 47934 deletions

3
frontend/.gitignore vendored
View File

@@ -43,3 +43,6 @@ next-env.d.ts
# Include data and models folders
!/data/
!/public/models/
# Exclude models directory (use public/models instead)
/models/

View File

@@ -24,7 +24,7 @@ export default function Home() {
return (
<div className="relative h-screen">
<ModelViewer
modelPath="/models/EXPO_АР_PostRecon_level.gltf"
modelPath="/models/your_model_name.gltf" //пока что передаем модель через navigation page
onModelLoaded={handleModelLoaded}
onError={handleError}
/>

View File

@@ -219,15 +219,13 @@ const NavigationPage: React.FC = () => {
<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'
modelPath='/models/AerBIM-Monitor_ASM-HT-Viewer_Expo2017Astana_20250908_L_+1430.glb'
onModelLoaded={handleModelLoaded}
onError={handleModelError}
/>

View File

@@ -107,7 +107,6 @@ const ObjectsPage: React.FC = () => {
</div>
)
}
return (
<div className="flex h-screen bg-[#0e111a]">
<Sidebar activeItem={null} />

View File

@@ -33,8 +33,6 @@ const ReportsPage: React.FC = () => {
console.log(`Exporting reports as ${format}`)
}
return (
<div className="flex h-screen bg-[#0e111a]">
<Sidebar

View File

@@ -13,7 +13,6 @@ interface Detector {
checked: boolean
}
// Interface for raw detector data from JSON
interface RawDetector {
detector_id: number
name: string
@@ -54,8 +53,6 @@ const DetectorList: React.FC<DetectorListProps> = ({ objectId, selectedDetectors
}, [objectId])
const filteredDetectors = detectors.filter(detector => {
const matchesSearch = detector.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
detector.location.toLowerCase().includes(searchTerm.toLowerCase())
@@ -68,8 +65,6 @@ const DetectorList: React.FC<DetectorListProps> = ({ objectId, selectedDetectors
return matchesSearch
})
return (
<div className="space-y-6">
<div className="flex items-center justify-between gap-4">

View File

@@ -52,7 +52,6 @@ const Dashboard: React.FC = () => {
}, { critical: 0, warning: 0, normal: 0 })
const handleNavigationClick = () => {
// Close all submenus before navigating
closeMonitoring()
closeFloorNavigation()
closeNotifications()
@@ -60,8 +59,6 @@ const Dashboard: React.FC = () => {
router.push('/navigation')
}
// No custom sidebar handling needed - using unified navigation service
return (
<div className="flex h-screen bg-[#0e111a]">
<Sidebar

View File

@@ -44,7 +44,6 @@ const ModelViewer: React.FC<ModelViewerProps> = ({
const isDisposedRef = useRef(false)
useEffect(() => {
isDisposedRef.current = false
isInitializedRef.current = false
@@ -154,7 +153,11 @@ const ModelViewer: React.FC<ModelViewerProps> = ({
setLoadingProgress(100)
console.log('GLTF Model loaded successfully!')
console.log('\n=== Complete Model Object ===')
console.log(result)
console.log('\n=== Structure Overview ===')
console.log('Meshes:', result.meshes?.length || 0)
console.log('Transform Nodes:', result.transformNodes?.length || 0)
if (result.meshes.length > 0) {
const boundingBox = result.meshes[0].getHierarchyBoundingVectors()
@@ -165,7 +168,6 @@ const ModelViewer: React.FC<ModelViewerProps> = ({
camera.radius = maxDimension * 2
camera.target = result.meshes[0].position
onModelLoaded?.({
meshes: result.meshes,
boundingBox: {

View File

@@ -26,7 +26,6 @@ const DetectorMenu: React.FC<DetectorMenuProps> = ({ detector, isOpen, onClose,
return (
<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-5">
{/* Header */}
<div className="flex items-center justify-between mb-4">
<h3 className="text-white text-lg font-medium">
Датч.{detector.name}
@@ -47,7 +46,7 @@ const DetectorMenu: React.FC<DetectorMenuProps> = ({ detector, isOpen, onClose,
</div>
</div>
{/* Detector Information Table */}
{/* Таблица детекторов */}
<div className="space-y-0 border border-[rgb(30,31,36)] rounded-lg overflow-hidden">
<div className="flex">
<div className="flex-1 p-4 border-r border-[rgb(30,31,36)]">
@@ -80,7 +79,6 @@ const DetectorMenu: React.FC<DetectorMenuProps> = ({ detector, isOpen, onClose,
</div>
</div>
{/* Close Button */}
<button
onClick={onClose}
className="absolute top-4 right-4 text-gray-400 hover:text-white transition-colors"

View File

@@ -66,7 +66,6 @@ const NotificationDetectorInfo: React.FC<NotificationDetectorInfoProps> = ({ det
}
}
// Get the latest notification for this detector
const latestNotification = detectorInfo.notifications && detectorInfo.notifications.length > 0
? detectorInfo.notifications.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime())[0]
: null

View File

@@ -3,8 +3,8 @@
import React from 'react'
interface LoadingSpinnerProps {
progress?: number // 0-100
size?: number // diameter in pixels
progress?: number
size?: number
strokeWidth?: number
className?: string
}
@@ -23,7 +23,6 @@ const LoadingSpinner: React.FC<LoadingSpinnerProps> = ({
return (
<div className={`flex flex-col items-center justify-center ${className}`}>
<div className="relative" style={{ width: size, height: size }}>
{/* Background circle */}
<svg
className="transform -rotate-90"
width={size}
@@ -38,7 +37,6 @@ const LoadingSpinner: React.FC<LoadingSpinnerProps> = ({
strokeWidth={strokeWidth}
fill="transparent"
/>
{/* Progress circle */}
<circle
cx={size / 2}
cy={size / 2}
@@ -53,7 +51,6 @@ const LoadingSpinner: React.FC<LoadingSpinnerProps> = ({
/>
</svg>
{/* Percentage text */}
<div className="absolute inset-0 flex items-center justify-center">
<span className="text-white text-xl font-semibold">
{Math.round(progress)}%
@@ -61,7 +58,6 @@ const LoadingSpinner: React.FC<LoadingSpinnerProps> = ({
</div>
</div>
{/* Loading text */}
<div className="mt-4 text-white text-base font-medium">
Loading Model...
</div>

View File

@@ -25,66 +25,52 @@ interface SidebarProps {
onCustomItemClick?: (itemId: number) => boolean
}
// Иконки-заглушки
const IconWrapper = ({ src, alt, className }: { src: string; alt: string; className?: string }) => (
<div className={`relative ${className}`}>
<Image
src={src}
alt={alt}
width={20}
height={20}
className="w-full h-full object-contain"
/>
</div>
)
const BookOpen = ({ className }: { className?: string }) => (
<svg className={className} fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2L3 7v10c0 5.55 3.84 10 9 10s9-4.45 9-10V7l-9-5z" />
</svg>
<IconWrapper src="/icons/BookOpen.png" alt="Dashboard" className={className} />
)
const Bot = ({ className }: { className?: string }) => (
<svg className={className} 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 2z" />
</svg>
<IconWrapper src="/icons/Bot.png" alt="Navigation" className={className} />
)
const SquareTerminal = ({ className }: { className?: string }) => (
<svg className={className} fill="currentColor" viewBox="0 0 24 24">
<path d="M4 6h16v2H4zm0 5h16v2H4zm0 5h16v2H4z" />
</svg>
<IconWrapper src="/icons/SquareTerminal.png" alt="Terminal" className={className} />
)
const CircleDot = ({ className }: { className?: string }) => (
<svg className={className} fill="currentColor" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10" />
<circle cx="12" cy="12" r="3" />
</svg>
<IconWrapper src="/icons/CircleDot.png" alt="Sensors" className={className} />
)
const BellDot = ({ className }: { className?: string }) => (
<svg className={className} fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2C8.13 2 5 5.13 5 9v7l-2 2v1h16v-1l-2-2V9c0-3.87-3.13-7-7-7z" />
</svg>
<IconWrapper src="/icons/BellDot.png" alt="Notifications" className={className} />
)
const History = ({ className }: { className?: string }) => (
<svg className={className} fill="currentColor" viewBox="0 0 24 24">
<path d="M13 3c-4.97 0-9 4.03-9 9H1l3.89 3.89.07.14L9 12H6c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.93 0-3.68-.79-4.94-2.06l-1.42 1.42C8.27 19.99 10.51 21 13 21c4.97 0 9-4.03 9-9s-4.03-9-9-9z" />
</svg>
<IconWrapper src="/icons/History.png" alt="History" className={className} />
)
const Settings2 = ({ className }: { className?: string }) => (
<svg className={className} fill="currentColor" viewBox="0 0 24 24">
<path d="M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87 C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z" />
</svg>
<IconWrapper src="/icons/Settings2.png" alt="Settings" className={className} />
)
const Monitor = ({ className }: { className?: string }) => (
<svg className={className} fill="currentColor" viewBox="0 0 24 24">
<path d="M20 3H4c-1.1 0-2 .9-2 2v11c0 1.1.9 2 2 2h3l-1 1v1h12v-1l-1-1h3c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 13H4V5h16v11z" />
<circle cx="8" cy="10" r="2" />
<circle cx="16" cy="10" r="2" />
</svg>
<IconWrapper src="/icons/Bot.png" alt="Monitor" className={className} />
)
const Building = ({ className }: { className?: string }) => (
<svg className={className} fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2L2 7v10c0 5.55 3.84 10 9 10s9-4.45 9-10V7l-9-5zM12 4.14L18 7.69V17c0 3.31-2.69 6-6 6s-6-2.69-6-6V7.69L12 4.14z" />
<rect x="8" y="10" width="2" height="2" />
<rect x="14" y="10" width="2" height="2" />
<rect x="8" y="14" width="2" height="2" />
<rect x="14" y="14" width="2" height="2" />
</svg>
<IconWrapper src="/icons/BookOpen.png" alt="Building" className={className} />
)
// основные routes
@@ -195,9 +181,9 @@ const Sidebar: React.FC<SidebarProps> = ({
const handleItemClick = (itemId: number) => {
let handled = false
// Handle submenu items directly with navigation store
// Управление суб-меню через navigationStore (суб-меню - работают как отдельные элементы, но не страницы)
switch (itemId) {
case 2: // Navigation - only navigate to page, don't open submenus
case 2:
if (pathname !== '/navigation') {
router.push('/navigation')
}
@@ -205,11 +191,9 @@ const Sidebar: React.FC<SidebarProps> = ({
break
case 3: // Monitoring
if (pathname !== '/navigation') {
// Navigate to navigation page first, then open monitoring
router.push('/navigation')
setTimeout(() => openMonitoring(), 100)
} else if (showMonitoring) {
// Close if already open
closeMonitoring()
} else {
openMonitoring()
@@ -218,11 +202,9 @@ const Sidebar: React.FC<SidebarProps> = ({
break
case 4: // Floor Navigation
if (pathname !== '/navigation') {
// Navigate to navigation page first, then open floor navigation
router.push('/navigation')
setTimeout(() => openFloorNavigation(), 100)
} else if (showFloorNavigation) {
// Close if already open
closeFloorNavigation()
} else {
openFloorNavigation()
@@ -231,11 +213,9 @@ const Sidebar: React.FC<SidebarProps> = ({
break
case 5: // Notifications
if (pathname !== '/navigation') {
// Navigate to navigation page first, then open notifications
router.push('/navigation')
setTimeout(() => openNotifications(), 100)
} else if (showNotifications) {
// Close if already open
closeNotifications()
} else {
openNotifications()
@@ -243,7 +223,7 @@ const Sidebar: React.FC<SidebarProps> = ({
handled = true
break
default:
// For other items, use navigation service
// Для остального используем routes
if (navigationService) {
handled = navigationService.handleSidebarItemClick(itemId, pathname)
}
@@ -251,12 +231,10 @@ const Sidebar: React.FC<SidebarProps> = ({
}
if (handled) {
// Update internal active item state
if (propActiveItem === undefined) {
setInternalActiveItem(itemId)
}
// Call custom handler if provided (for additional logic)
if (onCustomItemClick) {
onCustomItemClick(itemId)
}
@@ -273,22 +251,14 @@ const Sidebar: React.FC<SidebarProps> = ({
>
<header className="flex items-center gap-2 pt-2 pb-2 px-4 relative self-stretch w-full flex-[0_0_auto] bg-[#161824]">
{!isCollapsed && (
<div className="relative w-[169.83px] h-[34px]">
{logoSrc && (
<div className="relative">
<Image
className="absolute w-12 h-[33px] top-0 left-0"
className="w-auto h-[33px]"
alt="AerBIM Monitor Logo"
src={logoSrc}
width={48}
src={logoSrc || "/icons/logo.png"}
width={169}
height={33}
/>
)}
<div className="absolute w-[99px] top-[21px] left-[50px] [font-family:'Open_Sans-Regular',Helvetica] font-normal text-[#389ee8] text-[13px] tracking-[0] leading-[13px] whitespace-nowrap">
AMS HT Viewer
</div>
<div className="absolute top-px left-[50px] [font-family:'Open_Sans-SemiBold',Helvetica] font-semibold text-[#f1f6fa] text-[15px] tracking-[0] leading-[15px] whitespace-nowrap">
AerBIM Monitor
</div>
</div>
)}
</header>
@@ -320,12 +290,12 @@ const Sidebar: React.FC<SidebarProps> = ({
</span>
)}
{item.id === 2 && !isCollapsed && (
// Закрыть все суб-меню при закрытии главного окна
<div
className="p-1 hover:bg-gray-600 rounded transition-colors duration-200 cursor-pointer"
className="p-1.5 hover:bg-gray-600 rounded-md transition-colors duration-200 cursor-pointer bg-gray-700/50 border border-gray-600/50"
onClick={(e) => {
e.stopPropagation()
setManuallyToggled(true)
// Close all active submenus when collapsing navigation menu
if (showNavigationSubItems || isNavigationSubItemActive) {
closeMonitoring()
closeFloorNavigation()
@@ -340,7 +310,6 @@ const Sidebar: React.FC<SidebarProps> = ({
e.preventDefault()
e.stopPropagation()
setManuallyToggled(true)
// Close all active submenus when collapsing navigation menu
if (showNavigationSubItems || isNavigationSubItemActive) {
closeMonitoring()
closeFloorNavigation()
@@ -352,7 +321,7 @@ const Sidebar: React.FC<SidebarProps> = ({
aria-label={isHydrated ? (showNavigationSubItems || isNavigationSubItemActive ? 'Collapse navigation menu' : 'Expand navigation menu') : 'Toggle navigation menu'}
>
<svg
className={`!relative !w-4 !h-4 text-white transition-transform duration-200 ${
className={`!relative !w-4 !h-4 text-white transition-transform duration-200 drop-shadow-sm ${
isHydrated && (showNavigationSubItems || isNavigationSubItemActive) ? 'rotate-90' : ''
}`}
fill="currentColor"
@@ -399,24 +368,24 @@ const Sidebar: React.FC<SidebarProps> = ({
})}
</ul>
<button
className="!relative !w-6 !h-6 p-1 rounded hover:bg-gray-700 focus:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 transition-all duration-200"
className="!relative !w-8 !h-8 p-1.5 rounded-lg hover:bg-gray-700 focus:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 transition-all duration-200 bg-gray-800/60 border border-gray-600/40 shadow-lg hover:shadow-xl"
onClick={() => {
// If navigation submenu is open, first collapse it (which closes all submenus)
// Убираем суб-меню перед сворачиванием сайдбара
if (showNavigationSubItems) {
setShowNavigationSubItems(false)
setManuallyToggled(true)
// Close all active submenus when collapsing navigation menu
closeMonitoring()
closeFloorNavigation()
closeNotifications()
}
// Always collapse the sidebar
// Убираем сайд-бар
toggleSidebar()
}}
aria-label={isCollapsed ? "Expand sidebar" : "Collapse sidebar"}
type="button"
>
<svg className={`!relative !w-4 !h-4 text-white transition-transform duration-200 ${isCollapsed ? 'rotate-180' : ''}`} fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<svg className={`!relative !w-5 !h-5 text-white transition-transform duration-200 drop-shadow-sm ${isCollapsed ? 'rotate-180' : ''}`} fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z" />
</svg>
</button>

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 363 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 55 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -25,7 +25,7 @@ export class NavigationService {
init(router: ReturnType<typeof useRouter>, navigationStore: NavigationStore) {
if (this.initialized) {
return // Prevent multiple initializations
return // Предотвращаем повторную инициализацию
}
this.router = router
this.navigationStore = navigationStore
@@ -37,7 +37,7 @@ export class NavigationService {
}
navigateToRoute(route: MainRoutes) {
// Clear any active submenus when navigating to a different route
// Убираем суб-меню перед переходом на другую страницу
if (route !== MainRoutes.NAVIGATION) {
this.navigationStore.setCurrentSubmenu(null)
}
@@ -81,7 +81,6 @@ export function useNavigationService() {
const router = useRouter()
const navigationStore = useNavigationStore()
// Initialize only once per component lifecycle
React.useMemo(() => {
if (!navigationService.isInitialized()) {
navigationService.init(router, navigationStore)