From 87a56a66c400080feb76672a0bbd8e98317eb3e0 Mon Sep 17 00:00:00 2001 From: Ben Stull Date: Thu, 11 Jun 2026 16:35:49 -0700 Subject: [PATCH] =?UTF-8?q?feat(products-ui):=20import=20run=20detail=20(?= =?UTF-8?q?=C2=A75.5,=20PUC-4/5/8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- frontend/src/screens/products/RunDetail.tsx | 90 ++++++++++++++++++++- 1 file changed, 87 insertions(+), 3 deletions(-) diff --git a/frontend/src/screens/products/RunDetail.tsx b/frontend/src/screens/products/RunDetail.tsx index cabba61..cd79704 100644 --- a/frontend/src/screens/products/RunDetail.tsx +++ b/frontend/src/screens/products/RunDetail.tsx @@ -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 = { + 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(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 ( + + ← Products + + ); + } + if (loadFail === "failed") { + return ( + + Something went wrong on our side.{" "} + + + ); + } + if (!run) return

Loading…

; + + const dialectLabel = run.dialect === "canonical" ? "Canonical format" : run.dialect; + + return ( +
+

+ ← Products +

+

{run.file_name}

+

+ Imported {new Date(run.created_at).toLocaleString()} by {run.by} · {dialectLabel} +

+

+ {run.products_added} added · {run.products_updated} updated · {run.rows_errored} rows in + error +

+

{STATUS_LABELS[run.status] ?? run.status}

+ {run.errors.length > 0 && ( + + + + + + + + + + {run.errors.map((e, i) => ( + + + + + + ))} + +
LineColumnProblem
{e.line}{e.column ?? ""}{e.message}
+ )} +
+ ); }