feat(products-ui): import run detail (§5.5, PUC-4/5/8)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-11 16:35:49 -07:00
parent 4141285b89
commit 87a56a66c4
+87 -3
View File
@@ -1,5 +1,89 @@
// Run detail (SD-0002 §5.5) — stub; Task 14 replaces this with the run report.
// Run detail (SD-0002 §5.5) — report card for a completed or in-progress import run.
// NO images section this slice (SLICE-7). PUC-4/5/8.
import { useEffect, useState } from "react";
import { getRun, type RunDetail as RunDetailType } from "../../productsApi";
import { Banner } from "../../ui/kit";
const STATUS_LABELS: Record<string, string> = {
applying: "Importing…",
fetching_images: "Fetching images…",
complete: "Complete",
complete_with_problems: "Complete with problems",
};
export default function RunDetail({ runId }: { runId: number }) {
void runId;
return null;
const [run, setRun] = useState<RunDetailType | null>(null);
const [loadFail, setLoadFail] = useState<"gone" | "failed" | null>(null);
async function load() {
setLoadFail(null);
const resp = await getRun(runId);
if (!resp.ok) {
setLoadFail(resp.status === 404 ? "gone" : "failed");
return;
}
setRun(resp.value);
}
useEffect(() => {
void load();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [runId]);
if (loadFail === "gone") {
return (
<Banner tone="attn" title="No such import run">
<a href="#/products"> Products</a>
</Banner>
);
}
if (loadFail === "failed") {
return (
<Banner tone="attn" title="Couldn't load this import run">
Something went wrong on our side.{" "}
<button type="button" className="linklike" onClick={() => void load()}>
Retry
</button>
</Banner>
);
}
if (!run) return <p className="note">Loading</p>;
const dialectLabel = run.dialect === "canonical" ? "Canonical format" : run.dialect;
return (
<div className="products">
<p className="note">
<a href="#/products"> Products</a>
</p>
<h1>{run.file_name}</h1>
<p className="note">
Imported {new Date(run.created_at).toLocaleString()} by {run.by} · {dialectLabel}
</p>
<p className="note">
{run.products_added} added · {run.products_updated} updated · {run.rows_errored} rows in
error
</p>
<p className="note">{STATUS_LABELS[run.status] ?? run.status}</p>
{run.errors.length > 0 && (
<table className="errortable">
<thead>
<tr>
<th>Line</th>
<th>Column</th>
<th>Problem</th>
</tr>
</thead>
<tbody>
{run.errors.map((e, i) => (
<tr key={i}>
<td>{e.line}</td>
<td>{e.column ?? ""}</td>
<td>{e.message}</td>
</tr>
))}
</tbody>
</table>
)}
</div>
);
}