Implement Grid-specific YTD filtering and default sorting

This commit is contained in:
christian.vidal
2026-01-29 20:27:19 +01:00
parent 55c91a7bfc
commit 2c6b9c9f5a
2 changed files with 301 additions and 276 deletions
+43 -19
View File
@@ -497,15 +497,38 @@ const App: React.FC = () => {
// Determine which dataset to use for velocity calculation based on top50Mode
// Combine Sales & Ads Data dynamically based on current filters
const combinedAdsData = useMemo(() => {
return mergeSalesAndAdsData(filteredData, filteredAdsData, globalAsinMetadata, trafficData, velocityMap);
}, [filteredData, filteredAdsData, globalAsinMetadata, trafficData, velocityMap]);
}, [filteredData, filteredAdsData, globalAsinMetadata, trafficData, velocityMap]);
// Derived Data for Views
const years = useMemo(() => getUniqueValues(rawData, 'year').sort().reverse(), [rawData]);
// Derived Data for Grid (YTD Filtered - Isolated)
// 1. Identify "Current Year" max week
const gridContext = useMemo(() => {
if (rawData.length === 0) return { minWeek: 1, maxWeek: 53, currentYear: new Date().getFullYear() };
// Derive Context Data (Product Line Context when drilling down)
const contextAggregatedData = useMemo(() => {
const currentYear = Math.max(...rawData.map(r => r.year));
const currentYearData = rawData.filter(r => r.year === currentYear);
const maxWeek = Math.max(...currentYearData.map(r => r.week).filter(Boolean));
return { minWeek: 1, maxWeek: maxWeek || 53, currentYear };
}, [rawData]);
const ytdGridData = useMemo(() => {
// Filter combinedAdsData to only include weeks <= maxWeek
// This ensures we compare "Like for Like" periods across years
// We do strictly LESS THAN OR EQUAL to maxWeek.
if (!combinedAdsData) return [];
return combinedAdsData.filter(item => {
// CombinedKPIs has 'week' property
if (!item.week) return true; // Keep non-weekly items if any (shouldn't be)
return item.week <= gridContext.maxWeek;
});
}, [combinedAdsData, gridContext.maxWeek]);
// Derived Data for Views
const years = useMemo(() => getUniqueValues(rawData, 'year').sort().reverse(), [rawData]);
// Derive Context Data (Product Line Context when drilling down)
const contextAggregatedData = useMemo(() => {
// Check if we are filtering by specific items (SKU, ASIN, Title)
const hasItemFilters = filters.sku.length > 0 || filters.asin.length > 0 || filters.title.length > 0;
@@ -530,11 +553,11 @@ const App: React.FC = () => {
const broadData = filterData(rawData, contextFilters);
return aggregateData(broadData);
}, [rawData, filters, filteredData]);
}, [rawData, filters, filteredData]);
// Derive Options for Filter Dropdowns
const filterOptions = useMemo(() => {
// Derive Options for Filter Dropdowns
const filterOptions = useMemo(() => {
return {
customer: getUniqueValues(rawData, 'customer'),
year: getUniqueValues(rawData, 'year'),
@@ -551,17 +574,17 @@ const App: React.FC = () => {
...Array.from(new Set(Array.from(stockMap.values()).map(String))).sort((a, b) => parseFloat(a) - parseFloat(b))
]
};
}, [rawData, stockMap]);
}, [rawData, stockMap]);
const handleFilterChange = (key: keyof FilterState, value: string[]) => {
const handleFilterChange = (key: keyof FilterState, value: string[]) => {
setFilters(prev => ({ ...prev, [key]: value }));
};
};
const handleAskGemini = async (text: string) => {
const handleAskGemini = async (text: string) => {
return await queryGemini(text, aggregatedData, filteredData.length);
};
};
const disconnectUrl = async () => {
const disconnectUrl = async () => {
// Allow disconnecting to clear data, but the app will likely re-connect on next reload due to "Permanent" requirement
localStorage.removeItem('craze_csv_url');
await clearSalesData();
@@ -569,9 +592,9 @@ const App: React.FC = () => {
setActiveUrl(null);
setRawData([]);
setAdsData([]);
};
};
return (
return (
<div className="min-h-screen flex flex-col bg-background text-slate-200">
{/* Header */}
@@ -683,7 +706,7 @@ const App: React.FC = () => {
<Suspense fallback={<LoadingSpinner />}>
<div className={view === 'table' ? '' : 'hidden'}>
<DataGrid
data={combinedAdsData}
data={ytdGridData}
hasCustomerFilter={filters.customer.length > 0}
adsData={filteredAdsData}
stockMap={stockMap}
@@ -692,6 +715,7 @@ const App: React.FC = () => {
top50Mode={filters.customer.includes('Amazon UK') ? 'uk' : 'eu'}
velocityMap={velocityMap}
buyBoxLostMap={buyBoxLostMap}
defaultSort={{ key: `total_sellOut_${gridContext.currentYear}`, direction: 'desc' }}
/>
</div>
</Suspense>
@@ -801,7 +825,7 @@ const App: React.FC = () => {
}
</div >
);
);
};
export default App;
+3 -2
View File
@@ -23,6 +23,7 @@ interface DataGridProps {
vendorStockMap?: Map<string, { eu: number; uk: number }>;
velocityMap?: Map<string, number>;
buyBoxLostMap?: Map<string, { countries: string[]; reasons: Record<string, string> }>;
defaultSort?: SortConfig;
}
type SortConfig = {
@@ -239,9 +240,9 @@ const ExpandableChartCard: React.FC<{ title: string; children: React.ReactNode;
};
const DataGrid: React.FC<DataGridProps> = ({ data, hasCustomerFilter, adsData, stockMap, vendorStockMap, top50Ranking, top50Mode, velocityMap, buyBoxLostMap }) => {
const DataGrid: React.FC<DataGridProps> = ({ data, hasCustomerFilter, adsData, stockMap, vendorStockMap, top50Ranking, top50Mode, velocityMap, buyBoxLostMap, defaultSort }) => {
const [currentPage, setCurrentPage] = useState(1);
const [sortConfig, setSortConfig] = useState<SortConfig>({ key: null, direction: 'desc' });
const [sortConfig, setSortConfig] = useState<SortConfig>(defaultSort || { key: null, direction: 'desc' });
const [showChart, setShowChart] = useState(true);
const [visibleMetrics, setVisibleMetrics] = useState<('sellOut' | 'units')[]>(['sellOut', 'units']);
const [showAdsMetrics, setShowAdsMetrics] = useState(true);