'use client'; import {proportional} from '@astryxdesign/core/Table'; import type {TableColumn} from '@astryxdesign/core/Table'; import {HStack} from '@astryxdesign/core/Layout'; import {Text} from '@astryxdesign/core/Text'; import {StatusDot} from '@astryxdesign/core/StatusDot'; import {ResponsiveTable} from '@/shared/components/patterns/ResponsiveTable'; import type { AdminAlert, AdminCameraStatus, AdminEvent, AdminSeverity, } from '@/features/admin/types/monitoring'; /** * The loaded branch of every drill-down section. * * None of these render today — every capability behind them is off, so their * sections show PendingIntegration instead. They exist so that turning a level * on is a flag flip: the rows are already written against the shapes in * types/monitoring.ts, and a missing field prints a dash, never a guess. */ const DASH = '—'; function when(iso?: string): string { return iso ? new Date(iso).toLocaleString() : DASH; } export const adminHref = { merchants: '/admin/merchants', company: (companyId: string) => `/admin/merchants/${encodeURIComponent(companyId)}`, /** The merchant page, opened on its Shops tab — where "back" from a shop goes. */ shops: (companyId: string) => `${adminHref.company(companyId)}?tab=shops`, store: (companyId: string, storeId: string) => `${adminHref.company(companyId)}/shops/${encodeURIComponent(storeId)}`, camera: (companyId: string, storeId: string, cameraId: string) => `${adminHref.store(companyId, storeId)}/cameras/${encodeURIComponent(cameraId)}`, }; /* ── Cameras ─────────────────────────────────────────────────────────── */ const CAMERA_STATUS: Record< AdminCameraStatus, {label: string; dot: 'success' | 'error' | 'neutral'} > = { online: {label: 'Online', dot: 'success'}, offline: {label: 'Offline', dot: 'error'}, unknown: {label: 'Unknown', dot: 'neutral'}, }; export function CameraStatus({status}: {status: AdminCameraStatus}) { const s = CAMERA_STATUS[status]; return ( {s.label} ); } const SEVERITY: Record< AdminSeverity, {label: string; dot: 'error' | 'warning' | 'neutral'} > = { critical: {label: 'Critical', dot: 'error'}, warning: {label: 'Warning', dot: 'warning'}, info: {label: 'Info', dot: 'neutral'}, }; function Severity({severity}: {severity?: AdminSeverity}) { if (!severity) return {DASH}; const s = SEVERITY[severity]; return ( {s.label} ); } type EventRow = Record & { id: string; at: string; type: string; camera: string; severity?: AdminSeverity; }; export function EventTable({events}: {events: AdminEvent[]}) { const columns: TableColumn[] = [ {key: 'at', header: 'Time', width: proportional(2)}, {key: 'type', header: 'Event', width: proportional(2)}, {key: 'camera', header: 'Camera', width: proportional(2)}, { key: 'severity', header: 'Severity', width: proportional(1), renderCell: (row) => , }, ]; const rows: EventRow[] = events.map((e) => ({ id: e.id, at: when(e.at), type: e.type, camera: e.cameraName ?? DASH, severity: e.severity, })); return ( ); } type AlertRow = Record & { id: string; at: string; title: string; camera: string; severity: AdminSeverity; status: string; }; const ALERT_STATUS: Record = { open: 'Open', acknowledged: 'Acknowledged', resolved: 'Resolved', }; export function AlertTable({alerts}: {alerts: AdminAlert[]}) { const columns: TableColumn[] = [ { key: 'severity', header: 'Severity', width: proportional(1), renderCell: (row) => , }, {key: 'title', header: 'Alert', width: proportional(3)}, {key: 'camera', header: 'Camera', width: proportional(2)}, {key: 'at', header: 'Time', width: proportional(2)}, {key: 'status', header: 'Status', width: proportional(1)}, ]; const rows: AlertRow[] = alerts.map((a) => ({ id: a.id, at: when(a.at), title: a.title, camera: a.cameraName ?? DASH, severity: a.severity, status: ALERT_STATUS[a.status], })); return ( ); }