'use client'; import React, { useState } from 'react'; import { OperationsNavId, OperatorSession, OperationsHealth, SafeAuditEvent, OperationsOverviewData, } from './types'; import { OperationsTopBar } from './operations-top-bar'; import { OperationsSidebar } from './operations-sidebar'; import { OperationsOverview } from './operations-overview'; import { OperationsAudit } from './operations-audit'; import { OperationsOperators } from './operations-operators'; import { OperationsSessionView } from './operations-session'; import { OperationsIncidentsList } from './operations-incidents-list'; import { OperationsCreateIncident } from './operations-create-incident'; import { OperationsIncidentDetail } from './operations-incident-detail'; import { OperationsUsers } from './operations-users'; import { OperationsSupport } from './operations-support'; import { OperationsCms } from './operations-cms'; import { OperationsInfrastructure } from './operations-infrastructure'; interface OperationsShellProps { initialSession: OperatorSession; initialHealth: OperationsHealth; initialOverview?: OperationsOverviewData | null; initialAudit?: { events: SafeAuditEvent[]; next_cursor?: string | null; total?: number; }; initialNav?: OperationsNavId; initialCaseRef?: string | null; } export function OperationsShell({ initialSession, initialHealth, initialOverview = null, initialAudit, initialNav = 'overview', initialCaseRef = null, }: OperationsShellProps) { const [session, setSession] = useState(initialSession); const [activeNav, setActiveNav] = useState(initialNav); const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false); // Incidents navigation sub-state const [incidentView, setIncidentView] = useState<'list' | 'create' | 'detail'>( initialCaseRef ? 'detail' : 'list' ); const [selectedCaseRef, setSelectedCaseRef] = useState(initialCaseRef); // Overview state const [overviewData, setOverviewData] = useState(initialOverview); const [isRefreshingOverview, setIsRefreshingOverview] = useState(false); // Health state const [health, setHealth] = useState(initialHealth); const [isRefreshingHealth, setIsRefreshingHealth] = useState(false); // Audit state const [auditEvents, setAuditEvents] = useState( initialAudit?.events ?? [] ); const [nextCursor, setNextCursor] = useState( initialAudit?.next_cursor ?? null ); const [auditTotal, setAuditTotal] = useState( initialAudit?.total ); const [isLoadingAudit, setIsLoadingAudit] = useState(false); const [isLoadingMoreAudit, setIsLoadingMoreAudit] = useState(false); // Sign out state const [signingOut, setSigningOut] = useState(false); // Manual Health Refresh (Zero automatic polling) async function handleRefreshHealth() { if (isRefreshingHealth) return; setIsRefreshingHealth(true); try { const response = await fetch('/api/ops/health', { method: 'GET', headers: { accept: 'application/json' }, cache: 'no-store', }); if (response.ok) { const data = (await response.json()) as OperationsHealth; setHealth(data); } else { setHealth((prev: OperationsHealth) => ({ ...prev, status: 'unavailable', checked_at: new Date().toISOString(), })); } } catch { setHealth((prev: OperationsHealth) => ({ ...prev, status: 'unavailable', checked_at: new Date().toISOString(), })); } finally { setIsRefreshingHealth(false); } } // Manual Overview Refresh (Zero automatic polling) async function handleRefreshOverview() { if (isRefreshingOverview) return; setIsRefreshingOverview(true); try { const response = await fetch('/api/ops/overview', { method: 'GET', headers: { accept: 'application/json' }, cache: 'no-store', }); if (response.ok) { const data = (await response.json()) as OperationsOverviewData; setOverviewData(data); if (data.health) { setHealth(data.health); } } } catch { // Retain current state on failure } finally { setIsRefreshingOverview(false); } } // Single initial fetch on mount (Zero interval polling) React.useEffect(() => { void handleRefreshOverview(); }, []); // Next Page of Audit Events (Owner only, server cursor) async function handleLoadNextAuditPage() { if (!nextCursor || isLoadingMoreAudit) return; setIsLoadingMoreAudit(true); try { const response = await fetch( `/api/ops/audit-events?cursor=${encodeURIComponent(nextCursor)}`, { method: 'GET', headers: { accept: 'application/json' }, cache: 'no-store', } ); if (response.ok) { const data = await response.json(); setAuditEvents((prev) => [...prev, ...(data.events ?? [])]); setNextCursor(data.next_cursor ?? null); if (typeof data.total === 'number') { setAuditTotal(data.total); } } } catch { // Retain current events on failure } finally { setIsLoadingMoreAudit(false); } } // Sign Out Handler async function handleSignOut() { if (signingOut) return; setSigningOut(true); try { await fetch('/api/ops/auth/logout', { method: 'POST', headers: { origin: window.location.origin, }, }); } catch { // Continue client cleanup regardless of network error } finally { window.location.href = '/login'; } } return (
{/* Sidebar (Desktop persistent + Mobile off-canvas drawer) */} setActiveNav(nav)} isOpenOnMobile={mobileSidebarOpen} onCloseMobile={() => setMobileSidebarOpen(false)} role={session.role} /> {/* Main Workspace Area */}
{/* Top App Bar */} setMobileSidebarOpen(!mobileSidebarOpen)} signingOut={signingOut} /> {/* Content Body Container */}
{activeNav === 'overview' && ( { await Promise.all([handleRefreshHealth(), handleRefreshOverview()]); }} isRefreshing={isRefreshingHealth || isRefreshingOverview} onSelectIncident={(caseRef) => { setSelectedCaseRef(caseRef); setIncidentView('detail'); setActiveNav('incidents'); }} onNavigateNav={(nav) => setActiveNav(nav)} role={session.role} operatorIdentity={session.identity} /> )} {activeNav === 'infrastructure' && ( )} {activeNav === 'users' && ( )} {activeNav === 'incidents' && ( <> {incidentView === 'list' && ( { setSelectedCaseRef(caseRef); setIncidentView('detail'); }} onCreateIncident={() => setIncidentView('create')} /> )} {incidentView === 'create' && ( { setSelectedCaseRef(caseRef); setIncidentView('detail'); }} onCancel={() => setIncidentView('list')} /> )} {incidentView === 'detail' && selectedCaseRef && ( { setSelectedCaseRef(null); setIncidentView('list'); }} /> )} )} {activeNav === 'support' && ( )} {activeNav === 'cms' && ( )} {activeNav === 'audit' && ( )} {activeNav === 'operators' && } {activeNav === 'session' && ( )}
); }