import React, { useState, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import { Building2, Users, CreditCard, Smartphone, Server, ShieldAlert, ArrowUpRight, Activity, CheckCircle2, Clock, Layers, } from 'lucide-react'; import { useKccStore, useOperatorRole } from '../store/kccStore'; import { MoneyDisplay } from '../components/MoneyDisplay'; import { StatusBadge } from '../components/StatusBadge'; import { HeartbeatTimeline } from '../components/HeartbeatTimeline'; import { RU_ROLES, RU_INCIDENT_SEVERITY, RU_INCIDENT_STATUS } from '../locales/ru'; type OverviewTab = 'attention' | 'metrics' | 'infrastructure'; export const OverviewPage: React.FC = () => { const navigate = useNavigate(); const { role, operator } = useOperatorRole(); const organizations = useKccStore((state) => state.organizations); const subscriptions = useKccStore((state) => state.subscriptions); const installations = useKccStore((state) => state.installations); const instances = useKccStore((state) => state.instances); const servers = useKccStore((state) => state.servers); const incidents = useKccStore((state) => state.incidents); const openInspector = useKccStore((state) => state.openInspector); const serverMonitorOutage = useKccStore((state) => state.serverMonitorOutage); // Aggregates const totalOrgs = organizations.length; const activeOrgs = organizations.filter((o) => o.platformStatus === 'active').length; const trialOrgs = organizations.filter((o) => o.platformStatus === 'trial').length; const graceOrgs = organizations.filter((o) => o.platformStatus === 'grace').length; const suspendedOrgs = organizations.filter((o) => o.platformStatus === 'suspended').length; const totalUsers = organizations.reduce((sum, o) => sum + o.userCount, 0); const totalBranches = organizations.reduce((sum, o) => sum + o.branchCount, 0); const totalMrrMinorUnits = organizations.reduce((sum, o) => sum + o.mrrMinorUnits, 0); const expiring7d = subscriptions.filter((s) => s.status === 'expiring_7d'); const overdueSubs = subscriptions.filter((s) => s.status === 'overdue'); const webInstalls = installations.filter((i) => i.kind === 'web').length; const desktopInstalls = installations.filter((i) => i.kind === 'desktop').length; const androidInstalls = installations.filter((i) => i.kind === 'android').length; const outdatedAndroid = installations.filter((i) => i.kind === 'android' && i.status === 'outdated').length; const instancesOffline = instances.filter((i) => i.health === 'offline'); const instancesDegraded = instances.filter((i) => i.health === 'degraded'); const serversHealthy = servers.filter((s) => s.status === 'healthy').length; const serversProblematic = servers.filter((s) => s.status !== 'healthy'); const openIncidents = incidents.filter((i) => i.status !== 'resolved'); // Total attention items count const attentionCount = useMemo(() => { let count = openIncidents.length; count += serversProblematic.length; count += instancesOffline.length; count += instancesDegraded.length; count += expiring7d.length; count += overdueSubs.length; if (serverMonitorOutage) count += 1; return count; }, [openIncidents, serversProblematic, instancesOffline, instancesDegraded, expiring7d, overdueSubs, serverMonitorOutage]); // Default tab is 'attention' if there are problems, otherwise 'metrics' const [activeTab, setActiveTab] = useState(() => (attentionCount > 0 ? 'attention' : 'metrics')); return (
{/* Top Headline */}

Сводная операторская панель Контур платформы

Оперативный контроль организаций, лицензий, модульных инстансов и серверных узлов Krona

Оператор: {operator.name} ({RU_ROLES[role]?.shortLabel || role})
{/* Tab Switcher (Mobile-First Priority Controls) */}
{/* ========================================================================= */} {/* TAB 1: ТРЕБУЕТ ВНИМАНИЯ (CRITICAL / PROBLEMS PRIORITY) */} {/* ========================================================================= */} {activeTab === 'attention' && (
{attentionCount === 0 && (
Все системы работают в штатном режиме

Нет открытых аварий, серверов с деградацией или просроченных подписок, требующих вмешательства оператора.

)} {/* 1. Открытые инциденты платформы */} {openIncidents.length > 0 && (
Открытые инциденты ({openIncidents.length})
{openIncidents.map((inc) => (
openInspector('incident', inc.incidentId)} className="p-3.5 rounded-lg border border-status-danger-border bg-status-danger-soft cursor-pointer hover:opacity-95 transition-opacity space-y-1.5" >
{RU_INCIDENT_SEVERITY[inc.severity]?.short || inc.severity} {inc.summary}
{RU_INCIDENT_STATUS[inc.status] || inc.status}

{inc.description}

Затронуто: {inc.affectedTenantIds.length} орг. • Узлов: {inc.affectedServerIds.length}
))}
)} {/* 2. Проблемные серверы */} {serversProblematic.length > 0 && (
Серверы с предупреждениями ({serversProblematic.length})
{serversProblematic.map((srv) => { const cpuPercent = srv.cpuLoadPercent; const ramPercent = Math.round((srv.ramUsedBytes / (srv.ramTotalBytes || 1)) * 100); const diskPercent = Math.round((srv.diskUsedBytes / (srv.diskTotalBytes || 1)) * 100); return (
openInspector('server', srv.serverId)} className="p-3.5 rounded-lg border border-krona-border bg-krona-workspace cursor-pointer hover:border-krona-gold/40 transition-colors space-y-2" >
{srv.hostname}
CPU: 80 ? 'text-status-danger-text font-bold' : ''}>{cpuPercent}%
RAM: 80 ? 'text-status-danger-text font-bold' : ''}>{ramPercent}%
Диск: 85 ? 'text-status-danger-text font-bold' : ''}>{diskPercent}%
Провайдер: {srv.hostingProvider} ({srv.region})
); })}
)} {/* 3. Модули с деградацией или сбоем */} {(instancesOffline.length > 0 || instancesDegraded.length > 0) && (
Модули с деградацией ({instancesOffline.length + instancesDegraded.length})
{[...instancesOffline, ...instancesDegraded].map((inst) => (
openInspector('organization', inst.tenantId)} className="p-3 rounded-lg border border-krona-border bg-krona-workspace cursor-pointer hover:border-krona-gold/40 transition-colors flex items-center justify-between" >
{inst.moduleName} v{inst.currentVersion}
Тенант: {inst.tenantName}
))}
)} {/* 4. Истекающие и просроченные подписки */} {(expiring7d.length > 0 || overdueSubs.length > 0) && (
Истекающие подписки ({expiring7d.length + overdueSubs.length})
{[...overdueSubs, ...expiring7d].map((sub) => (
openInspector('organization', sub.tenantId)} className="p-3 rounded-lg border border-krona-border bg-krona-workspace cursor-pointer hover:border-krona-gold/40 transition-colors flex items-center justify-between" >
{sub.tenantName}
Тариф: {sub.planCode} • До: {new Date(sub.validUntil).toLocaleDateString('ru-RU')}
))}
)}
)} {/* ========================================================================= */} {/* TAB 2: ПОКАЗАТЕЛИ (KEY METRICS & KPI) */} {/* ========================================================================= */} {activeTab === 'metrics' && (
{/* 6 Key KPI Cards */}
{/* Orgs */}
navigate('/control/organizations')} className="p-3.5 rounded-xl border border-krona-border bg-krona-surface hover:border-krona-gold/40 cursor-pointer transition-colors" >
Организации
{totalOrgs}
{activeOrgs} активных
{/* Users */}
Пользователи
{totalUsers}
{totalBranches} филиалов
{/* MRR */}
navigate('/control/billing')} className="p-3.5 rounded-xl border border-krona-border bg-krona-surface hover:border-krona-gold/40 cursor-pointer transition-colors col-span-2 sm:col-span-1" >
Выручка (MRR)
В месяц
{/* Subscriptions */}
navigate('/control/billing')} className="p-3.5 rounded-xl border border-krona-border bg-krona-surface hover:border-krona-gold/40 cursor-pointer transition-colors" >
Подписки
{subscriptions.length}
{expiring7d.length} истекают
{/* Installations */}
navigate('/control/installations')} className="p-3.5 rounded-xl border border-krona-border bg-krona-surface hover:border-krona-gold/40 cursor-pointer transition-colors" >
Установки
{installations.length}
Android: {androidInstalls}
{/* Servers */}
navigate('/control/infrastructure')} className="p-3.5 rounded-xl border border-krona-border bg-krona-surface hover:border-krona-gold/40 cursor-pointer transition-colors" >
Серверы
{servers.length}
{serversHealthy} в норме
{/* Detailed Statistics Cards */}
{/* Orgs Breakdown */}
Статус организаций Всего: {totalOrgs}
Активные организации {activeOrgs}
Пробный период (Trial) {trialOrgs}
Льготный период (Grace) {graceOrgs}
Приостановлены (Suspended) {suspendedOrgs}
{/* Installations Breakdown */}
Платформы клиентов Всего: {installations.length}
Веб-приложения (Web SPA) {webInstalls}
Рабочие станции (Desktop) {desktopInstalls}
Мобильные кассы (Android) {androidInstalls}
{outdatedAndroid > 0 && (
Устаревшие версии Android: {outdatedAndroid}
)}
)} {/* ========================================================================= */} {/* TAB 3: ИНФРАСТРУКТУРА (SERVERS & TELEMETRY) */} {/* ========================================================================= */} {activeTab === 'infrastructure' && (

Инфраструктурные серверы Krona

Текущая загрузка вычислительных узлов платформы

{servers.map((srv) => { const cpuPercent = srv.cpuLoadPercent; const ramPercent = Math.round((srv.ramUsedBytes / (srv.ramTotalBytes || 1)) * 100); const diskPercent = Math.round((srv.diskUsedBytes / (srv.diskTotalBytes || 1)) * 100); return (
openInspector('server', srv.serverId)} className="p-3.5 rounded-lg border border-krona-border bg-krona-workspace cursor-pointer hover:border-krona-gold/40 transition-colors space-y-2.5" >
{srv.hostname}
{srv.hostingProvider} ({srv.region})
CPU
80 ? 'text-status-danger-text' : 'text-krona-primary'}`}> {cpuPercent}%
RAM
80 ? 'text-status-danger-text' : 'text-krona-primary'}`}> {ramPercent}%
Диск
85 ? 'text-status-danger-text' : 'text-krona-primary'}`}> {diskPercent}%
Резервная копия: {srv.backup.state === 'ok' ? 'Успешно' : srv.backup.state === 'failed' ? 'Ошибка' : srv.backup.state}
); })}
)}
); };