import React, { useState, useMemo } from 'react'; import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; import MultiSelectDropdown from './MultiSelectDropdown'; import { BSRRecord } from '../types'; const MARKET_COLORS: Record = { DE: '#3b82f6', UK: '#ef4444', IT: '#22c55e', FR: '#a855f7', ES: '#f59e0b', }; interface VendorDataViewProps { bsrData: BSRRecord[]; } interface ChartPoint { label: string; sortKey: string; [key: string]: number | string | null; } const VendorDataView: React.FC = ({ bsrData }) => { // Filters const [selectedMarkets, setSelectedMarkets] = useState([]); const [selectedAsins, setSelectedAsins] = useState([]); const [selectedTopCats, setSelectedTopCats] = useState([]); const [selectedDetailCats, setSelectedDetailCats] = useState([]); // Extract available filter options from the dataset const { availableMarkets, availableAsins, availableTopCats, availableDetailCats } = useMemo(() => { const markets = new Set(); const asins = new Set(); const topCats = new Set(); const detailCats = new Set(); bsrData.forEach(r => { if (r.market) markets.add(r.market); if (r.asin) asins.add(r.asin); if (r.topLevelName) topCats.add(r.topLevelName); if (r.detailLevelName) detailCats.add(r.detailLevelName); }); return { availableMarkets: Array.from(markets).sort(), availableAsins: Array.from(asins).sort(), availableTopCats: Array.from(topCats).sort(), availableDetailCats: Array.from(detailCats).sort() }; }, [bsrData]); // Apply filters const filteredData = useMemo(() => { let result = bsrData; if (selectedMarkets.length > 0) { result = result.filter(r => selectedMarkets.includes(r.market)); } if (selectedAsins.length > 0) { result = result.filter(r => selectedAsins.includes(r.asin)); } if (selectedTopCats.length > 0) { result = result.filter(r => r.topLevelName && selectedTopCats.includes(r.topLevelName)); } if (selectedDetailCats.length > 0) { result = result.filter(r => r.detailLevelName && selectedDetailCats.includes(r.detailLevelName)); } return result; }, [bsrData, selectedMarkets, selectedAsins, selectedTopCats, selectedDetailCats]); // Determine active markets in filtered data for series generation const activeMarkets = useMemo(() => { const m = new Set(filteredData.map(r => r.market)); return Array.from(m).sort(); }, [filteredData]); // Determine if daily resolution is available (>= half the records have a date) const useDailyResolution = useMemo(() => { const withDate = filteredData.filter(r => r.date).length; return withDate > filteredData.length / 2; }, [filteredData]); // Aggregate Data for Charts — daily if dates available, else weekly const { topBsrChartData, detailBsrChartData, ratingChartData } = useMemo(() => { // Group by date string (YYYY-MM-DD) or by week number const byBucket = new Map(); filteredData.forEach(r => { const key = useDailyResolution && r.date ? r.date : `W${String(r.week).padStart(2, '0')}`; if (!byBucket.has(key)) byBucket.set(key, []); byBucket.get(key)!.push(r); }); const sortedKeys = Array.from(byBucket.keys()).sort(); const topBsrChartData: ChartPoint[] = []; const detailBsrChartData: ChartPoint[] = []; const ratingChartData: ChartPoint[] = []; sortedKeys.forEach(key => { const rows = byBucket.get(key)!; // Human-readable label let label: string; if (useDailyResolution && key.includes('-')) { // YYYY-MM-DD → "DD MMM" const d = new Date(key + 'T12:00:00Z'); label = d.toLocaleDateString('en-GB', { day: '2-digit', month: 'short' }); } else { label = key; // e.g. "W08" } const topPoint: ChartPoint = { label, sortKey: key }; const detailPoint: ChartPoint = { label, sortKey: key }; const ratingPoint: ChartPoint = { label, sortKey: key }; activeMarkets.forEach(m => { const marketRows = rows.filter(r => r.market === m); // Top BSR const topBsrRows = marketRows.filter(r => r.topLevelBSR != null); topPoint[`${m}_bsr`] = topBsrRows.length > 0 ? Math.round(topBsrRows.reduce((sum, r) => sum + r.topLevelBSR!, 0) / topBsrRows.length) : null; // Detail BSR const detailBsrRows = marketRows.filter(r => r.detailLevelBSR != null); detailPoint[`${m}_bsr`] = detailBsrRows.length > 0 ? Math.round(detailBsrRows.reduce((sum, r) => sum + r.detailLevelBSR!, 0) / detailBsrRows.length) : null; // Rating const ratingRows = marketRows.filter(r => r.avgRating != null); ratingPoint[`${m}_rating`] = ratingRows.length > 0 ? Math.round((ratingRows.reduce((sum, r) => sum + r.avgRating!, 0) / ratingRows.length) * 10) / 10 : null; }); topBsrChartData.push(topPoint); detailBsrChartData.push(detailPoint); ratingChartData.push(ratingPoint); }); return { topBsrChartData, detailBsrChartData, ratingChartData }; }, [filteredData, activeMarkets, useDailyResolution]); if (bsrData.length === 0) { return (

No BSR Data Yet

Ensure BSR.xlsx is present and loading to see Top Level BSR, Detail Level BSR, and Average Rating trends.

); } const resolutionLabel = useDailyResolution ? 'Daily Avg' : 'Weekly Avg'; return (
{/* Filters */}
{(selectedMarkets.length > 0 || selectedAsins.length > 0 || selectedTopCats.length > 0 || selectedDetailCats.length > 0) && ( )} {filteredData.length.toLocaleString()} records filtered
{/* Top Level BSR Trend Chart */}

Top Level BSR Trend ({resolutionLabel})

Lower rank = better position. Averaged across filtered ASINs per market.

{activeMarkets.map(m => ( ))}
{/* Detail Level BSR Trend Chart */}

Detail Level BSR Trend ({resolutionLabel})

Lower rank = better position. Averaged across filtered ASINs per market.

{activeMarkets.map(m => ( ))}
{/* Average Rating Chart */}

Average Rating ({resolutionLabel})

Averaged across filtered ASINs per market.

{activeMarkets.map(m => ( ))}
); }; export default VendorDataView;