feat: enhance product listing with additional metrics and sorting options

This commit is contained in:
Victor Noguera
2026-04-13 03:04:28 -04:00
parent 937fe5da40
commit 299ad7a1a6
4 changed files with 60 additions and 21 deletions

View File

@@ -87,6 +87,10 @@ type ProductListItem = {
verdict: "FBA" | "FBM" | "SKIP";
confidence: number | null;
sellability_status: string | null;
monthly_sold: number | null;
seller_count: number | null;
sales_rank: number | null;
current_price: number | null;
fetched_at: string;
};
@@ -615,6 +619,7 @@ function ProductList({ verdict, onBack }: { verdict: VerdictFilter; onBack: () =
const [activeVerdict, setActiveVerdict] = useState<VerdictFilter>(verdict);
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(25);
const [sort, setSort] = useState<SortState>({ field: "monthly_sold", direction: "DESC" });
useEffect(() => {
setActiveVerdict(verdict);
@@ -626,6 +631,7 @@ function ProductList({ verdict, onBack }: { verdict: VerdictFilter; onBack: () =
async function load() {
setLoading(true);
const params = new URLSearchParams({ page: String(page), pageSize: String(pageSize) });
params.set("sort", buildSortValue(sort));
if (search) params.set("q", search);
if (activeVerdict) params.set("verdict", activeVerdict);
const res = await fetch(`/api/products?${params.toString()}`);
@@ -640,7 +646,7 @@ function ProductList({ verdict, onBack }: { verdict: VerdictFilter; onBack: () =
return () => {
cancelled = true;
};
}, [search, activeVerdict, page, pageSize]);
}, [search, activeVerdict, page, pageSize, sort]);
return (
<div className="page">
@@ -667,36 +673,32 @@ function ProductList({ verdict, onBack }: { verdict: VerdictFilter; onBack: () =
<table>
<thead>
<tr>
<th>ASIN</th>
<th>Verdict</th>
<th>Product</th>
<th>Brand</th>
<th>Category</th>
<th>Confidence</th>
<th>Process</th>
<th>Run ID</th>
<th>Fetched</th>
<th><button onClick={() => setSort(nextSort(sort, "asin"))}>ASIN</button></th>
<th><button onClick={() => setSort(nextSort(sort, "verdict"))}>Verdict</button></th>
<th><button onClick={() => setSort(nextSort(sort, "monthly_sold"))}>Monthly Sold</button></th>
<th><button onClick={() => setSort(nextSort(sort, "seller_count"))}>Sellers</button></th>
<th><button onClick={() => setSort(nextSort(sort, "sales_rank"))}>Sales Rank</button></th>
<th><button onClick={() => setSort(nextSort(sort, "current_price"))}>Current Price</button></th>
<th className="product-col"><button onClick={() => setSort(nextSort(sort, "product_name"))}>Product</button></th>
</tr>
</thead>
<tbody>
{loading ? (
<tr><td colSpan={9}>Loading...</td></tr>
<tr><td colSpan={7}>Loading...</td></tr>
) : items?.items.length ? (
items.items.map((item) => (
<tr key={`${item.processType}-${item.runId}-${item.asin}-${item.fetched_at}`}>
<td><a href={`http://amazon.com/dp/${item.asin}`} target="_blank" rel="noreferrer">{item.asin}</a></td>
<td><span className={verdictBadgeClass(item.verdict)}>{item.verdict}</span></td>
<td>{item.product_name || "-"}</td>
<td>{item.brand || "-"}</td>
<td>{item.category || "-"}</td>
<td>{formatNumber(item.confidence)}</td>
<td>{item.processType}</td>
<td>{item.runId}</td>
<td>{formatDate(item.fetched_at)}</td>
<td>{formatNumber(item.monthly_sold)}</td>
<td>{formatNumber(item.seller_count)}</td>
<td>{formatNumber(item.sales_rank)}</td>
<td>{formatCurrency(item.current_price)}</td>
<td className="product-col" title={item.product_name || undefined}>{item.product_name || "-"}</td>
</tr>
))
) : (
<tr><td colSpan={9}>No products found</td></tr>
<tr><td colSpan={7}>No products found</td></tr>
)}
</tbody>
</table>