112 lines
3.7 KiB
TypeScript
112 lines
3.7 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
|
|
type ImportStatus = "RAW" | "PENDING_MAPPING" | "MAPPED";
|
|
|
|
type SummaryRow = {
|
|
status: ImportStatus;
|
|
count: number;
|
|
};
|
|
|
|
type ByMerchantRow = {
|
|
merchantName: string;
|
|
platform: string;
|
|
status: ImportStatus;
|
|
count: number;
|
|
};
|
|
|
|
const API_BASE_URL =
|
|
process.env.NEXT_PUBLIC_API_BASE_URL ?? "http://localhost:8080";
|
|
|
|
export default function ImportStatusPage() {
|
|
const [summary, setSummary] = useState<SummaryRow[]>([]);
|
|
const [byMerchant, setByMerchant] = useState<ByMerchantRow[]>([]);
|
|
|
|
useEffect(() => {
|
|
async function load() {
|
|
const [summaryRes, byMerchantRes] = await Promise.all([
|
|
fetch(`${API_BASE_URL}/api/admin/import-status/summary`),
|
|
fetch(`${API_BASE_URL}/api/admin/import-status/by-merchant`),
|
|
]);
|
|
|
|
setSummary(await summaryRes.json());
|
|
setByMerchant(await byMerchantRes.json());
|
|
}
|
|
|
|
load();
|
|
}, []);
|
|
|
|
const total = summary.reduce((sum, row) => sum + row.count, 0);
|
|
|
|
return (
|
|
<main className="min-h-screen bg-black text-zinc-50">
|
|
<div className="mx-auto max-w-5xl px-4 py-6">
|
|
<h1 className="text-xl font-semibold tracking-tight">
|
|
Import Status Dashboard
|
|
</h1>
|
|
<p className="mt-1 text-sm text-zinc-400">
|
|
Quick sanity check on how clean (or not) your catalog is.
|
|
</p>
|
|
|
|
{/* Status cards */}
|
|
<div className="mt-4 grid grid-cols-1 gap-3 sm:grid-cols-3">
|
|
{summary.map((row) => (
|
|
<div
|
|
key={row.status}
|
|
className="rounded-lg border border-zinc-800 bg-zinc-950/80 p-3"
|
|
>
|
|
<div className="text-xs font-semibold uppercase tracking-[0.14em] text-zinc-500">
|
|
{row.status}
|
|
</div>
|
|
<div className="mt-1 text-2xl font-semibold text-amber-300">
|
|
{row.count}
|
|
</div>
|
|
<div className="mt-1 text-[0.7rem] text-zinc-500">
|
|
{total > 0
|
|
? `${((row.count / total) * 100).toFixed(1)}% of catalog`
|
|
: "—"}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Merchant breakdown */}
|
|
<section className="mt-6 rounded-lg border border-zinc-800 bg-zinc-950/80 p-3">
|
|
<h2 className="text-xs font-semibold uppercase tracking-[0.14em] text-zinc-500">
|
|
By Merchant & Platform
|
|
</h2>
|
|
<div className="mt-2 overflow-x-auto">
|
|
<table className="min-w-full text-xs">
|
|
<thead>
|
|
<tr className="border-b border-zinc-800 bg-zinc-900/60">
|
|
<th className="px-2 py-1 text-left text-zinc-400">Merchant</th>
|
|
<th className="px-2 py-1 text-left text-zinc-400">Platform</th>
|
|
<th className="px-2 py-1 text-left text-zinc-400">Status</th>
|
|
<th className="px-2 py-1 text-right text-zinc-400">Count</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{byMerchant.map((row, i) => (
|
|
<tr
|
|
key={`${row.merchantName}-${row.platform}-${row.status}-${i}`}
|
|
className="border-b border-zinc-900"
|
|
>
|
|
<td className="px-2 py-1 text-zinc-100">
|
|
{row.merchantName}
|
|
</td>
|
|
<td className="px-2 py-1 text-zinc-300">{row.platform}</td>
|
|
<td className="px-2 py-1 text-zinc-400">{row.status}</td>
|
|
<td className="px-2 py-1 text-right text-zinc-100">
|
|
{row.count}
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</main>
|
|
);
|
|
} |