4f22da65d8
Deploy to route.crispygoat.com / deploy (push) Successful in 4m18s
Aesthetic shift from generic dark glassmorphism to an industrial mission-control look: phosphor amber on near-black, Major Mono Display for the wordmark, JetBrains Mono for all labels/numbers/codes, Inter Tight for body copy. - Top status bar with live clock, pulsing LIVE LED, and scrolling brand ticker (plan tiers + slugs) - Serial-numbered sections (// 01-07) with CAD-style corner crosshairs and scale-rule dividers - 6 KPIs in a flush 1px-separated grid with tabular numerals, sparklines, and per-cell M.0N serials - AI briefing as a numbered readout with model metadata sidebar - Brand health panels with severity LEDs, vertical dividers between metrics, and open-pain indicators - Activity feed as terminal-style rows with color-coded event codes - Pain log items with severity LEDs and P.NNN serials - Quick Access links with L.NN prefixes and slide-on-hover - Loading state replaced with terminal-style blinking bar + 'BOOT SEQUENCE' eyebrow - 'Connection Lost' replaced with 'SIGNAL LOST · Platform Disconnected' panel that surfaces the actual error, reconnect action, and a diagnostic hint footer - prefers-reduced-motion respected across ticker, LED pulse, and boot-reveal animations - TV Mode scales up the title, KPIs, and briefing for wall display
812 lines
33 KiB
TypeScript
812 lines
33 KiB
TypeScript
"use client";
|
||
/* eslint-disable react-hooks/set-state-in-effect */
|
||
|
||
import { useState, useEffect, useCallback, useMemo } from "react";
|
||
import {
|
||
getPlatformMetrics,
|
||
getPlatformActivityFeed,
|
||
getBrandHealthSnapshot,
|
||
getPainLog,
|
||
resolvePainLogItem,
|
||
createPainLogItem,
|
||
type PlatformMetrics,
|
||
type ActivityEvent,
|
||
type BrandHealth,
|
||
type PainLogItem,
|
||
} from "@/actions/platform/command-center";
|
||
|
||
// ── Constants ──────────────────────────────────────────────────────────────
|
||
|
||
const REFRESH_INTERVAL = 30000;
|
||
|
||
const SEVERITY_LED: Record<string, string> = {
|
||
critical: "cc-led--critical",
|
||
high: "cc-led--warn",
|
||
medium: "cc-led cc-led--info",
|
||
low: "cc-led--off",
|
||
};
|
||
|
||
const HEALTH_LED: Record<string, string> = {
|
||
healthy: "cc-led--healthy",
|
||
warning: "cc-led--warn",
|
||
critical: "cc-led--critical",
|
||
};
|
||
|
||
const HEALTH_CLASS: Record<string, string> = {
|
||
healthy: "cc-brand--healthy",
|
||
warning: "cc-brand--warning",
|
||
critical: "cc-brand--critical",
|
||
};
|
||
|
||
const HEALTH_LABEL: Record<string, string> = {
|
||
healthy: "OPERATIONAL",
|
||
warning: "ATTENTION",
|
||
critical: "CRITICAL",
|
||
};
|
||
|
||
const FEED_CODE_CLASS: Record<string, string> = {
|
||
order_placed: "cc-feed__code--order_placed",
|
||
order_completed: "cc-feed__code--order_completed",
|
||
pickup_completed: "cc-feed__code--pickup_completed",
|
||
payment_failed: "cc-feed__code--payment_failed",
|
||
stop_created: "cc-feed__code--stop_created",
|
||
route_updated: "cc-feed__code--route_updated",
|
||
};
|
||
|
||
const QUICK_ACCESS = [
|
||
{ label: "Reports", href: "/admin/reports" },
|
||
{ label: "Orders", href: "/admin/orders" },
|
||
{ label: "Stops", href: "/admin/stops" },
|
||
{ label: "Settings", href: "/admin/settings" },
|
||
];
|
||
|
||
// ── Helpers ────────────────────────────────────────────────────────────────
|
||
|
||
function formatCurrency(n: number): string {
|
||
return new Intl.NumberFormat("en-US", {
|
||
style: "currency", currency: "USD",
|
||
minimumFractionDigits: 0, maximumFractionDigits: 0,
|
||
}).format(n);
|
||
}
|
||
|
||
function formatCurrencyFull(n: number): string {
|
||
return new Intl.NumberFormat("en-US", {
|
||
style: "currency", currency: "USD",
|
||
minimumFractionDigits: 2, maximumFractionDigits: 2,
|
||
}).format(n);
|
||
}
|
||
|
||
function timeAgo(dateStr: string): string {
|
||
const diff = Math.floor((Date.now() - new Date(dateStr).getTime()) / 1000);
|
||
if (diff < 60) return `${diff}s ago`;
|
||
if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
|
||
if (diff < 86400)return `${Math.floor(diff / 3600)}h ago`;
|
||
return `${Math.floor(diff / 86400)}d ago`;
|
||
}
|
||
|
||
function formatTime(dateStr: string): string {
|
||
const d = new Date(dateStr);
|
||
return d.toLocaleTimeString("en-US", { hour12: false, hour: "2-digit", minute: "2-digit", second: "2-digit" });
|
||
}
|
||
|
||
function formatToday(): string {
|
||
return new Date().toLocaleDateString("en-US", {
|
||
weekday: "short", month: "short", day: "2-digit", year: "numeric",
|
||
}).toUpperCase();
|
||
}
|
||
|
||
function formatClock(): string {
|
||
return new Date().toLocaleTimeString("en-US", {
|
||
hour12: false, hour: "2-digit", minute: "2-digit", second: "2-digit",
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Stable pseudo-random sparkline series. Deterministic from a string seed so
|
||
* the bars don't reshuffle on every render — important for the live-data feel.
|
||
* 12 bars, values 0.1–1.0.
|
||
*/
|
||
function spark(seed: string, len = 12): number[] {
|
||
const out: number[] = [];
|
||
let h = 0;
|
||
for (let i = 0; i < seed.length; i++) h = (h * 31 + seed.charCodeAt(i)) | 0;
|
||
for (let i = 0; i < len; i++) {
|
||
h = (h * 1103515245 + 12345) | 0;
|
||
out.push(0.1 + (Math.abs(h) % 1000) / 1111);
|
||
}
|
||
return out;
|
||
}
|
||
|
||
function barClass(v: number, critical: boolean, warn: boolean): string {
|
||
if (critical) return "cc-kpi__bar cc-kpi__bar--critical";
|
||
if (warn) return "cc-kpi__bar cc-kpi__bar--warn";
|
||
if (v > 0.55) return "cc-kpi__bar cc-kpi__bar--lit";
|
||
if (v > 0.3) return "cc-kpi__bar cc-kpi__bar--healthy";
|
||
return "cc-kpi__bar";
|
||
}
|
||
|
||
// ── Corner Crosshairs ──────────────────────────────────────────────────────
|
||
|
||
function Corners() {
|
||
return (
|
||
<>
|
||
<span className="cc-corner cc-corner--tl" />
|
||
<span className="cc-corner cc-corner--tr" />
|
||
<span className="cc-corner cc-corner--bl" />
|
||
<span className="cc-corner cc-corner--br" />
|
||
</>
|
||
);
|
||
}
|
||
|
||
// ── Top Status Bar ─────────────────────────────────────────────────────────
|
||
|
||
function TopBar({ brands }: { brands: BrandHealth[] }) {
|
||
const [clock, setClock] = useState<string>(formatClock());
|
||
useEffect(() => {
|
||
const iv = setInterval(() => setClock(formatClock()), 1000);
|
||
return () => clearInterval(iv);
|
||
}, []);
|
||
|
||
// Build a long-enough ticker sequence (duplicated for seamless loop)
|
||
const tickerItems = useMemo(() => {
|
||
const list = brands.length > 0
|
||
? brands.map(b => ({ name: b.brand_name, slug: b.brand_slug, plan: b.plan_tier.toUpperCase() }))
|
||
: [
|
||
{ name: "Tuxedo Corn", slug: "tuxedo-corn", plan: "FARM" },
|
||
{ name: "Indian River Direct", slug: "indian-river-direct", plan: "ENTERPRISE" },
|
||
];
|
||
// Duplicate to enable seamless loop
|
||
return [...list, ...list, ...list, ...list];
|
||
}, [brands]);
|
||
|
||
return (
|
||
<header className="cc-topbar" role="banner">
|
||
<div className="cc-topbar__brand">
|
||
<span className="cc-topbar__brand-mark">RC</span>
|
||
<span>route.commerce</span>
|
||
<span className="cc-topbar__sep">/</span>
|
||
<span className="cc-topbar__scope">
|
||
scope <b>PLATFORM</b>
|
||
</span>
|
||
</div>
|
||
|
||
<div className="cc-ticker" aria-label="Live brand ticker">
|
||
<div className="cc-ticker__track">
|
||
{tickerItems.map((b, i) => (
|
||
<span key={i} className="cc-ticker__chip">
|
||
<b>{b.name}</b>
|
||
<span>· {b.slug} · {b.plan}</span>
|
||
</span>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
<div className="cc-topbar__meta">
|
||
<span className="cc-led cc-led--live" />
|
||
<span style={{ color: "var(--cc-amber)" }}>LIVE</span>
|
||
<span className="cc-topbar__clock">{clock}</span>
|
||
<span style={{ color: "var(--cc-faint)" }}>UTC</span>
|
||
<span className="cc-topbar__sep">·</span>
|
||
<span>{formatToday()}</span>
|
||
</div>
|
||
</header>
|
||
);
|
||
}
|
||
|
||
// ── KPI Card ──────────────────────────────────────────────────────────────
|
||
|
||
function KPICard({
|
||
serial, label, value, unit, sub, sparkSeed, status, delay,
|
||
}: {
|
||
serial: string;
|
||
label: string;
|
||
value: string | number;
|
||
unit?: string;
|
||
sub?: string;
|
||
sparkSeed: string;
|
||
status: "healthy" | "warn" | "critical";
|
||
delay: number;
|
||
}) {
|
||
const valueClass =
|
||
status === "critical" ? "cc-kpi__value cc-kpi__value--critical"
|
||
: status === "warn" ? "cc-kpi__value cc-kpi__value--warn"
|
||
: "cc-kpi__value cc-kpi__value--healthy";
|
||
const ledClass =
|
||
status === "critical" ? "cc-led cc-led--critical"
|
||
: status === "warn" ? "cc-led cc-led--warn"
|
||
: "cc-led cc-led--healthy";
|
||
const bars = spark(sparkSeed);
|
||
|
||
return (
|
||
<div className="cc-kpi cc-rise" data-stagger={delay}>
|
||
<Corners />
|
||
<div className="cc-kpi__head">
|
||
<span className="cc-kpi__label">{label}</span>
|
||
<span className={ledClass} />
|
||
</div>
|
||
<div className={valueClass}>
|
||
{typeof value === "number" ? value.toLocaleString() : value}
|
||
{unit && <span className="cc-kpi__unit"> {unit}</span>}
|
||
</div>
|
||
{sub && <div className="cc-kpi__sub">{sub}</div>}
|
||
<div className="cc-kpi__spark" aria-hidden>
|
||
{bars.map((v, i) => (
|
||
<span
|
||
key={i}
|
||
className={barClass(v, status === "critical", status === "warn")}
|
||
style={{ height: `${Math.max(2, v * 22)}px` }}
|
||
/>
|
||
))}
|
||
</div>
|
||
<span className="cc-kpi__serial" style={{ position: "absolute", right: 12, bottom: 8 }}>
|
||
{serial}
|
||
</span>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// ── AI Briefing ────────────────────────────────────────────────────────────
|
||
|
||
function AIBriefing({ metrics, brandHealth }: {
|
||
metrics: PlatformMetrics | null;
|
||
brandHealth: BrandHealth[];
|
||
}) {
|
||
const items = useMemo(() => {
|
||
if (!metrics) return [] as { idx: string; text: React.ReactNode }[];
|
||
const out: { idx: string; text: React.ReactNode }[] = [];
|
||
if (metrics.orders_today > 0) {
|
||
out.push({ idx: "01", text: <><b>{metrics.orders_today}</b> order{metrics.orders_today !== 1 ? "s" : ""} placed across all brands today.</> });
|
||
}
|
||
if (Number(metrics.revenue_today) > 0) {
|
||
out.push({ idx: "02", text: <><b>{formatCurrencyFull(Number(metrics.revenue_today))}</b> in revenue processed · target pacing on track.</> });
|
||
}
|
||
if (metrics.active_routes > 0) {
|
||
out.push({ idx: "03", text: <><b>{metrics.active_routes}</b> active route{metrics.active_routes !== 1 ? "s" : ""} running · all on schedule.</> });
|
||
}
|
||
if (metrics.pending_orders_today > 0) {
|
||
out.push({ idx: "04", text: <><b>{metrics.pending_orders_today}</b> order{metrics.pending_orders_today !== 1 ? "s" : ""} pending confirmation · review queue.</> });
|
||
}
|
||
if (metrics.failed_orders_today > 0) {
|
||
out.push({ idx: "05", text: <><em>{metrics.failed_orders_today} failed</em> · payment retry window open · triage required.</> });
|
||
}
|
||
const crit = brandHealth.filter(b => b.health_status === "critical").length;
|
||
if (crit > 0) {
|
||
out.push({ idx: "06", text: <><em>{crit} brand{crit !== 1 ? "s" : ""}</em> in critical state · investigate pain log entries.</> });
|
||
}
|
||
const warn = brandHealth.filter(b => b.health_status === "warning").length;
|
||
if (warn > 0 && crit === 0) {
|
||
out.push({ idx: "07", text: <><b>{warn}</b> brand{warn !== 1 ? "s" : ""} flagged for attention · monitoring.</> });
|
||
}
|
||
if (out.length === 0) {
|
||
out.push({ idx: "00", text: <span className="cc-briefing__line-ok">PLATFORM NOMINAL · no active issues · all channels green</span> });
|
||
}
|
||
return out;
|
||
}, [metrics, brandHealth]);
|
||
|
||
return (
|
||
<section className="cc-briefing cc-rise" data-stagger="4">
|
||
<Corners />
|
||
<div className="cc-briefing__main">
|
||
<div className="cc-briefing__head">
|
||
<span>Platform Briefing</span>
|
||
<span className="cc-briefing__head-meta">
|
||
<span className="cc-led cc-led--live" />
|
||
<span>GENERATED · {new Date().toLocaleTimeString("en-US", { hour12: false })}</span>
|
||
</span>
|
||
</div>
|
||
<div className="cc-briefing__lines">
|
||
{items.map(it => (
|
||
<div key={it.idx} className="cc-briefing__line">
|
||
<span className="cc-briefing__line-idx">{it.idx}</span>
|
||
<span>{it.text}</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
<div className="cc-briefing__model" aria-label="Model metadata">
|
||
<div className="cc-briefing__model-row"><span>Source</span><b>live.rpc</b></div>
|
||
<div className="cc-briefing__model-row"><span>Window</span><b>24h</b></div>
|
||
<div className="cc-briefing__model-row"><span>Build</span><b>cc-26.6</b></div>
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|
||
|
||
// ── Brand Health Panel ─────────────────────────────────────────────────────
|
||
|
||
function BrandPanel({ brand, delay }: { brand: BrandHealth; delay: number }) {
|
||
const ledClass = HEALTH_LED[brand.health_status] ?? "cc-led--off";
|
||
const statusClass = HEALTH_CLASS[brand.health_status] ?? "";
|
||
const statusLabel = HEALTH_LABEL[brand.health_status] ?? "UNKNOWN";
|
||
|
||
return (
|
||
<article className={`cc-brand ${statusClass} cc-rise`} data-stagger={delay}>
|
||
<Corners />
|
||
<div className="cc-brand__head">
|
||
<div>
|
||
<div className="cc-brand__name">
|
||
<span className={`cc-led ${ledClass}`} />
|
||
{brand.brand_name}
|
||
</div>
|
||
<div className="cc-brand__slug">
|
||
<b>/{brand.brand_slug}</b> · {brand.plan_tier}
|
||
</div>
|
||
</div>
|
||
<span className={`cc-brand__status cc-brand__status--${brand.health_status}`}>
|
||
{statusLabel}
|
||
</span>
|
||
</div>
|
||
|
||
<div className="cc-brand__metrics">
|
||
<div className="cc-brand__metric">
|
||
<span className="cc-brand__metric-label">Orders</span>
|
||
<span className="cc-brand__metric-value">{brand.orders_today}</span>
|
||
</div>
|
||
<div className="cc-brand__metric">
|
||
<span className="cc-brand__metric-label">Revenue</span>
|
||
<span className="cc-brand__metric-value">{formatCurrency(Number(brand.revenue_today))}</span>
|
||
</div>
|
||
<div className="cc-brand__metric">
|
||
<span className="cc-brand__metric-label">Routes</span>
|
||
<span className={`cc-brand__metric-value ${brand.failed_orders > 0 ? "cc-brand__metric-value--critical" : ""}`}>
|
||
{brand.active_stops}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
{brand.open_pain_items > 0 && (
|
||
<div className="cc-brand__pain">
|
||
<span className="cc-led cc-led--critical" />
|
||
<span><b>{brand.open_pain_items}</b> open issue{brand.open_pain_items !== 1 ? "s" : ""} on this brand</span>
|
||
<span>Founder Queue</span>
|
||
</div>
|
||
)}
|
||
</article>
|
||
);
|
||
}
|
||
|
||
// ── Pain Log Item ─────────────────────────────────────────────────────────
|
||
|
||
function PainItem({
|
||
item, idx, onResolve, onSnooze,
|
||
}: {
|
||
item: PainLogItem;
|
||
idx: number;
|
||
onResolve: (id: string) => void;
|
||
onSnooze: (id: string) => void;
|
||
}) {
|
||
const ledClass = SEVERITY_LED[item.severity] ?? "cc-led--off";
|
||
const severityClass = `cc-pain--${item.severity}`;
|
||
const serial = `P.${String(idx).padStart(3, "0")}`;
|
||
|
||
return (
|
||
<article className={`cc-pain ${severityClass} cc-rise`} data-stagger={Math.min(idx + 1, 10)}>
|
||
<Corners />
|
||
<div className="cc-pain__head">
|
||
<span className="cc-pain__severity" style={{
|
||
color: item.severity === "critical" ? "var(--cc-critical)"
|
||
: item.severity === "high" ? "var(--cc-warn)"
|
||
: item.severity === "medium" ? "var(--cc-amber)"
|
||
: "var(--cc-muted)"
|
||
}}>
|
||
<span className={ledClass} />
|
||
{item.severity}
|
||
</span>
|
||
<span className="cc-pain__title">{item.title}</span>
|
||
<div className="cc-pain__actions">
|
||
<button onClick={() => onSnooze(item.id)} className="cc-btn cc-btn--snooze">Snooze</button>
|
||
<button onClick={() => onResolve(item.id)} className="cc-btn cc-btn--resolved">Done</button>
|
||
</div>
|
||
</div>
|
||
<div className="cc-pain__foot">
|
||
<span>{serial}</span>
|
||
<span className="cc-pain__foot-sep">·</span>
|
||
{item.brand_name && <><span><b>{item.brand_name}</b></span><span className="cc-pain__foot-sep">·</span></>}
|
||
<span><b>{item.category}</b></span>
|
||
<span className="cc-pain__foot-sep">·</span>
|
||
<span>{item.created_at ? timeAgo(item.created_at) : ""}</span>
|
||
</div>
|
||
</article>
|
||
);
|
||
}
|
||
|
||
// ── Activity Feed Row ──────────────────────────────────────────────────────
|
||
|
||
function ActivityRow({ event, idx }: { event: ActivityEvent; idx: number }) {
|
||
const codeClass = FEED_CODE_CLASS[event.event_type] ?? "cc-feed__code--default";
|
||
const code = event.event_type.replace(/_/g, " ").toUpperCase();
|
||
|
||
return (
|
||
<div className="cc-feed__row cc-rise" data-stagger={Math.min(idx + 1, 10)}>
|
||
<span className="cc-feed__time">
|
||
{event.created_at ? formatTime(event.created_at) : "—"}
|
||
</span>
|
||
<span className={`cc-feed__code ${codeClass}`}>{code}</span>
|
||
<span className="cc-feed__detail">
|
||
{event.brand_name && <span className="cc-feed__brand">{event.brand_name}</span>}
|
||
<span className="cc-feed__entity">{event.entity_type ?? "event"}</span>
|
||
{event.entity_id && (
|
||
<span className="cc-feed__entity-id">#{event.entity_id.slice(0, 8)}</span>
|
||
)}
|
||
</span>
|
||
<span className="cc-feed__ago">{event.created_at ? timeAgo(event.created_at) : ""}</span>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// ── Main ──────────────────────────────────────────────────────────────────
|
||
|
||
export default function CommandCenterDashboard() {
|
||
const [metrics, setMetrics] = useState<PlatformMetrics | null>(null);
|
||
const [activity, setActivity] = useState<ActivityEvent[]>([]);
|
||
const [brandHealth, setBrandHealth] = useState<BrandHealth[]>([]);
|
||
const [painLog, setPainLog] = useState<PainLogItem[]>([]);
|
||
const [loading, setLoading] = useState(true);
|
||
const [error, setError] = useState<string | null>(null);
|
||
const [resolving, setResolving] = useState<Set<string>>(new Set());
|
||
const [showAddForm, setShowAddForm] = useState(false);
|
||
const [tvMode, setTvMode] = useState(false);
|
||
const [addForm, setAddForm] = useState({ severity: "medium", category: "operations", title: "", description: "" });
|
||
const [snoozed, setSnoozed] = useState<Set<string>>(new Set());
|
||
|
||
const fetchAll = useCallback(async () => {
|
||
try {
|
||
const [m, a, b, p] = await Promise.all([
|
||
getPlatformMetrics(),
|
||
getPlatformActivityFeed(),
|
||
getBrandHealthSnapshot(),
|
||
getPainLog(),
|
||
]);
|
||
setMetrics(m);
|
||
setActivity(a);
|
||
setBrandHealth(b);
|
||
setPainLog(p);
|
||
setError(null);
|
||
} catch (err) {
|
||
setError(err instanceof Error ? err.message : "Failed to load");
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}, []);
|
||
|
||
useEffect(() => {
|
||
fetchAll();
|
||
const iv = setInterval(fetchAll, REFRESH_INTERVAL);
|
||
return () => clearInterval(iv);
|
||
}, [fetchAll]);
|
||
|
||
const handleResolve = async (id: string) => {
|
||
if (resolving.has(id)) return;
|
||
setResolving(prev => new Set([...prev, id]));
|
||
try {
|
||
const r = await resolvePainLogItem(id);
|
||
if (r.success) setPainLog(prev => prev.filter(i => i.id !== id));
|
||
} finally {
|
||
setResolving(prev => { const n = new Set(prev); n.delete(id); return n; });
|
||
}
|
||
};
|
||
|
||
const handleSnooze = (id: string) => {
|
||
setSnoozed(prev => new Set([...prev, id]));
|
||
setTimeout(() => {
|
||
setSnoozed(prev => { const n = new Set(prev); n.delete(id); return n; });
|
||
}, 3600000);
|
||
};
|
||
|
||
const handleAdd = async () => {
|
||
if (!addForm.title.trim()) return;
|
||
const r = await createPainLogItem({
|
||
severity: addForm.severity as "low" | "medium" | "high" | "critical",
|
||
category: addForm.category,
|
||
title: addForm.title,
|
||
description: addForm.description || null,
|
||
});
|
||
if (r.success) {
|
||
setAddForm({ severity: "medium", category: "operations", title: "", description: "" });
|
||
setShowAddForm(false);
|
||
fetchAll();
|
||
}
|
||
};
|
||
|
||
const openItems = painLog.filter(p => p.status === "open" && !snoozed.has(p.id));
|
||
|
||
// ── Loading state ─────────────────────────────────────────────────────
|
||
if (loading) {
|
||
return (
|
||
<div className="cc-schematic" data-tv={tvMode ? "true" : "false"}>
|
||
<TopBar brands={[]} />
|
||
<div className="cc-frame">
|
||
<div className="cc-masthead cc-rise">
|
||
<div>
|
||
<div className="cc-masthead__eyebrow">{"// "}00 · BOOT SEQUENCE</div>
|
||
<h1 className="cc-masthead__title">Command <em>Center</em></h1>
|
||
</div>
|
||
</div>
|
||
<div className="cc-skeleton cc-rise" data-stagger="1" style={{ marginTop: 32 }}>
|
||
<span className="cc-skeleton__bar">▌<span>▌</span><span>▌</span></span>
|
||
<span>Initializing platform channels</span>
|
||
<span style={{ marginLeft: "auto", color: "var(--cc-faint)" }}>{"// "}awaiting telemetry</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// ── Error state ("Signal Lost") ────────────────────────────────────────
|
||
if (error) {
|
||
return (
|
||
<div className="cc-schematic" data-tv={tvMode ? "true" : "false"}>
|
||
<TopBar brands={[]} />
|
||
<div className="cc-signal-lost">
|
||
<div className="cc-signal-lost__panel cc-rise">
|
||
<Corners />
|
||
<div className="cc-signal-lost__eyebrow">
|
||
<span>SIGNAL LOST · COMMS OFFLINE</span>
|
||
</div>
|
||
<h2 className="cc-signal-lost__title">
|
||
Platform <em>Disconnected</em>
|
||
</h2>
|
||
<p className="cc-signal-lost__sub">
|
||
The Command Center cannot reach the platform RPCs right now.
|
||
This is usually a transient database or auth issue — the data is safe, the connection isn{`'`}t.
|
||
</p>
|
||
<div className="cc-signal-lost__error">{error}</div>
|
||
<div className="cc-signal-lost__actions">
|
||
<button onClick={fetchAll} className="cc-btn cc-btn--primary">
|
||
<span>↻</span> Reconnect
|
||
</button>
|
||
<a href="/admin" className="cc-btn cc-btn--ghost">Return to admin</a>
|
||
</div>
|
||
<div className="cc-signal-lost__hint">
|
||
<span>{"// "}SUGGESTED · check DATABASE_URL · re-run migrations</span>
|
||
<span>CODE: <b style={{ color: "var(--cc-critical)" }}>RPC_UNREACHABLE</b></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// ── Main state ────────────────────────────────────────────────────────
|
||
return (
|
||
<div className="cc-schematic" data-tv={tvMode ? "true" : "false"}>
|
||
<TopBar brands={brandHealth} />
|
||
|
||
<div className="cc-frame">
|
||
{/* ── Masthead ───────────────────────────────────────────────────── */}
|
||
<div className="cc-masthead cc-rise">
|
||
<div>
|
||
<div className="cc-masthead__eyebrow">{"// "}01 · PLATFORM OPERATIONS</div>
|
||
<h1 className="cc-masthead__title">Command <em>Center</em></h1>
|
||
<p className="cc-masthead__sub">
|
||
Real-time platform telemetry · {brandHealth.length} active brand{brandHealth.length !== 1 ? "s" : ""} ·
|
||
last sync <b>just now</b> · refresh every <b>30s</b>
|
||
</p>
|
||
</div>
|
||
<div className="cc-masthead__actions">
|
||
<button onClick={fetchAll} className="cc-btn cc-btn--ghost">
|
||
<span>↻</span> Refresh
|
||
</button>
|
||
<button onClick={() => setTvMode(v => !v)} className="cc-btn">
|
||
{tvMode ? "Exit TV" : "TV Mode"}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* ── KPI Row ────────────────────────────────────────────────────── */}
|
||
<section className="cc-section">
|
||
<div className="cc-section__head">
|
||
<span className="cc-section__serial">{"// "}02</span>
|
||
<span className="cc-section__title">KPI OVERVIEW</span>
|
||
<span className="cc-section__rule" />
|
||
<span className="cc-section__count">06</span>
|
||
</div>
|
||
<div className="cc-kpi-grid">
|
||
<KPICard
|
||
serial="M.01" label="Active Brands" delay={1}
|
||
value={metrics?.active_brands ?? 0}
|
||
sub={`${brandHealth.filter(b => b.health_status === "healthy").length} healthy`}
|
||
sparkSeed="brands" status="healthy"
|
||
/>
|
||
<KPICard
|
||
serial="M.02" label="Orders Today" delay={2}
|
||
value={metrics?.orders_today ?? 0}
|
||
sub={`${metrics?.pending_orders_today ?? 0} pending`}
|
||
sparkSeed="orders" status={(metrics?.failed_orders_today ?? 0) > 0 ? "critical" : "healthy"}
|
||
/>
|
||
<KPICard
|
||
serial="M.03" label="Revenue Today" delay={3}
|
||
value={formatCurrency(Number(metrics?.revenue_today ?? 0))}
|
||
sub="24h window"
|
||
sparkSeed="revenue" status="healthy"
|
||
/>
|
||
<KPICard
|
||
serial="M.04" label="Active Routes" delay={4}
|
||
value={metrics?.active_routes ?? 0}
|
||
sub="In field"
|
||
sparkSeed="routes" status={(metrics?.pending_orders_today ?? 0) > 5 ? "warn" : "healthy"}
|
||
/>
|
||
<KPICard
|
||
serial="M.05" label="Failed Orders" delay={5}
|
||
value={metrics?.failed_orders_today ?? 0}
|
||
sub={metrics?.failed_orders_today ? "Needs attention" : "All clean"}
|
||
sparkSeed="failed" status={(metrics?.failed_orders_today ?? 0) > 0 ? "critical" : "healthy"}
|
||
/>
|
||
<KPICard
|
||
serial="M.06" label="Pending Orders" delay={6}
|
||
value={metrics?.pending_orders_today ?? 0}
|
||
sub="Awaiting confirmation"
|
||
sparkSeed="pending" status={(metrics?.pending_orders_today ?? 0) > 5 ? "warn" : "healthy"}
|
||
/>
|
||
</div>
|
||
</section>
|
||
|
||
{/* ── AI Briefing ─────────────────────────────────────────────────── */}
|
||
<section className="cc-section">
|
||
<div className="cc-section__head">
|
||
<span className="cc-section__serial">{"// "}03</span>
|
||
<span className="cc-section__title">DAILY BRIEFING</span>
|
||
<span className="cc-section__rule" />
|
||
<span className="cc-section__count">AUTO</span>
|
||
</div>
|
||
<AIBriefing metrics={metrics} brandHealth={brandHealth} />
|
||
</section>
|
||
|
||
{/* ── Two-column body ────────────────────────────────────────────── */}
|
||
<div className="cc-grid">
|
||
{/* LEFT: Brand Health + Activity */}
|
||
<div>
|
||
<section className="cc-section">
|
||
<div className="cc-section__head">
|
||
<span className="cc-section__serial">{"// "}04</span>
|
||
<span className="cc-section__title">BRAND HEALTH</span>
|
||
<span className="cc-section__rule" />
|
||
<span className="cc-section__count">{brandHealth.length}</span>
|
||
</div>
|
||
{brandHealth.length === 0 ? (
|
||
<div className="cc-empty cc-rise">
|
||
<div className="cc-empty__title">{"// "}NO ACTIVE BRANDS</div>
|
||
<div className="cc-empty__sub">No brands registered on this platform.</div>
|
||
</div>
|
||
) : (
|
||
<div style={{ display: "grid", gap: 16 }}>
|
||
{brandHealth.map((b, i) => (
|
||
<BrandPanel key={b.brand_id} brand={b} delay={i + 1} />
|
||
))}
|
||
</div>
|
||
)}
|
||
</section>
|
||
|
||
<section className="cc-section">
|
||
<div className="cc-section__head">
|
||
<span className="cc-section__serial">{"// "}05</span>
|
||
<span className="cc-section__title">LIVE ACTIVITY</span>
|
||
<span className="cc-section__rule" />
|
||
<span className="cc-section__count">{activity.length}</span>
|
||
</div>
|
||
<div className="cc-feed cc-rise" data-stagger="3">
|
||
<Corners />
|
||
<div className="cc-feed__list">
|
||
{activity.length === 0 ? (
|
||
<div className="cc-empty">
|
||
<div className="cc-empty__title">{"// "}NO EVENTS</div>
|
||
<div className="cc-empty__sub">Waiting for telemetry.</div>
|
||
</div>
|
||
) : (
|
||
activity.slice(0, 30).map((e, i) => (
|
||
<ActivityRow key={e.id} event={e} idx={i} />
|
||
))
|
||
)}
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
|
||
{/* RIGHT: Founder Queue + Quick Access */}
|
||
<div>
|
||
<section className="cc-section">
|
||
<div className="cc-section__head">
|
||
<span className="cc-section__serial">{"// "}06</span>
|
||
<span className="cc-section__title">FOUNDER QUEUE</span>
|
||
<span className="cc-section__rule" />
|
||
<span className="cc-section__count">{openItems.length}</span>
|
||
<button
|
||
onClick={() => setShowAddForm(v => !v)}
|
||
className="cc-btn cc-btn--ghost"
|
||
style={{ marginLeft: 12 }}
|
||
>
|
||
{showAddForm ? "Cancel" : "+ Log Issue"}
|
||
</button>
|
||
</div>
|
||
|
||
{showAddForm && (
|
||
<div className="cc-form cc-rise" data-stagger="1" style={{ marginBottom: 16 }}>
|
||
<div className="cc-form__head">NEW PAIN LOG ENTRY</div>
|
||
<div className="cc-form__row">
|
||
<select
|
||
value={addForm.severity}
|
||
onChange={e => setAddForm(f => ({ ...f, severity: e.target.value }))}
|
||
className="cc-field"
|
||
>
|
||
<option value="low">LOW</option>
|
||
<option value="medium">MEDIUM</option>
|
||
<option value="high">HIGH</option>
|
||
<option value="critical">CRITICAL</option>
|
||
</select>
|
||
<input
|
||
value={addForm.category}
|
||
onChange={e => setAddForm(f => ({ ...f, category: e.target.value }))}
|
||
placeholder="category (e.g. deployment)"
|
||
className="cc-field"
|
||
/>
|
||
</div>
|
||
<input
|
||
value={addForm.title}
|
||
onChange={e => setAddForm(f => ({ ...f, title: e.target.value }))}
|
||
placeholder="issue title..."
|
||
className="cc-field"
|
||
/>
|
||
<textarea
|
||
value={addForm.description}
|
||
onChange={e => setAddForm(f => ({ ...f, description: e.target.value }))}
|
||
placeholder="description (optional)"
|
||
rows={2}
|
||
className="cc-field"
|
||
style={{ resize: "none", fontFamily: "var(--cc-font-mono)" }}
|
||
/>
|
||
<div className="cc-form__actions">
|
||
<button onClick={handleAdd} className="cc-btn cc-btn--primary">Log Issue</button>
|
||
<button onClick={() => setShowAddForm(false)} className="cc-btn cc-btn--ghost">Discard</button>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{openItems.length === 0 ? (
|
||
<div className="cc-empty cc-rise">
|
||
<div className="cc-empty__title">{"// "}QUEUE CLEAR</div>
|
||
<div className="cc-empty__sub">No open founder pain items. Add one to track it here.</div>
|
||
</div>
|
||
) : (
|
||
<div style={{ display: "grid", gap: 12 }}>
|
||
{openItems.map((item, i) => (
|
||
<PainItem
|
||
key={item.id}
|
||
item={item}
|
||
idx={i + 1}
|
||
onResolve={handleResolve}
|
||
onSnooze={handleSnooze}
|
||
/>
|
||
))}
|
||
</div>
|
||
)}
|
||
</section>
|
||
|
||
<section className="cc-section">
|
||
<div className="cc-section__head">
|
||
<span className="cc-section__serial">{"// "}07</span>
|
||
<span className="cc-section__title">QUICK ACCESS</span>
|
||
<span className="cc-section__rule" />
|
||
<span className="cc-section__count">{QUICK_ACCESS.length}</span>
|
||
</div>
|
||
<div className="cc-links cc-rise" data-stagger="2">
|
||
<Corners />
|
||
{QUICK_ACCESS.map((link, i) => (
|
||
<a
|
||
key={link.href}
|
||
href={link.href}
|
||
className="cc-link"
|
||
>
|
||
<span className="cc-link__serial">L.{String(i + 1).padStart(2, "0")}</span>
|
||
<span className="cc-link__label">{link.label}</span>
|
||
<span className="cc-link__arrow">→</span>
|
||
</a>
|
||
))}
|
||
</div>
|
||
</section>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|