import { createRoot } from "react-dom/client"; import { useEffect, useMemo, useState } from "react"; type ProcessType = "lead_analysis" | "category_analysis"; type SortDirection = "ASC" | "DESC"; type Run = { processType: ProcessType; runId: number; timestamp: string; status: string; jobType: string; source: string | null; output: string | null; totalProducts: number; fbaCount: number; fbmCount: number; skipCount: number; }; type RunsResponse = { items: Run[]; page: number; pageSize: number; total: number; totalPages: number; }; type RunDetail = { processType: ProcessType; runId: number; timestamp: string; status: string; jobType: string; source: string | null; output: string | null; totalProducts: number; fbaCount: number; fbmCount: number; skipCount: number; summary: { totalProducts: number; fbaCount: number; fbmCount: number; skipCount: number; }; errorMessage?: string; availableAsins?: number; }; type ResultItem = { id?: number; run_id: number; asin: string; product_name: string | null; brand: string | null; category: string | null; current_price: number | null; avg_price_90d: number | null; sales_rank: number | null; seller_count: number | null; monthly_sold: number | null; verdict: "FBA" | "FBM" | "SKIP"; amazon_is_seller: number | null; amazon_buybox_share_pct_90d: number | null; confidence: number | null; sellability_status: string | null; reasoning: string | null; fetched_at: string; }; type ResultsResponse = { items: ResultItem[]; page: number; pageSize: number; total: number; totalPages: number; }; type VerdictFilter = "" | "FBA" | "FBM" | "SKIP"; type AmazonSellerFilter = "" | "yes" | "no"; type ProductListItem = { processType: ProcessType; runId: number; asin: string; product_name: string | null; brand: string | null; category: string | null; verdict: "FBA" | "FBM" | "SKIP"; confidence: number | null; sellability_status: string | null; monthly_sold: number | null; seller_count: number | null; amazon_is_seller: number | null; amazon_buybox_share_pct_90d: number | null; sales_rank: number | null; current_price: number | null; avg_price_90d: number | null; reasoning: string | null; fetched_at: string; }; type ProductListResponse = { items: ProductListItem[]; page: number; pageSize: number; total: number; totalPages: number; }; type StalkerResultItem = { runId: number; started_at: string; status: string; input_file: string; seller_id: string; seller_name: string | null; rating: number | null; rating_count: number | null; storefront_asin_total: number | null; persisted_inventory_sample_count: number | null; discovered_from_count: number; first_seen_at: string; last_seen_at: string; persisted_inventory_asin_count: number; inventory_sample_asins: string | null; }; type StalkerResultsResponse = { items: StalkerResultItem[]; summary: { runs: number; sellers: number; persistedInventoryAsins: number; }; page: number; pageSize: number; total: number; totalPages: number; }; type StalkerProductItem = { runId: number; started_at: string; seller_id: string; seller_name: string | null; rating: number | null; rating_count: number | null; asin: string; 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; }; type StalkerProductsResponse = { items: StalkerProductItem[]; summary: { runs: number; sellers: number; products: number; }; page: number; pageSize: number; total: number; totalPages: number; }; type SortState = { field: string; direction: SortDirection; }; function formatDate(input: string): string { const d = new Date(input); if (Number.isNaN(d.getTime())) return input; return d.toLocaleString(); } function formatNumber(value: number | null | undefined): string { if (value === null || value === undefined) return "-"; return new Intl.NumberFormat().format(value); } function formatCurrency(value: number | null | undefined): string { if (value === null || value === undefined) return "-"; return new Intl.NumberFormat(undefined, { style: "currency", currency: "USD", maximumFractionDigits: 2, }).format(value); } function formatAmazonSeller(value: number | null | undefined): string { if (value === null || value === undefined) return "-"; return value === 1 ? "Yes" : "No"; } function formatBoolean(value: number | null | undefined): string { if (value === null || value === undefined) return "-"; 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}`; } function nextSort(current: SortState, field: string): SortState { if (current.field !== field) { return { field, direction: "ASC" }; } return { field, direction: current.direction === "ASC" ? "DESC" : "ASC", }; } function statusBadgeClass(status: string): string { if (status === "ok" || status === "completed") return "badge badge-ok"; if (status === "failed") return "badge badge-failed"; return "badge badge-empty"; } function verdictBadgeClass(verdict: string): string { if (verdict === "FBA") return "badge badge-fba"; if (verdict === "FBM") return "badge badge-fbm"; return "badge badge-skip"; } function TinyBar({ fba, fbm, skip }: { fba: number; fbm: number; skip: number }) { const total = Math.max(1, fba + fbm + skip); const fbaPct = (fba / total) * 100; const fbmPct = (fbm / total) * 100; const skipPct = (skip / total) * 100; return (
); } function detectAnomaly(item: ResultItem): string { const confidence = item.confidence ?? 0; if ((item.sellability_status === "restricted" || item.sellability_status === "not_available") && item.verdict !== "SKIP") { return "restricted-vs-verdict"; } if (item.verdict === "FBA" && confidence < 60) { return "low-confidence-fba"; } if ((item.sales_rank ?? Number.MAX_SAFE_INTEGER) > 300000 && item.verdict === "FBA") { return "high-rank-fba"; } return ""; } function Dashboard({ onOpenRun, onOpenProducts, onOpenStalker, onOpenStalkerProducts, }: { onOpenRun: (run: Run) => void; onOpenProducts: (verdict: VerdictFilter) => void; onOpenStalker: () => void; onOpenStalkerProducts: () => void; }) { const [runs, setRuns] = useState(null); const [loading, setLoading] = useState(false); const [search, setSearch] = useState(""); const [processType, setProcessType] = useState(""); const [status, setStatus] = useState(""); const [startDate, setStartDate] = useState(""); const [endDate, setEndDate] = useState(""); const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(25); const [sort, setSort] = useState({ field: "timestamp", direction: "DESC" }); const [refreshTick, setRefreshTick] = useState(0); const [deletingKey, setDeletingKey] = useState(null); const summary = useMemo(() => { if (!runs) return { total: 0, fba: 0, fbm: 0, skip: 0 }; return runs.items.reduce( (acc, run) => { acc.total += run.totalProducts; acc.fba += run.fbaCount; acc.fbm += run.fbmCount; acc.skip += run.skipCount; return acc; }, { total: 0, fba: 0, fbm: 0, skip: 0 }, ); }, [runs]); const timeline = useMemo(() => { if (!runs) return [] as Run[]; return [...runs.items] .sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()) .slice(-12); }, [runs]); useEffect(() => { let cancelled = false; async function load() { setLoading(true); const params = new URLSearchParams({ page: String(page), pageSize: String(pageSize), sort: buildSortValue(sort), }); if (search) params.set("q", search); if (processType) params.set("processType", processType); if (status) params.set("status", status); if (startDate) params.set("startDate", `${startDate}T00:00:00.000Z`); if (endDate) params.set("endDate", `${endDate}T23:59:59.999Z`); const resp = await fetch(`/api/runs?${params.toString()}`); const data = (await resp.json()) as RunsResponse; if (!cancelled) { setRuns(data); setLoading(false); } } load(); return () => { cancelled = true; }; }, [search, processType, status, startDate, endDate, page, pageSize, sort, refreshTick]); async function deleteRun(run: Run) { const key = `${run.processType}-${run.runId}`; const confirmed = window.confirm(`Delete run ${run.runId} (${run.processType}) and all associated results?`); if (!confirmed) return; setDeletingKey(key); try { const response = await fetch(`/api/runs/${run.processType}/${run.runId}`, { method: "DELETE" }); if (!response.ok) { const errorPayload = await response.json().catch(() => null) as { error?: string } | null; const message = errorPayload?.error ?? "Failed to delete run"; window.alert(message); return; } setPage(1); setRefreshTick((tick) => tick + 1); } finally { setDeletingKey(null); } } return (

Runs Dashboard

onOpenProducts("")} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") onOpenProducts(""); }}>
Total products
{formatNumber(summary.total)}
onOpenProducts("FBA")} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") onOpenProducts("FBA"); }}>
FBA
{formatNumber(summary.fba)}
onOpenProducts("FBM")} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") onOpenProducts("FBM"); }}>
FBM
{formatNumber(summary.fbm)}
onOpenProducts("SKIP")} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") onOpenProducts("SKIP"); }}>
SKIP
{formatNumber(summary.skip)}
{ setPage(1); setSearch(e.target.value); }} placeholder="Search run/job/source" /> { setPage(1); setStartDate(e.target.value); }} /> { setPage(1); setEndDate(e.target.value); }} />
{loading ? ( ) : runs?.items.length ? ( runs.items.map((run) => ( )) ) : ( )}
FBA FBM SKIP Mix Source Open Delete
Loading...
{run.runId} {run.processType} {run.jobType} {formatDate(run.timestamp)} {run.status} {formatNumber(run.totalProducts)} {formatNumber(run.fbaCount)} {formatNumber(run.fbmCount)} {formatNumber(run.skipCount)} {run.source || "-"}
No runs found
Showing {runs?.items.length ?? 0} of {runs?.total ?? 0}
Page {runs?.page ?? page} / {runs?.totalPages ?? 1}

Recent trend (last 12 runs in current view)

{timeline.length === 0 ? (
No trend data
) : ( timeline.map((run) => (
#{run.runId}
)) )}
); } function RunDetails({ processType, runId, onBack, }: { processType: ProcessType; runId: number; onBack: () => void; }) { const [run, setRun] = useState(null); const [results, setResults] = useState(null); const [loading, setLoading] = useState(false); const [search, setSearch] = useState(""); const [verdict, setVerdict] = useState(""); const [sellabilityStatus, setSellabilityStatus] = useState(""); const [amazonSellerFilter, setAmazonSellerFilter] = useState(""); const [minConfidence, setMinConfidence] = useState(""); const [maxConfidence, setMaxConfidence] = useState(""); const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(25); const [sort, setSort] = useState({ field: "monthly_sold", direction: "DESC" }); const [refreshTick, setRefreshTick] = useState(0); const [reanalyzing, setReanalyzing] = useState>({}); const anomalies = useMemo(() => { if (!results) return [] as ResultItem[]; return results.items.filter((item) => detectAnomaly(item) !== ""); }, [results]); useEffect(() => { let cancelled = false; async function loadRun() { const res = await fetch(`/api/runs/${processType}/${runId}`); const payload = (await res.json()) as RunDetail; if (!cancelled) { setRun(payload); } } loadRun(); return () => { cancelled = true; }; }, [processType, runId, refreshTick]); useEffect(() => { let cancelled = false; async function loadResults() { setLoading(true); const params = new URLSearchParams({ page: String(page), pageSize: String(pageSize), sort: buildSortValue(sort), }); if (search) params.set("q", search); if (verdict) params.set("verdict", verdict); if (sellabilityStatus) params.set("sellabilityStatus", sellabilityStatus); if (amazonSellerFilter) params.set("amazonIsSeller", amazonSellerFilter); if (minConfidence) params.set("minConfidence", minConfidence); if (maxConfidence) params.set("maxConfidence", maxConfidence); const res = await fetch(`/api/runs/${processType}/${runId}/results?${params.toString()}`); const payload = (await res.json()) as ResultsResponse; if (!cancelled) { setResults(payload); setLoading(false); } } loadResults(); return () => { cancelled = true; }; }, [processType, runId, search, verdict, sellabilityStatus, amazonSellerFilter, minConfidence, maxConfidence, page, pageSize, sort, refreshTick]); useEffect(() => { const interval = window.setInterval(() => { setRefreshTick((tick) => tick + 1); }, 4000); return () => { window.clearInterval(interval); }; }, [processType, runId]); async function reanalyzeAsin(asin: string) { if (reanalyzing[asin]) return; setReanalyzing((prev) => ({ ...prev, [asin]: true })); try { const response = await fetch( `/api/runs/${processType}/${runId}/asins/${encodeURIComponent(asin)}/reanalyze`, { method: "POST" }, ); if (!response.ok) { const payload = (await response.json().catch(() => null)) as { error?: string } | null; window.alert(payload?.error ?? "Failed to re-analyze ASIN"); return; } setRefreshTick((tick) => tick + 1); } finally { setReanalyzing((prev) => { const next = { ...prev }; delete next[asin]; return next; }); } } return (

Run Detail

Process: {processType}
Run ID: {runId}
Status: {run ? {run.status} : "-"}
Timestamp: {run ? formatDate(run.timestamp) : "-"}
Job: {run?.jobType ?? "-"}
Source: {run?.source ?? "-"}
Total: {formatNumber(run?.summary.totalProducts)}
FBA/FBM/SKIP: {formatNumber(run?.summary.fbaCount)}/{formatNumber(run?.summary.fbmCount)}/{formatNumber(run?.summary.skipCount)}
{ setPage(1); setSearch(e.target.value); }} placeholder="Search ASIN/name/brand/category/reason" /> { setPage(1); setMinConfidence(e.target.value); }} placeholder="Min confidence" /> { setPage(1); setMaxConfidence(e.target.value); }} placeholder="Max confidence" />

Anomalies in current page

{anomalies.length === 0 ? (
No anomalies detected with current heuristic.
) : (
{anomalies.slice(0, 8).map((item) => (
{item.asin} {item.verdict} {detectAnomaly(item)}
))}
)}
{loading ? ( ) : results?.items.length ? ( results.items.map((item) => ( )) ) : ( )}
Action
Loading...
{item.asin} {item.verdict} {formatNumber(item.monthly_sold)} {formatNumber(item.seller_count)} {formatAmazonSeller(item.amazon_is_seller)} {formatNumber(item.amazon_buybox_share_pct_90d)} {formatNumber(item.sales_rank)} {formatCurrency(item.current_price)} {item.product_name || "-"} {item.brand || "-"} {item.category || "-"} {formatCurrency(item.avg_price_90d)} {formatNumber(item.confidence)} {item.reasoning || "-"}
No results found
Showing {results?.items.length ?? 0} of {results?.total ?? 0}
Page {results?.page ?? page} / {results?.totalPages ?? 1}
); } function ProductList({ verdict, onBack }: { verdict: VerdictFilter; onBack: () => void }) { const [items, setItems] = useState(null); const [loading, setLoading] = useState(false); const [search, setSearch] = useState(""); const [activeVerdict, setActiveVerdict] = useState(verdict); const [amazonSellerFilter, setAmazonSellerFilter] = useState(""); const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(25); const [sort, setSort] = useState({ field: "monthly_sold", direction: "DESC" }); const [reanalyzing, setReanalyzing] = useState>({}); useEffect(() => { setActiveVerdict(verdict); setPage(1); }, [verdict]); useEffect(() => { let cancelled = false; 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); if (amazonSellerFilter) params.set("amazonIsSeller", amazonSellerFilter); const res = await fetch(`/api/products?${params.toString()}`); const payload = (await res.json()) as ProductListResponse; if (!cancelled) { setItems(payload); setLoading(false); } } load(); return () => { cancelled = true; }; }, [search, activeVerdict, amazonSellerFilter, page, pageSize, sort]); async function reanalyzeAsin(item: ProductListItem) { const key = `${item.processType}:${item.runId}:${item.asin}`; if (reanalyzing[key]) return; setReanalyzing((prev) => ({ ...prev, [key]: true })); try { const response = await fetch( `/api/runs/${item.processType}/${item.runId}/asins/${encodeURIComponent(item.asin)}/reanalyze`, { method: "POST" }, ); if (!response.ok) { const payload = (await response.json().catch(() => null)) as { error?: string } | null; window.alert(payload?.error ?? "Failed to re-analyze ASIN"); return; } 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); if (amazonSellerFilter) params.set("amazonIsSeller", amazonSellerFilter); const res = await fetch(`/api/products?${params.toString()}`); const payload = (await res.json()) as ProductListResponse; setItems(payload); } finally { setReanalyzing((prev) => { const next = { ...prev }; delete next[key]; return next; }); } } return (

Products

{ setPage(1); setSearch(e.target.value); }} placeholder="Search ASIN/name/brand/category" />
{loading ? ( ) : items?.items.length ? ( items.items.map((item) => ( )) ) : ( )}
Action
Loading...
{item.asin} {item.verdict} {formatNumber(item.monthly_sold)} {formatNumber(item.seller_count)} {formatAmazonSeller(item.amazon_is_seller)} {formatNumber(item.amazon_buybox_share_pct_90d)} {formatNumber(item.sales_rank)} {formatCurrency(item.current_price)} {item.product_name || "-"} {item.brand || "-"} {item.category || "-"} {formatCurrency(item.avg_price_90d)} {formatNumber(item.confidence)} {item.reasoning || "-"}
No products found
Showing {items?.items.length ?? 0} of {items?.total ?? 0}
Page {items?.page ?? page} / {items?.totalPages ?? 1}
); } function StalkerExplorer({ onBack, onOpenProducts, }: { onBack: () => void; onOpenProducts: () => void; }) { const [results, setResults] = useState(null); const [loading, setLoading] = useState(false); const [purging, setPurging] = useState(false); const [search, setSearch] = useState(""); const [sellerId, setSellerId] = useState(""); const [runId, setRunId] = useState(""); const [minRatingCount, setMinRatingCount] = useState("1"); const [maxRatingCount, setMaxRatingCount] = useState("30"); const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(25); const [sort, setSort] = useState({ field: "persisted_inventory_asin_count", direction: "DESC" }); const [refreshTick, setRefreshTick] = useState(0); useEffect(() => { let cancelled = false; async function load() { setLoading(true); const params = new URLSearchParams({ page: String(page), pageSize: String(pageSize), sort: buildSortValue(sort), }); if (search) params.set("q", search); if (sellerId) params.set("sellerId", sellerId); if (runId) params.set("runId", runId); if (minRatingCount) params.set("minRatingCount", minRatingCount); if (maxRatingCount) params.set("maxRatingCount", maxRatingCount); const res = await fetch(`/api/stalker/results?${params.toString()}`); const payload = (await res.json()) as StalkerResultsResponse; if (!cancelled) { setResults(payload); setLoading(false); } } load(); return () => { cancelled = true; }; }, [search, sellerId, runId, minRatingCount, maxRatingCount, page, pageSize, sort, refreshTick]); async function purgeStalkerData() { const confirmed = window.confirm("Permanently delete all Stalker runs, sellers, and sellable products from the database?"); if (!confirmed) return; setPurging(true); try { const res = await fetch("/api/stalker/purge", { method: "DELETE" }); if (!res.ok) { const payload = await res.json().catch(() => null) as { error?: string } | null; window.alert(payload?.error ?? "Failed to purge Stalker data"); return; } setPage(1); setRefreshTick((tick) => tick + 1); } finally { setPurging(false); } } return (

Seller Storefronts

Runs
{formatNumber(results?.summary.runs)}
Matched sellers
{formatNumber(results?.summary.sellers)}
Sellable inventory ASINs
{formatNumber(results?.summary.persistedInventoryAsins)}
{ setPage(1); setSearch(e.target.value); }} placeholder="Search seller or sellable ASIN" /> { setPage(1); setSellerId(e.target.value.toUpperCase()); }} placeholder="Seller ID" /> { setPage(1); setRunId(e.target.value); }} placeholder="Run ID" /> { setPage(1); setMinRatingCount(e.target.value); }} placeholder="Min rating count" /> { setPage(1); setMaxRatingCount(e.target.value); }} placeholder="Max rating count" />
{loading ? ( ) : results?.items.length ? ( results.items.map((item) => { const inventorySample = (item.inventory_sample_asins ?? "") .split(",") .filter(Boolean) .slice(0, 20); return ( ); }) ) : ( )}
Sellable inventory ASIN sample
Loading...
{item.runId} {item.seller_id} {item.seller_name || "-"} {formatNumber(item.rating)} {formatNumber(item.rating_count)} {formatNumber(item.discovered_from_count)} {formatNumber(item.storefront_asin_total)} {formatNumber(item.persisted_inventory_asin_count)} {formatDate(item.last_seen_at)} {inventorySample.length === 0 ? "-" : inventorySample.map((asin) => ( {asin} ))}
No seller storefronts found
Showing {results?.items.length ?? 0} of {results?.total ?? 0}
Page {results?.page ?? page} / {results?.totalPages ?? 1}
); } function StalkerProductsExplorer({ onBack, onOpenSellers, }: { onBack: () => void; onOpenSellers: () => void; }) { const [results, setResults] = useState(null); const [loading, setLoading] = useState(false); const [search, setSearch] = useState(""); const [sellerId, setSellerId] = useState(""); const [runId, setRunId] = useState(""); const [verdict, setVerdict] = useState(""); const [amazonIsSeller, setAmazonIsSeller] = useState(""); const [minPrice, setMinPrice] = useState(""); const [maxPrice, setMaxPrice] = useState(""); const [minMonthlySold, setMinMonthlySold] = useState(""); const [maxMonthlySold, setMaxMonthlySold] = useState(""); const [minSalesRank, setMinSalesRank] = useState(""); const [maxSalesRank, setMaxSalesRank] = useState(""); const [minSellerCount, setMinSellerCount] = useState(""); const [maxSellerCount, setMaxSellerCount] = useState(""); const [minRatingCount, setMinRatingCount] = useState(""); const [maxRatingCount, setMaxRatingCount] = useState(""); const [minConfidence, setMinConfidence] = useState(""); const [maxConfidence, setMaxConfidence] = useState(""); const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(50); const [sort, setSort] = useState({ field: "monthly_sold", direction: "DESC" }); function buildStalkerProductParams(includePaging: boolean): URLSearchParams { const params = new URLSearchParams({ sort: buildSortValue(sort), }); if (includePaging) { params.set("page", String(page)); params.set("pageSize", String(pageSize)); } if (search) params.set("q", search); if (sellerId) params.set("sellerId", sellerId); if (runId) params.set("runId", runId); if (verdict) params.set("verdict", verdict); if (amazonIsSeller) params.set("amazonIsSeller", amazonIsSeller); if (minPrice) params.set("minPrice", minPrice); if (maxPrice) params.set("maxPrice", maxPrice); if (minMonthlySold) params.set("minMonthlySold", minMonthlySold); if (maxMonthlySold) params.set("maxMonthlySold", maxMonthlySold); if (minSalesRank) params.set("minSalesRank", minSalesRank); if (maxSalesRank) params.set("maxSalesRank", maxSalesRank); if (minSellerCount) params.set("minSellerCount", minSellerCount); if (maxSellerCount) params.set("maxSellerCount", maxSellerCount); if (minRatingCount) params.set("minRatingCount", minRatingCount); if (maxRatingCount) params.set("maxRatingCount", maxRatingCount); if (minConfidence) params.set("minConfidence", minConfidence); if (maxConfidence) params.set("maxConfidence", maxConfidence); return params; } useEffect(() => { let cancelled = false; async function load() { setLoading(true); const params = buildStalkerProductParams(true); const res = await fetch(`/api/stalker/products?${params.toString()}`); const payload = (await res.json()) as StalkerProductsResponse; if (!cancelled) { setResults(payload); setLoading(false); } } load(); return () => { cancelled = true; }; }, [ search, sellerId, runId, verdict, amazonIsSeller, minPrice, maxPrice, minMonthlySold, maxMonthlySold, minSalesRank, maxSalesRank, minSellerCount, maxSellerCount, minRatingCount, maxRatingCount, minConfidence, maxConfidence, page, pageSize, sort, ]); function resetFilters() { setSearch(""); setSellerId(""); setRunId(""); setVerdict(""); setAmazonIsSeller(""); setMinPrice(""); setMaxPrice(""); setMinMonthlySold(""); setMaxMonthlySold(""); setMinSalesRank(""); setMaxSalesRank(""); setMinSellerCount(""); setMaxSellerCount(""); setMinRatingCount(""); setMaxRatingCount(""); setMinConfidence(""); setMaxConfidence(""); setPage(1); } const exportHref = `/api/stalker/products/export.xlsx?${buildStalkerProductParams(false).toString()}`; return (

Sellable Stalker Products

Runs
{formatNumber(results?.summary.runs)}
Sellers
{formatNumber(results?.summary.sellers)}
Sellable products
{formatNumber(results?.summary.products)}
{ setPage(1); setSearch(e.target.value); }} placeholder="Search ASIN, product, brand, category, or seller" /> { setPage(1); setSellerId(e.target.value.toUpperCase()); }} placeholder="Seller ID" /> { setPage(1); setRunId(e.target.value); }} placeholder="Run ID" /> { setPage(1); setMinPrice(e.target.value); }} placeholder="Min price" /> { setPage(1); setMaxPrice(e.target.value); }} placeholder="Max price" /> { setPage(1); setMinMonthlySold(e.target.value); }} placeholder="Min monthly sold" /> { setPage(1); setMaxMonthlySold(e.target.value); }} placeholder="Max monthly sold" /> { setPage(1); setMinSalesRank(e.target.value); }} placeholder="Min rank" /> { setPage(1); setMaxSalesRank(e.target.value); }} placeholder="Max rank" /> { setPage(1); setMinSellerCount(e.target.value); }} placeholder="Min sellers" /> { setPage(1); setMaxSellerCount(e.target.value); }} placeholder="Max sellers" /> { setPage(1); setMinRatingCount(e.target.value); }} placeholder="Min seller rating count" /> { setPage(1); setMaxRatingCount(e.target.value); }} placeholder="Max seller rating count" /> { setPage(1); setMinConfidence(e.target.value); }} placeholder="Min confidence" /> { setPage(1); setMaxConfidence(e.target.value); }} placeholder="Max confidence" /> Export XLSX
{loading ? ( ) : results?.items.length ? ( results.items.map((item) => { const categories = parseStringArrayJson(item.category_tree); return ( ); }) ) : ( )}
Category Status
Loading...
{item.asin} {item.product_title || "-"} {item.brand || "-"} {categories.at(-1) || "-"} {formatNumber(item.monthly_sold)} {formatNumber(item.seller_count)} {formatAmazonSeller(item.amazon_is_seller)} {formatNumber(item.sales_rank)} {formatCurrency(item.current_price)} {formatCurrency(item.avg_price_90d)} {item.verdict ? {item.verdict} : "-"} {formatNumber(item.confidence)} {item.seller_id} {item.seller_name || "-"} {formatNumber(item.rating_count)} {item.sellability_status} {item.runId} {formatDate(item.last_seen_at)}
No sellable Stalker products found
Showing {results?.items.length ?? 0} of {results?.total ?? 0}
Page {results?.page ?? page} / {results?.totalPages ?? 1}
); } type AppRoute = | { kind: "dashboard" } | { kind: "run"; processType: ProcessType; runId: number } | { kind: "products"; verdict: VerdictFilter } | { kind: "stalker" } | { kind: "stalker-products" }; function parseRoute(pathname: string, search: string): AppRoute { const runMatch = pathname.match(/^\/runs\/(lead_analysis|category_analysis)\/(\d+)$/); if (runMatch) { return { kind: "run", processType: runMatch[1] as ProcessType, runId: Number(runMatch[2]) }; } if (pathname === "/products") { const params = new URLSearchParams(search); const verdictParam = params.get("verdict"); const verdict = verdictParam === "FBA" || verdictParam === "FBM" || verdictParam === "SKIP" ? verdictParam : ""; return { kind: "products", verdict }; } if (pathname === "/stalker") { return { kind: "stalker" }; } if (pathname === "/stalker/products") { return { kind: "stalker-products" }; } return { kind: "dashboard" }; } function App() { const [route, setRoute] = useState(() => parseRoute(window.location.pathname, window.location.search)); useEffect(() => { const onPopState = () => setRoute(parseRoute(window.location.pathname, window.location.search)); window.addEventListener("popstate", onPopState); return () => window.removeEventListener("popstate", onPopState); }, []); function openRun(run: Run) { const path = `/runs/${run.processType}/${run.runId}`; history.pushState({}, "", path); setRoute({ kind: "run", processType: run.processType, runId: run.runId }); } function openProducts(verdict: VerdictFilter) { const path = verdict ? `/products?verdict=${encodeURIComponent(verdict)}` : "/products"; history.pushState({}, "", path); setRoute({ kind: "products", verdict }); } function openStalker() { history.pushState({}, "", "/stalker"); setRoute({ kind: "stalker" }); } function openStalkerProducts() { history.pushState({}, "", "/stalker/products"); setRoute({ kind: "stalker-products" }); } function backToDashboard() { history.pushState({}, "", "/"); setRoute({ kind: "dashboard" }); } if (route.kind === "run") { return ; } if (route.kind === "products") { return ; } if (route.kind === "stalker") { return ; } if (route.kind === "stalker-products") { return ; } return ( ); } const root = document.getElementById("root"); if (!root) { throw new Error("Root element not found"); } createRoot(root).render();