mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 12:25:22 +02:00
Implement Vendor Stock Filtering across Weekly Sales, Ads, and Forecast views
This commit is contained in:
@@ -19,6 +19,8 @@ interface AdsPerformanceProps {
|
||||
onStockFilterChange: (newFilters: string[]) => void;
|
||||
top50Mode: 'eu' | 'uk';
|
||||
vendorStockMap?: Map<string, { eu: number; uk: number }>;
|
||||
vendorStockFilter: string[];
|
||||
onVendorStockFilterChange: (newFilters: string[]) => void;
|
||||
}
|
||||
|
||||
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>
|
||||
</div>
|
||||
<span className="text-[10px] uppercase font-black text-indigo-400 tracking-tighter">SKU: {item.sku}</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-[10px] uppercase font-black text-indigo-400 tracking-tighter">SKU: {item.sku}</span>
|
||||
</div>
|
||||
<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>
|
||||
{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 [showOnlyTop50, setShowOnlyTop50] = useState(false);
|
||||
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]">
|
||||
<div className="flex items-center gap-2">
|
||||
<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>
|
||||
</th>
|
||||
<SortableHeader label="TOTAL SALES" sortKey="salesTotal" activeSort={sortConfig} onSort={handleSort} />
|
||||
|
||||
+34
-14
@@ -12,18 +12,10 @@ import {
|
||||
LineChart, Line, Legend, ComposedChart, Area
|
||||
} from 'recharts';
|
||||
|
||||
interface ForecastViewProps {
|
||||
data: ProductForecastData[];
|
||||
filters: FilterState; // Restored filters prop
|
||||
top50Ranking?: {
|
||||
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';
|
||||
top50Mode: 'eu' | 'uk';
|
||||
vendorStockMap ?: Map<string, { eu: number; uk: number }>;
|
||||
vendorStockFilter: string[];
|
||||
onVendorStockFilterChange: (newFilters: string[]) => void;
|
||||
}
|
||||
|
||||
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 [showOnlyTop50, setShowOnlyTop50] = useState(false);
|
||||
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]">
|
||||
<div className="flex items-center gap-2">
|
||||
<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>
|
||||
</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 {
|
||||
currentFilters: string[];
|
||||
onFilterChange: (newFilters: string[]) => void;
|
||||
title?: string;
|
||||
options?: string[];
|
||||
}
|
||||
|
||||
const STOCK_OPTIONS = [
|
||||
const DEFAULT_STOCK_OPTIONS = [
|
||||
'Out of Stock (0)',
|
||||
'In Stock (>0)',
|
||||
'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 containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
@@ -59,12 +66,12 @@ export const InColumnStockFilter: React.FC<InColumnStockFilterProps> = ({ curren
|
||||
{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="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 && (
|
||||
<button onClick={clearFilters} className="text-indigo-400 hover:text-indigo-300">Clear</button>
|
||||
)}
|
||||
</div>
|
||||
{STOCK_OPTIONS.map(option => {
|
||||
{options.map(option => {
|
||||
const isSelected = currentFilters.includes(option);
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -19,6 +19,8 @@ interface WeeklyGridProps {
|
||||
onStockFilterChange: (newFilters: string[]) => void;
|
||||
customerFilters: string[];
|
||||
vendorStockMap?: Map<string, { eu: number; uk: number }>;
|
||||
vendorStockFilter: string[];
|
||||
onVendorStockFilterChange: (newFilters: string[]) => void;
|
||||
}
|
||||
|
||||
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
|
||||
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">
|
||||
<span>Product Details</span>
|
||||
<InColumnStockFilter
|
||||
currentFilters={stockFilter}
|
||||
onFilterChange={onStockFilterChange}
|
||||
/>
|
||||
<div className="flex items-center gap-1">
|
||||
<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>
|
||||
{sortConfig?.metric === 'rank' && (
|
||||
<span className="text-amber-500 font-black text-sm animate-bounce-subtle">
|
||||
{sortConfig.direction === 'asc' ? '↑' : '↓'}
|
||||
|
||||
Reference in New Issue
Block a user