feat: enhance Stalker functionality with additional product details and analysis capabilities
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user