feat: enhance Stalker functionality with additional product details and analysis capabilities

This commit is contained in:
Victor Noguera
2026-05-19 19:57:53 -04:00
parent f6178a665c
commit 0552d183b3
7 changed files with 795 additions and 26 deletions

View File

@@ -151,6 +151,18 @@ type StalkerProductItem = {
can_sell: number;
sellability_status: string;
sellability_reason: string | null;
product_title: string | null;
brand: string | null;
category_tree: string | null;
current_price: number | null;
avg_price_90d: number | null;
sales_rank: number | null;
monthly_sold: number | null;
seller_count: number | null;
amazon_is_seller: number | null;
verdict: "FBA" | "FBM" | "SKIP" | null;
confidence: number | null;
reasoning: string | null;
last_seen_at: string;
};
@@ -202,6 +214,18 @@ function formatBoolean(value: number | null | undefined): string {
return value === 1 ? "Yes" : "No";
}
function parseStringArrayJson(value: string | null | undefined): string[] {
if (!value) return [];
try {
const parsed = JSON.parse(value);
return Array.isArray(parsed)
? parsed.filter((item): item is string => typeof item === "string")
: [];
} catch {
return [];
}
}
function buildSortValue(sort: SortState): string {
return `${sort.field}:${sort.direction}`;
}
@@ -1113,7 +1137,7 @@ function StalkerProductsExplorer({
const [runId, setRunId] = useState("");
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(50);
const [sort, setSort] = useState<SortState>({ field: "last_seen_at", direction: "DESC" });
const [sort, setSort] = useState<SortState>({ field: "monthly_sold", direction: "DESC" });
useEffect(() => {
let cancelled = false;
@@ -1170,7 +1194,7 @@ function StalkerProductsExplorer({
<div className="card">
<div className="toolbar">
<input value={search} onChange={(e) => { setPage(1); setSearch(e.target.value); }} placeholder="Search ASIN or seller" />
<input value={search} onChange={(e) => { setPage(1); setSearch(e.target.value); }} placeholder="Search ASIN, product, brand, category, or seller" />
<input value={sellerId} onChange={(e) => { setPage(1); setSellerId(e.target.value.toUpperCase()); }} placeholder="Seller ID" />
<input value={runId} onChange={(e) => { setPage(1); setRunId(e.target.value); }} placeholder="Run ID" />
<select value={String(pageSize)} onChange={(e) => { setPage(1); setPageSize(Number(e.target.value)); }}>
@@ -1187,6 +1211,17 @@ function StalkerProductsExplorer({
<thead>
<tr>
<th><button onClick={() => setSort(nextSort(sort, "asin"))}>ASIN</button></th>
<th className="product-col"><button onClick={() => setSort(nextSort(sort, "product_title"))}>Product</button></th>
<th><button onClick={() => setSort(nextSort(sort, "brand"))}>Brand</button></th>
<th>Category</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, "amazon_is_seller"))}>Amazon Seller</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><button onClick={() => setSort(nextSort(sort, "avg_price_90d"))}>Avg 90d</button></th>
<th><button onClick={() => setSort(nextSort(sort, "verdict"))}>Verdict</button></th>
<th><button onClick={() => setSort(nextSort(sort, "confidence"))}>Confidence</button></th>
<th><button onClick={() => setSort(nextSort(sort, "seller_id"))}>Seller ID</button></th>
<th><button onClick={() => setSort(nextSort(sort, "seller_name"))}>Seller</button></th>
<th><button onClick={() => setSort(nextSort(sort, "rating_count"))}>Rating Count</button></th>
@@ -1197,21 +1232,35 @@ function StalkerProductsExplorer({
</thead>
<tbody>
{loading ? (
<tr><td colSpan={7}>Loading...</td></tr>
<tr><td colSpan={18}>Loading...</td></tr>
) : results?.items.length ? (
results.items.map((item) => (
<tr key={`${item.runId}-${item.seller_id}-${item.asin}`}>
<td><a href={`http://amazon.com/dp/${item.asin}`} target="_blank" rel="noreferrer">{item.asin}</a></td>
<td>{item.seller_id}</td>
<td>{item.seller_name || "-"}</td>
<td>{formatNumber(item.rating_count)}</td>
<td><span className="badge badge-ok">{item.sellability_status}</span></td>
<td>{item.runId}</td>
<td>{formatDate(item.last_seen_at)}</td>
</tr>
))
results.items.map((item) => {
const categories = parseStringArrayJson(item.category_tree);
return (
<tr key={`${item.runId}-${item.seller_id}-${item.asin}`}>
<td><a href={`http://amazon.com/dp/${item.asin}`} target="_blank" rel="noreferrer">{item.asin}</a></td>
<td className="product-col" title={item.product_title || undefined}>{item.product_title || "-"}</td>
<td>{item.brand || "-"}</td>
<td>{categories.at(-1) || "-"}</td>
<td>{formatNumber(item.monthly_sold)}</td>
<td>{formatNumber(item.seller_count)}</td>
<td>{formatAmazonSeller(item.amazon_is_seller)}</td>
<td>{formatNumber(item.sales_rank)}</td>
<td>{formatCurrency(item.current_price)}</td>
<td>{formatCurrency(item.avg_price_90d)}</td>
<td>{item.verdict ? <span className={verdictBadgeClass(item.verdict)}>{item.verdict}</span> : "-"}</td>
<td title={item.reasoning || undefined}>{formatNumber(item.confidence)}</td>
<td>{item.seller_id}</td>
<td>{item.seller_name || "-"}</td>
<td>{formatNumber(item.rating_count)}</td>
<td><span className="badge badge-ok">{item.sellability_status}</span></td>
<td>{item.runId}</td>
<td>{formatDate(item.last_seen_at)}</td>
</tr>
);
})
) : (
<tr><td colSpan={7}>No sellable Stalker products found</td></tr>
<tr><td colSpan={18}>No sellable Stalker products found</td></tr>
)}
</tbody>
</table>