Files
PacketStation/frontend/src/components/RunsTable.tsx
T
spencermandClaude Fable 5.1 591fe90076
ci / frontend (push) Successful in 30s
ci / server (push) Successful in 4m12s
ci / image (push) Successful in 46s
ci / e2e (push) Successful in 1m24s
Phase 3: VISUAL feed and M-Files drawings in the worker
Migration 0004 (VISUAL and drawing caches, go-live ignored list, resource
map, job tables, order-derived columns with provenance; work order key
includes the operation sequence). The real worker on APScheduler with
settings re-read each fire, pause, run now, advisory locks, job_runs and a
heartbeat. The visual-feed job (five VisualKit reads, COPY and MERGE cache,
suspect-batch guard, the apply in one transaction: drafts, change orders,
retire-moved, work order gone, closed and cancelled observed, projects and
units from order links, auto-release) and the fingerprint. The drawing-lookup
job (queue, budget, download on version change, sha256 into assets, decision
table, staging for rev approval, drawing-origin change orders). Clerical From
VISUAL panel, flags, rev approval, hand uploads, change-order review; Admin
Feed settings and resource map. Synthetic fixture generator and a
fixture-driven worker in CI with a Playwright scenario. Docs updated.

Verified: 3249 server tests and 13 Playwright scenarios on the docker-1 CI
stack, 76 frontend tests, doctor at 0004; one feed and one drawing run
against VISUAL DEV and the vault from the developer machine (counts in the
changelog).

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-10-16 14:45:38 -06:00

51 lines
2.0 KiB
TypeScript

import type { JobRun } from "../api/types";
import { count, runPillClass, RUN_STATUS_LABEL, TRIGGER_LABEL } from "../lib/feed";
import { fmtTime } from "../lib/format";
/** The last runs of the VISUAL feed as a small table. `attached` maps a run id to the drawings attached after it. */
export function RunsTable({ runs, attached, label = "Last runs" }: { runs: JobRun[]; attached?: Map<number, number>; label?: string }) {
if (!runs.length) return <div className="empty slim">No runs yet.</div>;
return (
<div className="tw">
<table aria-label={label}>
<thead>
<tr>
<th>When</th>
<th>Trigger</th>
<th>Status</th>
<th className="num">New</th>
<th className="num">Updated</th>
<th className="num">Change orders</th>
<th className="num">Drawings attached</th>
<th>Errors</th>
</tr>
</thead>
<tbody>
{runs.map((r) => (
<tr key={r.id}>
<td className="small mono nowrap">{fmtTime(r.started_at)}</td>
<td className="small">{TRIGGER_LABEL[r.trigger] ?? r.trigger}</td>
<td>
<span className={runPillClass(r.status)}>{RUN_STATUS_LABEL[r.status] ?? r.status}</span>
</td>
<td className="num">{count(r, "new_drafts")}</td>
<td className="num">{count(r, "drafts_updated")}</td>
<td className="num">{count(r, "cos_opened") + count(r, "cos_reaimed")}</td>
<td className="num">{attached?.get(r.id) ?? count(r, "attached")}</td>
<td className="small">
{r.errors.length ? (
<span className="due over" title={r.errors.join("\n")}>
{r.errors.length === 1 ? r.errors[0] : `${r.errors.length} errors: ${r.errors[0] ?? ""}`}
</span>
) : (
<span className="muted">None</span>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
);
}