see /admin/mapping page.
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user