import { useState, useMemo } from 'react' import { useParams } from 'react-router-dom' import styles from './Dashboard.module.css' // Layout import { AppShell } from '../../design-system/layout/AppShell/AppShell' import { Sidebar } from '../../design-system/layout/Sidebar/Sidebar' import { TopBar } from '../../design-system/layout/TopBar/TopBar' // Composites import { DataTable } from '../../design-system/composites/DataTable/DataTable' import type { Column } from '../../design-system/composites/DataTable/types' import { DetailPanel } from '../../design-system/composites/DetailPanel/DetailPanel' import { ShortcutsBar } from '../../design-system/composites/ShortcutsBar/ShortcutsBar' import { ProcessorTimeline } from '../../design-system/composites/ProcessorTimeline/ProcessorTimeline' // Primitives import { StatCard } from '../../design-system/primitives/StatCard/StatCard' import { StatusDot } from '../../design-system/primitives/StatusDot/StatusDot' import { MonoText } from '../../design-system/primitives/MonoText/MonoText' import { Badge } from '../../design-system/primitives/Badge/Badge' // Global filters import { useGlobalFilters } from '../../design-system/providers/GlobalFilterProvider' // Mock data import { exchanges, type Exchange } from '../../mocks/exchanges' import { kpiMetrics } from '../../mocks/metrics' import { SIDEBAR_APPS } from '../../mocks/sidebar' // ─── Helpers ───────────────────────────────────────────────────────────────── function formatDuration(ms: number): string { if (ms >= 60_000) return `${(ms / 1000).toFixed(0)}s` if (ms >= 1000) return `${(ms / 1000).toFixed(2)}s` return `${ms}ms` } function formatTimestamp(date: Date): string { return date.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit', second: '2-digit' }) } function statusToVariant(status: Exchange['status']): 'success' | 'error' | 'running' | 'warning' { switch (status) { case 'completed': return 'success' case 'failed': return 'error' case 'running': return 'running' case 'warning': return 'warning' } } function statusLabel(status: Exchange['status']): string { switch (status) { case 'completed': return 'OK' case 'failed': return 'ERR' case 'running': return 'RUN' case 'warning': return 'WARN' } } // ─── Table columns ──────────────────────────────────────────────────────────── const COLUMNS: Column[] = [ { key: 'status', header: 'Status', width: '80px', render: (_, row) => ( {statusLabel(row.status)} ), }, { key: 'route', header: 'Route', sortable: true, render: (_, row) => (
{row.route}
{row.routeGroup}
), }, { key: 'orderId', header: 'Order ID', sortable: true, render: (_, row) => ( {row.orderId} ), }, { key: 'customer', header: 'Customer', render: (_, row) => ( {row.customer} ), }, { key: 'timestamp', header: 'Started', sortable: true, render: (_, row) => ( {formatTimestamp(row.timestamp)} ), }, { key: 'durationMs', header: 'Duration', sortable: true, render: (_, row) => ( {formatDuration(row.durationMs)} ), }, { key: 'agent', header: 'Agent', render: (_, row) => ( {row.agent} ), }, ] function durationClass(ms: number, status: Exchange['status']): string { if (status === 'failed') return styles.durBreach if (ms < 100) return styles.durFast if (ms < 200) return styles.durNormal if (ms < 300) return styles.durSlow return styles.durBreach } const SHORTCUTS = [ { keys: 'Ctrl+K', label: 'Search' }, { keys: '↑↓', label: 'Navigate rows' }, { keys: 'Enter', label: 'Open detail' }, { keys: 'Esc', label: 'Close panel' }, ] // ─── Dashboard component ────────────────────────────────────────────────────── export function Dashboard() { const { id: appId } = useParams<{ id: string }>() const [selectedId, setSelectedId] = useState() const [panelOpen, setPanelOpen] = useState(false) const [selectedExchange, setSelectedExchange] = useState(null) const { isInTimeRange, statusFilters } = useGlobalFilters() // Build set of route IDs belonging to the selected app (if any) const appRouteIds = useMemo(() => { if (!appId) return null const app = SIDEBAR_APPS.find((a) => a.id === appId) if (!app) return null return new Set(app.routes.map((r) => r.id)) }, [appId]) // Scope all data to the selected app const scopedExchanges = useMemo(() => { if (!appRouteIds) return exchanges return exchanges.filter((e) => appRouteIds.has(e.route)) }, [appRouteIds]) // Filter exchanges (scoped + global filters) const filteredExchanges = useMemo(() => { let data = scopedExchanges // Time range filter data = data.filter((e) => isInTimeRange(e.timestamp)) // Status filter if (statusFilters.size > 0) { data = data.filter((e) => statusFilters.has(e.status)) } return data }, [scopedExchanges, isInTimeRange, statusFilters]) function handleRowClick(row: Exchange) { setSelectedId(row.id) setSelectedExchange(row) setPanelOpen(true) } function handleRowAccent(row: Exchange): 'error' | 'warning' | undefined { if (row.status === 'failed') return 'error' if (row.status === 'warning') return 'warning' return undefined } // Build detail panel tabs for selected exchange const detailTabs = selectedExchange ? [ { label: 'Overview', value: 'overview', content: (
Order ID {selectedExchange.orderId}
Route {selectedExchange.route}
Status {statusLabel(selectedExchange.status)}
Duration {formatDuration(selectedExchange.durationMs)}
Customer {selectedExchange.customer}
Agent {selectedExchange.agent}
Correlation ID {selectedExchange.correlationId}
Timestamp {selectedExchange.timestamp.toISOString()}
{selectedExchange.errorMessage && (
{selectedExchange.errorClass}
{selectedExchange.errorMessage}
)}
), }, { label: 'Processors', value: 'processors', content: (
), }, { label: 'Exchange', value: 'exchange', content: (
Exchange snapshot not available in mock mode.
), }, { label: 'Error', value: 'error', content: (
{selectedExchange.errorMessage ? ( <>
{selectedExchange.errorClass}
{selectedExchange.errorMessage}
) : (
No error for this exchange.
)}
), }, ] : [] return ( } detail={ selectedExchange ? ( setPanelOpen(false)} title={`${selectedExchange.orderId} — ${selectedExchange.route}`} tabs={detailTabs} /> ) : undefined } > {/* Top bar */} {/* Scrollable content */}
{/* Health strip */}
{kpiMetrics.map((kpi, i) => ( ))}
{/* Exchanges table */}
Recent Exchanges
{filteredExchanges.length.toLocaleString()} of {scopedExchanges.length.toLocaleString()} exchanges
row.errorMessage ? (
{row.errorMessage}
Click to view full stack trace
) : null } />
{/* Shortcuts bar */}
) }