1
0
Fork 0
polaris-task-force/src/components/frontend/operations/AarView.tsx

162 lines
6.7 KiB
TypeScript

import Link from "next/link";
import { LedgerRow, type ReadoutCell } from "./LedgerRow";
import { NoticeState } from "./NoticeState";
import { ProvenanceChip } from "./ProvenanceChip";
import { STATUS_TONE_CLASSES, statusToneFor } from "./tokens";
import type { AarResult } from "@/lib/operations/surfaceQuery";
const headCell = "px-3 py-2 text-left font-mono text-[9px] uppercase tracking-widest text-white/50";
const cell = "px-3 py-2 align-middle text-xs";
function Section({ title, children }: { title: string; children: React.ReactNode }) {
return (
<section data-slot="aar-section" className="flex flex-col gap-2">
<h2 className="font-mono text-[9px] uppercase tracking-widest text-white/60">{title}</h2>
{children}
</section>
);
}
function countsRow(aar: AarResult): ReadoutCell[] {
return [
{ label: "Events", value: String(aar.counts.events), mono: true },
{ label: "Validated", value: String(aar.counts.validated), mono: true },
{ label: "Rejected", value: String(aar.counts.rejected), mono: true },
{ label: "Applied", value: String(aar.counts.applied), mono: true },
{ label: "Failed", value: String(aar.counts.failed), mono: true },
{ label: "Pending", value: String(aar.counts.pending), mono: true },
];
}
export function AarView({ aar }: { aar: AarResult }) {
const failedEffects = aar.effects.filter((e) => e.status === "failed" || e.status === "reversed");
const appliedEffects = aar.effects.filter((e) => e.status === "applied");
return (
<div className="flex flex-col gap-6" data-slot="operations-aar-view">
<LedgerRow cells={countsRow(aar)} />
<Section title="Timeline">
{aar.events.length === 0 ? (
<NoticeState variant="empty" message="No events recorded for this operation." />
) : (
<table className="w-full border-collapse">
<thead>
<tr className="border-b border-white/10">
<th className={headCell}>Event</th>
<th className={headCell}>Type</th>
<th className={headCell}>Status</th>
<th className={headCell}>Occurred</th>
</tr>
</thead>
<tbody>
{aar.events.map((ev) => (
<tr key={ev.id} className="border-b border-white/5 last:border-0">
<td className={`${cell} font-mono text-white/85`}>{ev.eventId}</td>
<td className={`${cell} font-mono`}>{ev.type}</td>
<td className={cell}>
<span
className={`border px-1.5 py-0.5 font-mono text-[10px] uppercase tracking-widest ${STATUS_TONE_CLASSES[statusToneFor(ev.status)]}`}
>
{ev.status}
</span>
{ev.errorCode && (
<span className="ml-2 text-[10px] text-rose-300">
{ev.errorCode}: {ev.errorMessage ?? "see notes"}
</span>
)}
</td>
<td className={`${cell} font-mono tabular-nums text-white/70`}>{ev.occurredAt}</td>
</tr>
))}
</tbody>
</table>
)}
</Section>
<Section title="Applied effects">
{appliedEffects.length === 0 ? (
<NoticeState variant="empty" message="No applied effects yet for this operation." />
) : (
<ul className="flex flex-col gap-1">
{appliedEffects.map((eff) => (
<li
key={eff.id}
className="flex items-center justify-between gap-2 border border-white/10 px-2 py-1.5"
>
<span className="min-w-0 truncate font-mono text-xs text-white/80">{eff.effectKey}</span>
<span className="flex shrink-0 items-center gap-2">
<span className="font-mono text-[10px] uppercase tracking-widest text-white/50">
{eff.effectType}
</span>
<ProvenanceChip label={eff.status} href="/operations/ledger" />
</span>
</li>
))}
</ul>
)}
</Section>
<Section title="Failed or reversed effects">
{failedEffects.length === 0 ? (
<NoticeState variant="empty" message="No failed or reversed effects. Clean run." />
) : (
<ul className="flex flex-col gap-1">
{failedEffects.map((eff) => (
<li key={eff.id} className="border border-rose-500/30 px-2 py-1.5">
<p className="font-mono text-xs text-rose-300">{eff.effectKey}</p>
<p className="text-xs text-white/70">
{eff.notes ?? "Effect did not apply; see the operations ledger for reconciliation state."}
</p>
</li>
))}
</ul>
)}
</Section>
{aar.canSeePressure ? (
<Section title="Zone pressure">
{aar.pressure.length === 0 ? (
<NoticeState variant="empty" message="No pressure effects recorded for this operation." />
) : (
<ul className="flex flex-col gap-1">
{aar.pressure.map((p, i) => (
<li key={`${p.zoneId}-${i}`} className="flex items-center justify-between border border-white/10 px-2 py-1.5">
<span className="font-mono text-xs text-white/80">Zone {p.zoneId}</span>
<span
className={`font-mono text-[10px] uppercase tracking-widest ${p.delta >= 0 ? "text-emerald-400" : "text-rose-400"}`}
>
{p.delta >= 0 ? `+${p.delta}` : p.delta}
</span>
</li>
))}
</ul>
)}
</Section>
) : (
<NoticeState variant="empty" message="Zone pressure is currently disabled." />
)}
<Section title="Reservations">
{aar.reservations.length === 0 ? (
<NoticeState variant="empty" message="No reservations linked to this operation." />
) : (
<ul className="flex flex-col gap-1">
{aar.reservations.map((r) => (
<li key={r.reservationKey} className="flex items-center justify-between border border-white/10 px-2 py-1.5">
<span className="min-w-0 truncate font-mono text-xs text-white/80">{r.reservationKey}</span>
<span className="font-mono text-[10px] uppercase tracking-widest text-white/50">{r.status}</span>
</li>
))}
</ul>
)}
</Section>
<p className="text-xs text-white/50">
<Link href="/operations/ledger" className="underline-offset-4 hover:underline">
Open the full operations ledger
</Link>
</p>
</div>
);
}