Implement Excel-style stock filtering with Smart Filters and Range support

This commit is contained in:
Christian Vidal Wolf
2026-01-27 19:03:42 +01:00
parent 6541f6655a
commit c4213acb3d
4 changed files with 72 additions and 8 deletions
+50 -4
View File
@@ -701,11 +701,51 @@ export const mergeSalesAndAdsData = (
// --- EXISTING HELPERS ---
// Helper to check stock filter
const checkStockFilter = (sku: string, filters: string[], stockMap?: Map<string, number>): boolean => {
if (!filters || filters.length === 0) return true;
if (!stockMap) return true;
// Normalize SKU (remove DE/EN) to match stock map
const baseSku = sku?.replace(/(DE|EN)$/i, '');
const stockValue = stockMap.get(baseSku) || 0;
return filters.some(f => {
// Smart Filters
if (f === 'Out of Stock (0)') return stockValue === 0;
if (f === 'In Stock (>0)') return stockValue > 0;
if (f === 'Low Stock (<10)') return stockValue < 10;
// Numeric Range (e.g. ">10", "1-50")
const input = f.trim().toLowerCase();
if (input.includes('-')) {
const [start, end] = input.split('-').map(s => parseFloat(s.trim()));
if (!isNaN(start) && !isNaN(end)) return stockValue >= start && stockValue <= end;
} else if (input.startsWith('<=')) {
const val = parseFloat(input.substring(2).trim());
if (!isNaN(val)) return stockValue <= val;
} else if (input.startsWith('>=')) {
const val = parseFloat(input.substring(2).trim());
if (!isNaN(val)) return stockValue >= val;
} else if (input.startsWith('<')) {
const val = parseFloat(input.substring(1).trim());
if (!isNaN(val)) return stockValue < val;
} else if (input.startsWith('>')) {
const val = parseFloat(input.substring(1).trim());
if (!isNaN(val)) return stockValue > val;
}
// Exact Match
return stockValue.toString() === f;
});
};
// Filter Ads Data by Country, Year, Week, ASIN, SKU, and Product Line
export const filterAdsData = (
adsData: AdsRecord[],
filters: FilterState,
asinMetadata?: Map<string, { sku: string; line: string }>
asinMetadata?: Map<string, { sku: string; line: string }>,
stockMap?: Map<string, number>
): AdsRecord[] => {
return adsData.filter(ad => {
const asin = ad.asin.trim().toUpperCase();
@@ -740,11 +780,14 @@ export const filterAdsData = (
lineMatch = meta ? filters.line.includes(meta.line) : false;
}
return countryMatch && yearMatch && weekMatch && asinMatch && skuMatch && lineMatch;
// Stock match
const stockMatch = checkStockFilter(meta?.sku || '', filters.stock, stockMap);
return countryMatch && yearMatch && weekMatch && asinMatch && skuMatch && lineMatch && stockMatch;
});
};
export const filterData = (data: SalesRecord[], filters: FilterState): SalesRecord[] => {
export const filterData = (data: SalesRecord[], filters: FilterState, stockMap?: Map<string, number>): SalesRecord[] => {
return data.filter(item => {
// 1. Month Logic: Handle "Apr-23" matching "Apr" filter
const recordMonth = item.month; // e.g. "Apr-23"
@@ -769,7 +812,10 @@ export const filterData = (data: SalesRecord[], filters: FilterState): SalesReco
const weekStr = item.week ? `W${item.week}` : '';
const weekMatch = filters.week.length === 0 || (weekStr !== '' && filters.week.includes(weekStr));
return customerMatch && yearMatch && monthMatch && lineMatch && asinMatch && skuMatch && titleMatch && weekMatch;
// Stock match
const stockMatch = checkStockFilter(item.sku, filters.stock, stockMap);
return customerMatch && yearMatch && monthMatch && lineMatch && asinMatch && skuMatch && titleMatch && weekMatch && stockMatch;
});
};