import React, { useEffect, useState, useMemo } from 'react'; import { NavLink, Outlet, useLocation, useNavigate } from 'react-router-dom'; import { LayoutDashboard, Building2, CreditCard, Package, Smartphone, Server, BellRing, AlertTriangle, FileCheck2, RotateCcw, CheckCircle2, Radio, ExternalLink, Menu, X, ShieldAlert, Sun, Moon, Sparkles, ShieldCheck, ChevronRight, SlidersHorizontal, ChevronLeft, } from 'lucide-react'; import { useKccStore, useOperatorRole } from '../store/kccStore'; import { OperatorSessionSwitcher } from './OperatorSessionSwitcher'; import { InspectorDrawer } from './InspectorDrawer'; import { ConfirmOperatorActionModal } from './ConfirmOperatorAction'; import { ServerMonitorOutageBanner } from './ServerMonitorOutageBanner'; import { useThemeStore, ThemeMode } from '../../../stores/theme'; import { RU_ROLES, RU_THEMES, RU_UI } from '../locales/ru'; export const ControlCenterShell: React.FC = () => { const location = useLocation(); const navigate = useNavigate(); const isInitialized = useKccStore((state) => state.isInitialized); const initialize = useKccStore((state) => state.initialize); const resetToDefaults = useKccStore((state) => state.resetToDefaults); const serverMonitorOutage = useKccStore((state) => state.serverMonitorOutage); const toggleServerMonitorOutage = useKccStore((state) => state.toggleServerMonitorOutage); const incidents = useKccStore((state) => state.incidents); const { role, operator } = useOperatorRole(); const themeMode = useThemeStore((state) => state.themeMode); const setTheme = useThemeStore((state) => state.setTheme); const [utcTime, setUtcTime] = useState(''); const [isResetting, setIsResetting] = useState(false); const [showSavedFeedback, setShowSavedFeedback] = useState(false); // Mobile / Tablet Drawer states const [isMobileDrawerOpen, setIsMobileDrawerOpen] = useState(false); const [isTabletSidebarCollapsed, setIsTabletSidebarCollapsed] = useState(false); useEffect(() => { if (!isInitialized) { initialize(); } }, [isInitialized, initialize]); useEffect(() => { const updateTime = () => { const now = new Date(); setUtcTime(now.toISOString().replace('T', ' ').slice(0, 19) + ' UTC'); }; updateTime(); const interval = setInterval(updateTime, 1000); return () => clearInterval(interval); }, []); // Close mobile drawer on route change useEffect(() => { setIsMobileDrawerOpen(false); }, [location.pathname]); const handleReset = async () => { if (confirm('Сбросить демонстрационные данные платформы к исходному состоянию?')) { setIsResetting(true); await resetToDefaults(); setIsResetting(false); setShowSavedFeedback(true); setTimeout(() => setShowSavedFeedback(false), 3000); } }; // Critical events calculation const criticalCount = useMemo(() => { let count = 0; if (serverMonitorOutage) count += 1; count += incidents.filter( (i) => i.status !== 'resolved' && (i.severity === 'P0_CRITICAL' || i.severity === 'P1_MAJOR') ).length; return count; }, [serverMonitorOutage, incidents]); // Navigation Items const primaryMobileNav = [ { to: '/control', label: 'Обзор', icon: LayoutDashboard, end: true }, { to: '/control/organizations', label: 'Организации', icon: Building2 }, { to: '/control/incidents', label: 'Инциденты', icon: AlertTriangle, badge: criticalCount > 0 ? criticalCount : undefined }, { to: '/control/infrastructure', label: 'Серверы', icon: Server }, ]; const allNavItems = [ { to: '/control', label: 'Обзор', icon: LayoutDashboard, end: true }, { to: '/control/organizations', label: 'Организации', icon: Building2 }, { to: '/control/billing', label: 'Подписки и платежи', icon: CreditCard }, { to: '/control/modules', label: 'Каталог модулей', icon: Package }, { to: '/control/installations', label: 'Установки', icon: Smartphone }, { to: '/control/infrastructure', label: 'Серверы', icon: Server }, { to: '/control/announcements', label: 'Оповещения', icon: BellRing }, { to: '/control/incidents', label: 'Инциденты', icon: AlertTriangle, badge: criticalCount > 0 ? criticalCount : undefined }, { to: '/control/audit', label: 'Журнал аудита', icon: FileCheck2 }, ]; const moreNavItems = [ { to: '/control/billing', label: 'Подписки и платежи', icon: CreditCard }, { to: '/control/modules', label: 'Каталог модулей', icon: Package }, { to: '/control/installations', label: 'Установки', icon: Smartphone }, { to: '/control/announcements', label: 'Оповещения', icon: BellRing }, { to: '/control/audit', label: 'Журнал аудита', icon: FileCheck2 }, ]; // Current section title const currentTitle = useMemo(() => { const current = allNavItems.find((item) => item.end ? location.pathname === item.to : location.pathname.startsWith(item.to) ); return current?.label || 'Control Center'; }, [location.pathname, allNavItems]); const currentRoleLabel = RU_ROLES[role]?.shortLabel || role; return (
{/* ========================================================================= */} {/* 1. DESKTOP & TABLET HEADER (Hidden on mobile < md) */} {/* ========================================================================= */}
{/* Logo & Platform info */}
K
Krona Control Center PROD v1.0
Кабинет владельца платформы
{utcTime} • {RU_UI.system.synced}
{/* Right Controls: Outage simulation, Theme Switcher, Operator Switcher, Reset */}
{/* Circuit Breaker Outage toggle */} {/* Theme Switcher in Desktop Header */}
{/* Reset Demo Data */} {/* Operator Role Switcher */}
{/* ========================================================================= */} {/* 2. MOBILE HEADER (< md) */} {/* ========================================================================= */}
{/* Left: Menu toggle + Logo + Title */}
K
{currentTitle}
Control Center
{/* Right: Critical Badge + Operator Profile button */}
{criticalCount > 0 && ( )}
{/* ========================================================================= */} {/* 3. MAIN LAYOUT (Sidebar on Desktop/Tablet + Content Body) */} {/* ========================================================================= */}
{/* Desktop & Tablet Sidebar */} {/* Main Content Area */}
{/* Feedback Alert if Database Reset */} {showSavedFeedback && (
Демонстрационные данные успешно сброшены к эталонному состоянию.
)} {/* Outage Banner */} {/* Outlet for Page Routes */}
{/* ========================================================================= */} {/* 4. MOBILE BOTTOM BAR (< md) */} {/* ========================================================================= */} {/* ========================================================================= */} {/* 5. MOBILE DRAWER / BOTTOM SHEET */} {/* ========================================================================= */} {isMobileDrawerOpen && (
setIsMobileDrawerOpen(false)} >
e.stopPropagation()} > {/* Sheet Header */}
K
Панель оператора
{/* Scrollable Sheet Content */}
{/* Operator Role Section */}
Сессия оператора
{operator.name}
{RU_ROLES[role]?.label}
{/* Theme Switcher Section */}
{RU_UI.system.themeLabel}
{(['soft-light', 'light', 'dark'] as ThemeMode[]).map((t) => ( ))}
{/* More Navigation Links */}
Все разделы
{allNavItems.map((item) => { const Icon = item.icon; const isActive = item.end ? location.pathname === item.to : location.pathname.startsWith(item.to); return (
{item.label}
{item.badge && item.badge > 0 && ( {item.badge} )}
); })}
{/* System Actions */}
Сервисные действия
Перейти в Krona Business OS
{/* Node info */}
srv-msk-prod-01 • Данные синхронизированы (IndexedDB)
)} {/* Global Contextual Modals */}
); };