mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 16:35:23 +02:00
Implement Vendor Stock Filtering across Weekly Sales, Ads, and Forecast views
This commit is contained in:
@@ -54,7 +54,6 @@ const App: React.FC = () => {
|
|||||||
// Modal State
|
// Modal State
|
||||||
const [isDataModalOpen, setIsDataModalOpen] = useState(false);
|
const [isDataModalOpen, setIsDataModalOpen] = useState(false);
|
||||||
|
|
||||||
// Filters State
|
|
||||||
const [filters, setFilters] = useState<FilterState>({
|
const [filters, setFilters] = useState<FilterState>({
|
||||||
customer: [],
|
customer: [],
|
||||||
year: [],
|
year: [],
|
||||||
@@ -63,7 +62,9 @@ const App: React.FC = () => {
|
|||||||
asin: [],
|
asin: [],
|
||||||
sku: [],
|
sku: [],
|
||||||
title: [],
|
title: [],
|
||||||
|
week: [],
|
||||||
stock: [],
|
stock: [],
|
||||||
|
vendorStock: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle Data Fetch (Simplified)
|
// Handle Data Fetch (Simplified)
|
||||||
@@ -229,6 +230,7 @@ const App: React.FC = () => {
|
|||||||
title: [],
|
title: [],
|
||||||
week: [],
|
week: [],
|
||||||
stock: [],
|
stock: [],
|
||||||
|
vendorStock: [],
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -405,8 +407,15 @@ const App: React.FC = () => {
|
|||||||
return metaMap;
|
return metaMap;
|
||||||
}, [rawData]);
|
}, [rawData]);
|
||||||
|
|
||||||
const filteredData = useMemo(() => filterData(rawData, filters, stockMap), [rawData, filters, stockMap]);
|
const top50Mode = useMemo(() => {
|
||||||
const filteredAdsData = useMemo(() => filterAdsData(adsData, filters, globalAsinMetadata, stockMap), [adsData, filters, globalAsinMetadata, stockMap]);
|
const hasUK = filters.customer.includes('Amazon UK');
|
||||||
|
const hasEU = filters.customer.some(c => ['Amazon DE', 'Amazon IT', 'Amazon FR', 'Amazon ES'].includes(c));
|
||||||
|
if (hasUK) return 'uk';
|
||||||
|
return 'eu';
|
||||||
|
}, [filters.customer]);
|
||||||
|
|
||||||
|
const filteredData = useMemo(() => filterData(rawData, filters, stockMap, vendorStockMap, top50Mode), [rawData, filters, stockMap, vendorStockMap, top50Mode]);
|
||||||
|
const filteredAdsData = useMemo(() => filterAdsData(adsData, filters, globalAsinMetadata, stockMap, vendorStockMap, top50Mode), [adsData, filters, globalAsinMetadata, stockMap, vendorStockMap, top50Mode]);
|
||||||
const aggregatedData = useMemo(() => aggregateData(filteredData), [filteredData]);
|
const aggregatedData = useMemo(() => aggregateData(filteredData), [filteredData]);
|
||||||
|
|
||||||
// Calculate country-aware Top 50 Best Sellers for 2025
|
// Calculate country-aware Top 50 Best Sellers for 2025
|
||||||
@@ -641,6 +650,8 @@ const App: React.FC = () => {
|
|||||||
vendorStockMap={vendorStockMap}
|
vendorStockMap={vendorStockMap}
|
||||||
stockFilter={filters.stock}
|
stockFilter={filters.stock}
|
||||||
onStockFilterChange={(s) => setFilters(prev => ({ ...prev, stock: s }))}
|
onStockFilterChange={(s) => setFilters(prev => ({ ...prev, stock: s }))}
|
||||||
|
vendorStockFilter={filters.vendorStock}
|
||||||
|
onVendorStockFilterChange={(s) => setFilters(prev => ({ ...prev, vendorStock: s }))}
|
||||||
customerFilters={filters.customer}
|
customerFilters={filters.customer}
|
||||||
top50Mode={filters.customer.includes('Amazon UK') ? 'uk' : 'eu'}
|
top50Mode={filters.customer.includes('Amazon UK') ? 'uk' : 'eu'}
|
||||||
top50Ranking={top50Ranking2025}
|
top50Ranking={top50Ranking2025}
|
||||||
@@ -662,6 +673,8 @@ const App: React.FC = () => {
|
|||||||
vendorStockMap={vendorStockMap}
|
vendorStockMap={vendorStockMap}
|
||||||
stockFilter={filters.stock}
|
stockFilter={filters.stock}
|
||||||
onStockFilterChange={(s) => setFilters(prev => ({ ...prev, stock: s }))}
|
onStockFilterChange={(s) => setFilters(prev => ({ ...prev, stock: s }))}
|
||||||
|
vendorStockFilter={filters.vendorStock}
|
||||||
|
onVendorStockFilterChange={(s) => setFilters(prev => ({ ...prev, vendorStock: s }))}
|
||||||
top50Mode={filters.customer.includes('Amazon UK') ? 'uk' : 'eu'}
|
top50Mode={filters.customer.includes('Amazon UK') ? 'uk' : 'eu'}
|
||||||
/>
|
/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
@@ -676,6 +689,8 @@ const App: React.FC = () => {
|
|||||||
vendorStockMap={vendorStockMap}
|
vendorStockMap={vendorStockMap}
|
||||||
stockFilter={filters.stock}
|
stockFilter={filters.stock}
|
||||||
onStockFilterChange={(s) => setFilters(prev => ({ ...prev, stock: s }))}
|
onStockFilterChange={(s) => setFilters(prev => ({ ...prev, stock: s }))}
|
||||||
|
vendorStockFilter={filters.vendorStock}
|
||||||
|
onVendorStockFilterChange={(s) => setFilters(prev => ({ ...prev, vendorStock: s }))}
|
||||||
top50Mode={filters.customer.includes('Amazon UK') ? 'uk' : 'eu'}
|
top50Mode={filters.customer.includes('Amazon UK') ? 'uk' : 'eu'}
|
||||||
/>
|
/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ interface AdsPerformanceProps {
|
|||||||
onStockFilterChange: (newFilters: string[]) => void;
|
onStockFilterChange: (newFilters: string[]) => void;
|
||||||
top50Mode: 'eu' | 'uk';
|
top50Mode: 'eu' | 'uk';
|
||||||
vendorStockMap?: Map<string, { eu: number; uk: number }>;
|
vendorStockMap?: Map<string, { eu: number; uk: number }>;
|
||||||
|
vendorStockFilter: string[];
|
||||||
|
onVendorStockFilterChange: (newFilters: string[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
type SortKey = keyof CombinedKPIs | 'acos' | 'roas' | 'tacos' | 'ctr' | 'cpc' | 'cvrUnits';
|
type SortKey = keyof CombinedKPIs | 'acos' | 'roas' | 'tacos' | 'ctr' | 'cpc' | 'cvrUnits';
|
||||||
@@ -64,7 +66,9 @@ const AdsRow: React.FC<{
|
|||||||
))}
|
))}
|
||||||
<span className="text-white font-bold text-sm tracking-tight">{item.asin}</span>
|
<span className="text-white font-bold text-sm tracking-tight">{item.asin}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-[10px] uppercase font-black text-indigo-400 tracking-tighter">SKU: {item.sku}</span>
|
<span className="text-[10px] uppercase font-black text-indigo-400 tracking-tighter">SKU: {item.sku}</span>
|
||||||
|
</div>
|
||||||
<div className="flex items-start gap-2">
|
<div className="flex items-start gap-2">
|
||||||
<span className="text-[10px] text-slate-400 truncate max-w-[200px] leading-tight" title={item.title}>{item.title}</span>
|
<span className="text-[10px] text-slate-400 truncate max-w-[200px] leading-tight" title={item.title}>{item.title}</span>
|
||||||
{stockMap && (
|
{stockMap && (
|
||||||
@@ -92,7 +96,18 @@ const AdsRow: React.FC<{
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const AdsPerformance: React.FC<AdsPerformanceProps> = ({ data, filters, top50Ranking, stockMap, vendorStockMap, stockFilter, onStockFilterChange, top50Mode }) => {
|
const AdsPerformance: React.FC<AdsPerformanceProps> = ({
|
||||||
|
data,
|
||||||
|
filters,
|
||||||
|
top50Ranking,
|
||||||
|
stockMap,
|
||||||
|
vendorStockMap,
|
||||||
|
stockFilter,
|
||||||
|
onStockFilterChange,
|
||||||
|
vendorStockFilter,
|
||||||
|
onVendorStockFilterChange,
|
||||||
|
top50Mode
|
||||||
|
}) => {
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
const [showOnlyTop50, setShowOnlyTop50] = useState(false);
|
const [showOnlyTop50, setShowOnlyTop50] = useState(false);
|
||||||
const [sortConfig, setSortConfig] = useState<{ key: SortKey; direction: 'asc' | 'desc' }>({
|
const [sortConfig, setSortConfig] = useState<{ key: SortKey; direction: 'asc' | 'desc' }>({
|
||||||
@@ -376,7 +391,24 @@ const AdsPerformance: React.FC<AdsPerformanceProps> = ({ data, filters, top50Ran
|
|||||||
<th className="p-4 font-black text-slate-400 uppercase tracking-widest min-w-[250px]">
|
<th className="p-4 font-black text-slate-400 uppercase tracking-widest min-w-[250px]">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span>PRODUCT</span>
|
<span>PRODUCT</span>
|
||||||
<InColumnStockFilter currentFilters={stockFilter} onFilterChange={onStockFilterChange} />
|
<div className="flex items-center gap-1 scale-75 origin-left">
|
||||||
|
<InColumnStockFilter
|
||||||
|
currentFilters={stockFilter}
|
||||||
|
onFilterChange={onStockFilterChange}
|
||||||
|
title="Warehouse Stock"
|
||||||
|
/>
|
||||||
|
<InColumnStockFilter
|
||||||
|
currentFilters={vendorStockFilter}
|
||||||
|
onFilterChange={onVendorStockFilterChange}
|
||||||
|
title="Vendor Stock"
|
||||||
|
options={[
|
||||||
|
'Out of Stock (0)',
|
||||||
|
'In Stock (>0)',
|
||||||
|
'In Stock (>20)',
|
||||||
|
'Low Stock (<10)',
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<SortableHeader label="TOTAL SALES" sortKey="salesTotal" activeSort={sortConfig} onSort={handleSort} />
|
<SortableHeader label="TOTAL SALES" sortKey="salesTotal" activeSort={sortConfig} onSort={handleSort} />
|
||||||
|
|||||||
+34
-14
@@ -12,18 +12,10 @@ import {
|
|||||||
LineChart, Line, Legend, ComposedChart, Area
|
LineChart, Line, Legend, ComposedChart, Area
|
||||||
} from 'recharts';
|
} from 'recharts';
|
||||||
|
|
||||||
interface ForecastViewProps {
|
top50Mode: 'eu' | 'uk';
|
||||||
data: ProductForecastData[];
|
vendorStockMap ?: Map<string, { eu: number; uk: number }>;
|
||||||
filters: FilterState; // Restored filters prop
|
vendorStockFilter: string[];
|
||||||
top50Ranking?: {
|
onVendorStockFilterChange: (newFilters: string[]) => void;
|
||||||
eu: Map<string, number>;
|
|
||||||
uk: Map<string, number>;
|
|
||||||
};
|
|
||||||
stockMap?: Map<string, number>;
|
|
||||||
vendorStockMap?: Map<string, { eu: number; uk: number }>;
|
|
||||||
stockFilter: string[];
|
|
||||||
onStockFilterChange: (newFilters: string[]) => void;
|
|
||||||
top50Mode: 'eu' | 'uk';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const MONTH_ORDER = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
const MONTH_ORDER = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||||
@@ -116,7 +108,18 @@ const ForecastRow: React.FC<{
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const ForecastView: React.FC<ForecastViewProps> = ({ data, filters, top50Ranking, stockMap, vendorStockMap, stockFilter, onStockFilterChange, top50Mode }) => {
|
const ForecastView: React.FC<ForecastViewProps> = ({
|
||||||
|
data,
|
||||||
|
filters,
|
||||||
|
top50Ranking,
|
||||||
|
stockMap,
|
||||||
|
vendorStockMap,
|
||||||
|
stockFilter,
|
||||||
|
onStockFilterChange,
|
||||||
|
vendorStockFilter,
|
||||||
|
onVendorStockFilterChange,
|
||||||
|
top50Mode
|
||||||
|
}) => {
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
const [showOnlyTop50, setShowOnlyTop50] = useState(false);
|
const [showOnlyTop50, setShowOnlyTop50] = useState(false);
|
||||||
const [displayCount, setDisplayCount] = useState(50);
|
const [displayCount, setDisplayCount] = useState(50);
|
||||||
@@ -231,7 +234,24 @@ const ForecastView: React.FC<ForecastViewProps> = ({ data, filters, top50Ranking
|
|||||||
<th className="px-6 py-4 font-black text-slate-400 uppercase tracking-widest text-[10px] min-w-[300px]">
|
<th className="px-6 py-4 font-black text-slate-400 uppercase tracking-widest text-[10px] min-w-[300px]">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span>Product Details</span>
|
<span>Product Details</span>
|
||||||
<InColumnStockFilter currentFilters={stockFilter} onFilterChange={onStockFilterChange} />
|
<div className="flex items-center gap-1 scale-75 origin-left">
|
||||||
|
<InColumnStockFilter
|
||||||
|
currentFilters={stockFilter}
|
||||||
|
onFilterChange={onStockFilterChange}
|
||||||
|
title="Warehouse Stock"
|
||||||
|
/>
|
||||||
|
<InColumnStockFilter
|
||||||
|
currentFilters={vendorStockFilter}
|
||||||
|
onFilterChange={onVendorStockFilterChange}
|
||||||
|
title="Vendor Stock"
|
||||||
|
options={[
|
||||||
|
'Out of Stock (0)',
|
||||||
|
'In Stock (>0)',
|
||||||
|
'In Stock (>20)',
|
||||||
|
'Low Stock (<10)',
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-4 font-black text-slate-400 uppercase tracking-widest text-[10px] text-center">Actuals</th>
|
<th className="px-6 py-4 font-black text-slate-400 uppercase tracking-widest text-[10px] text-center">Actuals</th>
|
||||||
|
|||||||
@@ -3,15 +3,22 @@ import React, { useState, useRef, useEffect } from 'react';
|
|||||||
interface InColumnStockFilterProps {
|
interface InColumnStockFilterProps {
|
||||||
currentFilters: string[];
|
currentFilters: string[];
|
||||||
onFilterChange: (newFilters: string[]) => void;
|
onFilterChange: (newFilters: string[]) => void;
|
||||||
|
title?: string;
|
||||||
|
options?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const STOCK_OPTIONS = [
|
const DEFAULT_STOCK_OPTIONS = [
|
||||||
'Out of Stock (0)',
|
'Out of Stock (0)',
|
||||||
'In Stock (>0)',
|
'In Stock (>0)',
|
||||||
'Low Stock (<10)',
|
'Low Stock (<10)',
|
||||||
];
|
];
|
||||||
|
|
||||||
export const InColumnStockFilter: React.FC<InColumnStockFilterProps> = ({ currentFilters, onFilterChange }) => {
|
export const InColumnStockFilter: React.FC<InColumnStockFilterProps> = ({
|
||||||
|
currentFilters,
|
||||||
|
onFilterChange,
|
||||||
|
title = "Stock Filter",
|
||||||
|
options = DEFAULT_STOCK_OPTIONS
|
||||||
|
}) => {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@@ -59,12 +66,12 @@ export const InColumnStockFilter: React.FC<InColumnStockFilterProps> = ({ curren
|
|||||||
{isOpen && (
|
{isOpen && (
|
||||||
<div className="absolute left-0 mt-2 w-48 bg-slate-900 border border-slate-700 rounded-xl shadow-2xl z-[999] p-2 animate-in fade-in zoom-in duration-150">
|
<div className="absolute left-0 mt-2 w-48 bg-slate-900 border border-slate-700 rounded-xl shadow-2xl z-[999] p-2 animate-in fade-in zoom-in duration-150">
|
||||||
<div className="px-2 py-1.5 text-[10px] font-black text-slate-500 uppercase tracking-widest border-b border-slate-800 mb-1 flex justify-between items-center">
|
<div className="px-2 py-1.5 text-[10px] font-black text-slate-500 uppercase tracking-widest border-b border-slate-800 mb-1 flex justify-between items-center">
|
||||||
<span>Stock Filter</span>
|
<span>{title}</span>
|
||||||
{currentFilters.length > 0 && (
|
{currentFilters.length > 0 && (
|
||||||
<button onClick={clearFilters} className="text-indigo-400 hover:text-indigo-300">Clear</button>
|
<button onClick={clearFilters} className="text-indigo-400 hover:text-indigo-300">Clear</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{STOCK_OPTIONS.map(option => {
|
{options.map(option => {
|
||||||
const isSelected = currentFilters.includes(option);
|
const isSelected = currentFilters.includes(option);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ interface WeeklyGridProps {
|
|||||||
onStockFilterChange: (newFilters: string[]) => void;
|
onStockFilterChange: (newFilters: string[]) => void;
|
||||||
customerFilters: string[];
|
customerFilters: string[];
|
||||||
vendorStockMap?: Map<string, { eu: number; uk: number }>;
|
vendorStockMap?: Map<string, { eu: number; uk: number }>;
|
||||||
|
vendorStockFilter: string[];
|
||||||
|
onVendorStockFilterChange: (newFilters: string[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
type SortConfig = {
|
type SortConfig = {
|
||||||
@@ -133,7 +135,19 @@ const WeeklyRow: React.FC<{
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const WeeklyGrid: React.FC<WeeklyGridProps & { top50Mode: 'eu' | 'uk' }> = ({ data, top50Ranking, onDrillDown, stockMap, vendorStockMap, stockFilter, onStockFilterChange, customerFilters, top50Mode }) => {
|
const WeeklyGrid: React.FC<WeeklyGridProps & { top50Mode: 'eu' | 'uk' }> = ({
|
||||||
|
data,
|
||||||
|
top50Ranking,
|
||||||
|
onDrillDown,
|
||||||
|
stockMap,
|
||||||
|
vendorStockMap,
|
||||||
|
stockFilter,
|
||||||
|
onStockFilterChange,
|
||||||
|
vendorStockFilter,
|
||||||
|
onVendorStockFilterChange,
|
||||||
|
customerFilters,
|
||||||
|
top50Mode
|
||||||
|
}) => {
|
||||||
// Pivot data - memoized
|
// Pivot data - memoized
|
||||||
const { rows, weeks: allWeeks } = useMemo(() => pivotWeeklySalesData(data), [data]);
|
const { rows, weeks: allWeeks } = useMemo(() => pivotWeeklySalesData(data), [data]);
|
||||||
|
|
||||||
@@ -460,10 +474,24 @@ const WeeklyGrid: React.FC<WeeklyGridProps & { top50Mode: 'eu' | 'uk' }> = ({ da
|
|||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span>Product Details</span>
|
<span>Product Details</span>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
<InColumnStockFilter
|
<InColumnStockFilter
|
||||||
currentFilters={stockFilter}
|
currentFilters={stockFilter}
|
||||||
onFilterChange={onStockFilterChange}
|
onFilterChange={onStockFilterChange}
|
||||||
|
title="Warehouse Stock"
|
||||||
/>
|
/>
|
||||||
|
<InColumnStockFilter
|
||||||
|
currentFilters={vendorStockFilter}
|
||||||
|
onFilterChange={onVendorStockFilterChange}
|
||||||
|
title="Vendor Stock"
|
||||||
|
options={[
|
||||||
|
'Out of Stock (0)',
|
||||||
|
'In Stock (>0)',
|
||||||
|
'In Stock (>20)',
|
||||||
|
'Low Stock (<10)',
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
{sortConfig?.metric === 'rank' && (
|
{sortConfig?.metric === 'rank' && (
|
||||||
<span className="text-amber-500 font-black text-sm animate-bounce-subtle">
|
<span className="text-amber-500 font-black text-sm animate-bounce-subtle">
|
||||||
{sortConfig.direction === 'asc' ? '↑' : '↓'}
|
{sortConfig.direction === 'asc' ? '↑' : '↓'}
|
||||||
|
|||||||
@@ -743,12 +743,50 @@ const checkStockFilter = (sku: string, filters: string[], stockMap?: Map<string,
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const checkVendorStockFilter = (asin: string, filters: string[], vendorStockMap?: Map<string, { eu: number; uk: number }>, mode: 'eu' | 'uk' = 'eu'): boolean => {
|
||||||
|
if (!filters || !Array.isArray(filters) || filters.length === 0) return true;
|
||||||
|
if (!vendorStockMap) return true;
|
||||||
|
|
||||||
|
const data = vendorStockMap.get(asin);
|
||||||
|
const stockValue = data ? (mode === 'uk' ? data.uk : data.eu) : 0;
|
||||||
|
|
||||||
|
return filters.some(f => {
|
||||||
|
if (f === 'Out of Stock (0)') return stockValue === 0;
|
||||||
|
if (f === 'In Stock (>20)') return stockValue > 20;
|
||||||
|
if (f === 'In Stock (>0)') return stockValue > 0;
|
||||||
|
if (f === 'Low Stock (<10)') return stockValue < 10;
|
||||||
|
|
||||||
|
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;
|
||||||
|
} else if (!isNaN(parseFloat(input))) {
|
||||||
|
return stockValue === parseFloat(input);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// Filter Ads Data by Country, Year, Week, ASIN, SKU, and Product Line
|
// Filter Ads Data by Country, Year, Week, ASIN, SKU, and Product Line
|
||||||
export const filterAdsData = (
|
export const filterAdsData = (
|
||||||
adsData: AdsRecord[],
|
adsData: AdsRecord[],
|
||||||
filters: FilterState,
|
filters: FilterState,
|
||||||
asinMetadata?: Map<string, { sku: string; line: string }>,
|
asinMetadata?: Map<string, { sku: string; line: string }>,
|
||||||
stockMap?: Map<string, number>
|
stockMap?: Map<string, number>,
|
||||||
|
vendorStockMap?: Map<string, { eu: number; uk: number }>,
|
||||||
|
top50Mode: 'eu' | 'uk' = 'eu'
|
||||||
): AdsRecord[] => {
|
): AdsRecord[] => {
|
||||||
return adsData.filter(ad => {
|
return adsData.filter(ad => {
|
||||||
const asin = ad.asin.trim().toUpperCase();
|
const asin = ad.asin.trim().toUpperCase();
|
||||||
@@ -785,12 +823,19 @@ export const filterAdsData = (
|
|||||||
|
|
||||||
// Stock match
|
// Stock match
|
||||||
const stockMatch = checkStockFilter(meta?.sku || '', filters.stock, stockMap);
|
const stockMatch = checkStockFilter(meta?.sku || '', filters.stock, stockMap);
|
||||||
|
const vendorStockMatch = checkVendorStockFilter(asin, filters.vendorStock, vendorStockMap, top50Mode);
|
||||||
|
|
||||||
return countryMatch && yearMatch && weekMatch && asinMatch && skuMatch && lineMatch && stockMatch;
|
return countryMatch && yearMatch && weekMatch && asinMatch && skuMatch && lineMatch && stockMatch && vendorStockMatch;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const filterData = (data: SalesRecord[], filters: FilterState, stockMap?: Map<string, number>): SalesRecord[] => {
|
export const filterData = (
|
||||||
|
data: SalesRecord[],
|
||||||
|
filters: FilterState,
|
||||||
|
stockMap?: Map<string, number>,
|
||||||
|
vendorStockMap?: Map<string, { eu: number; uk: number }>,
|
||||||
|
top50Mode: 'eu' | 'uk' = 'eu'
|
||||||
|
): SalesRecord[] => {
|
||||||
return data.filter(item => {
|
return data.filter(item => {
|
||||||
// 1. Month Logic: Handle "Apr-23" matching "Apr" filter
|
// 1. Month Logic: Handle "Apr-23" matching "Apr" filter
|
||||||
const recordMonth = item.month; // e.g. "Apr-23"
|
const recordMonth = item.month; // e.g. "Apr-23"
|
||||||
@@ -817,8 +862,9 @@ export const filterData = (data: SalesRecord[], filters: FilterState, stockMap?:
|
|||||||
|
|
||||||
// Stock match
|
// Stock match
|
||||||
const stockMatch = checkStockFilter(item.sku, filters.stock, stockMap);
|
const stockMatch = checkStockFilter(item.sku, filters.stock, stockMap);
|
||||||
|
const vendorStockMatch = checkVendorStockFilter(item.asin, filters.vendorStock, vendorStockMap, top50Mode);
|
||||||
|
|
||||||
return customerMatch && yearMatch && monthMatch && lineMatch && asinMatch && skuMatch && titleMatch && weekMatch && stockMatch;
|
return customerMatch && yearMatch && monthMatch && lineMatch && asinMatch && skuMatch && titleMatch && weekMatch && stockMatch && vendorStockMatch;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export interface FilterState {
|
|||||||
title: string[]; // Added Title filter
|
title: string[]; // Added Title filter
|
||||||
week: string[]; // Added Week filter
|
week: string[]; // Added Week filter
|
||||||
stock: string[]; // Added Stock filter
|
stock: string[]; // Added Stock filter
|
||||||
|
vendorStock: string[]; // Added Vendor Stock filter
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GrowthMetric {
|
export interface GrowthMetric {
|
||||||
|
|||||||
Reference in New Issue
Block a user