feat: add advanced filtering options for Stalker products including price, sales rank, and seller metrics
This commit is contained in:
@@ -829,6 +829,20 @@ function parseStalkerProductFilters(filters: URLSearchParams) {
|
|||||||
const q = filters.get("q")?.trim() || "";
|
const q = filters.get("q")?.trim() || "";
|
||||||
const sellerId = filters.get("sellerId")?.trim().toUpperCase() || "";
|
const sellerId = filters.get("sellerId")?.trim().toUpperCase() || "";
|
||||||
const runIdRaw = filters.get("runId")?.trim() || "";
|
const runIdRaw = filters.get("runId")?.trim() || "";
|
||||||
|
const verdict = filters.get("verdict")?.trim().toUpperCase() || "";
|
||||||
|
const amazonIsSeller = filters.get("amazonIsSeller")?.trim() || "";
|
||||||
|
const minPriceRaw = filters.get("minPrice")?.trim() || "";
|
||||||
|
const maxPriceRaw = filters.get("maxPrice")?.trim() || "";
|
||||||
|
const minMonthlySoldRaw = filters.get("minMonthlySold")?.trim() || "";
|
||||||
|
const maxMonthlySoldRaw = filters.get("maxMonthlySold")?.trim() || "";
|
||||||
|
const minSalesRankRaw = filters.get("minSalesRank")?.trim() || "";
|
||||||
|
const maxSalesRankRaw = filters.get("maxSalesRank")?.trim() || "";
|
||||||
|
const minSellerCountRaw = filters.get("minSellerCount")?.trim() || "";
|
||||||
|
const maxSellerCountRaw = filters.get("maxSellerCount")?.trim() || "";
|
||||||
|
const minRatingCountRaw = filters.get("minRatingCount")?.trim() || "";
|
||||||
|
const maxRatingCountRaw = filters.get("maxRatingCount")?.trim() || "";
|
||||||
|
const minConfidenceRaw = filters.get("minConfidence")?.trim() || "";
|
||||||
|
const maxConfidenceRaw = filters.get("maxConfidence")?.trim() || "";
|
||||||
|
|
||||||
const conditions = [
|
const conditions = [
|
||||||
"inv.can_sell = 1",
|
"inv.can_sell = 1",
|
||||||
@@ -849,6 +863,45 @@ function parseStalkerProductFilters(filters: URLSearchParams) {
|
|||||||
params.push(sellerId);
|
params.push(sellerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (verdict === "FBA" || verdict === "FBM" || verdict === "SKIP") {
|
||||||
|
conditions.push("analysis.verdict = ?");
|
||||||
|
params.push(verdict);
|
||||||
|
} else if (verdict === "UNANALYZED") {
|
||||||
|
conditions.push("analysis.verdict IS NULL");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (amazonIsSeller === "yes") {
|
||||||
|
conditions.push("inv.amazon_is_seller = 1");
|
||||||
|
} else if (amazonIsSeller === "no") {
|
||||||
|
conditions.push("inv.amazon_is_seller = 0");
|
||||||
|
} else if (amazonIsSeller === "unknown") {
|
||||||
|
conditions.push("inv.amazon_is_seller IS NULL");
|
||||||
|
}
|
||||||
|
|
||||||
|
const numericFilters: Array<[string, string, string]> = [
|
||||||
|
[minPriceRaw, "inv.current_price >= ?", "minPrice"],
|
||||||
|
[maxPriceRaw, "inv.current_price <= ?", "maxPrice"],
|
||||||
|
[minMonthlySoldRaw, "inv.monthly_sold >= ?", "minMonthlySold"],
|
||||||
|
[maxMonthlySoldRaw, "inv.monthly_sold <= ?", "maxMonthlySold"],
|
||||||
|
[minSalesRankRaw, "inv.sales_rank >= ?", "minSalesRank"],
|
||||||
|
[maxSalesRankRaw, "inv.sales_rank <= ?", "maxSalesRank"],
|
||||||
|
[minSellerCountRaw, "inv.seller_count >= ?", "minSellerCount"],
|
||||||
|
[maxSellerCountRaw, "inv.seller_count <= ?", "maxSellerCount"],
|
||||||
|
[minRatingCountRaw, "s.rating_count >= ?", "minRatingCount"],
|
||||||
|
[maxRatingCountRaw, "s.rating_count <= ?", "maxRatingCount"],
|
||||||
|
[minConfidenceRaw, "analysis.confidence >= ?", "minConfidence"],
|
||||||
|
[maxConfidenceRaw, "analysis.confidence <= ?", "maxConfidence"],
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const [raw, condition] of numericFilters) {
|
||||||
|
if (!raw) continue;
|
||||||
|
const value = Number(raw);
|
||||||
|
if (Number.isFinite(value)) {
|
||||||
|
conditions.push(condition);
|
||||||
|
params.push(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (q) {
|
if (q) {
|
||||||
const wildcard = `%${q}%`;
|
const wildcard = `%${q}%`;
|
||||||
conditions.push(
|
conditions.push(
|
||||||
|
|||||||
@@ -1135,6 +1135,20 @@ function StalkerProductsExplorer({
|
|||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const [sellerId, setSellerId] = useState("");
|
const [sellerId, setSellerId] = useState("");
|
||||||
const [runId, setRunId] = 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 [page, setPage] = useState(1);
|
||||||
const [pageSize, setPageSize] = useState(50);
|
const [pageSize, setPageSize] = useState(50);
|
||||||
const [sort, setSort] = useState<SortState>({ field: "monthly_sold", direction: "DESC" });
|
const [sort, setSort] = useState<SortState>({ field: "monthly_sold", direction: "DESC" });
|
||||||
@@ -1151,6 +1165,20 @@ function StalkerProductsExplorer({
|
|||||||
if (search) params.set("q", search);
|
if (search) params.set("q", search);
|
||||||
if (sellerId) params.set("sellerId", sellerId);
|
if (sellerId) params.set("sellerId", sellerId);
|
||||||
if (runId) params.set("runId", runId);
|
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);
|
||||||
|
|
||||||
const res = await fetch(`/api/stalker/products?${params.toString()}`);
|
const res = await fetch(`/api/stalker/products?${params.toString()}`);
|
||||||
const payload = (await res.json()) as StalkerProductsResponse;
|
const payload = (await res.json()) as StalkerProductsResponse;
|
||||||
@@ -1164,7 +1192,49 @@ function StalkerProductsExplorer({
|
|||||||
return () => {
|
return () => {
|
||||||
cancelled = true;
|
cancelled = true;
|
||||||
};
|
};
|
||||||
}, [search, sellerId, runId, page, pageSize, sort]);
|
}, [
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="page">
|
<div className="page">
|
||||||
@@ -1197,11 +1267,37 @@ function StalkerProductsExplorer({
|
|||||||
<input value={search} onChange={(e) => { setPage(1); setSearch(e.target.value); }} placeholder="Search ASIN, product, brand, category, 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={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" />
|
<input value={runId} onChange={(e) => { setPage(1); setRunId(e.target.value); }} placeholder="Run ID" />
|
||||||
|
<select value={verdict} onChange={(e) => { setPage(1); setVerdict(e.target.value); }}>
|
||||||
|
<option value="">All verdicts</option>
|
||||||
|
<option value="FBA">FBA</option>
|
||||||
|
<option value="FBM">FBM</option>
|
||||||
|
<option value="SKIP">SKIP</option>
|
||||||
|
<option value="UNANALYZED">Unanalyzed</option>
|
||||||
|
</select>
|
||||||
|
<select value={amazonIsSeller} onChange={(e) => { setPage(1); setAmazonIsSeller(e.target.value); }}>
|
||||||
|
<option value="">Amazon seller: all</option>
|
||||||
|
<option value="yes">Amazon seller: yes</option>
|
||||||
|
<option value="no">Amazon seller: no</option>
|
||||||
|
<option value="unknown">Amazon seller: unknown</option>
|
||||||
|
</select>
|
||||||
|
<input value={minPrice} onChange={(e) => { setPage(1); setMinPrice(e.target.value); }} placeholder="Min price" />
|
||||||
|
<input value={maxPrice} onChange={(e) => { setPage(1); setMaxPrice(e.target.value); }} placeholder="Max price" />
|
||||||
|
<input value={minMonthlySold} onChange={(e) => { setPage(1); setMinMonthlySold(e.target.value); }} placeholder="Min monthly sold" />
|
||||||
|
<input value={maxMonthlySold} onChange={(e) => { setPage(1); setMaxMonthlySold(e.target.value); }} placeholder="Max monthly sold" />
|
||||||
|
<input value={minSalesRank} onChange={(e) => { setPage(1); setMinSalesRank(e.target.value); }} placeholder="Min rank" />
|
||||||
|
<input value={maxSalesRank} onChange={(e) => { setPage(1); setMaxSalesRank(e.target.value); }} placeholder="Max rank" />
|
||||||
|
<input value={minSellerCount} onChange={(e) => { setPage(1); setMinSellerCount(e.target.value); }} placeholder="Min sellers" />
|
||||||
|
<input value={maxSellerCount} onChange={(e) => { setPage(1); setMaxSellerCount(e.target.value); }} placeholder="Max sellers" />
|
||||||
|
<input value={minRatingCount} onChange={(e) => { setPage(1); setMinRatingCount(e.target.value); }} placeholder="Min seller rating count" />
|
||||||
|
<input value={maxRatingCount} onChange={(e) => { setPage(1); setMaxRatingCount(e.target.value); }} placeholder="Max seller rating count" />
|
||||||
|
<input value={minConfidence} onChange={(e) => { setPage(1); setMinConfidence(e.target.value); }} placeholder="Min confidence" />
|
||||||
|
<input value={maxConfidence} onChange={(e) => { setPage(1); setMaxConfidence(e.target.value); }} placeholder="Max confidence" />
|
||||||
<select value={String(pageSize)} onChange={(e) => { setPage(1); setPageSize(Number(e.target.value)); }}>
|
<select value={String(pageSize)} onChange={(e) => { setPage(1); setPageSize(Number(e.target.value)); }}>
|
||||||
<option value="25">25 / page</option>
|
<option value="25">25 / page</option>
|
||||||
<option value="50">50 / page</option>
|
<option value="50">50 / page</option>
|
||||||
<option value="100">100 / page</option>
|
<option value="100">100 / page</option>
|
||||||
</select>
|
</select>
|
||||||
|
<button onClick={resetFilters}>Reset filters</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user