import React, { useState, useMemo, useEffect, useRef } from 'react'; import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; import { SalesRecord, PivotRow, AdsRecord, CombinedKPIs } from '../types'; import { pivotSalesData, generateXLSX, aggregateForTimeSeries, aggregateForComparisonTimeSeries, applyPanEUGrouping } from '../services/dataProcessor'; import { DownloadIcon, FunnelIcon, CloseIcon, ChartIcon, TrendingIcon } from './Icons'; import { StockBadge } from './StockBadge'; interface DataGridProps { data: SalesRecord[] | CombinedKPIs[]; hasCustomerFilter: boolean; adsData?: AdsRecord[]; stockMap?: Map; } type SortConfig = { key: string | null; direction: 'asc' | 'desc'; }; type ConditionalFilter = { id: string; metric: string; operator: 'gt' | 'lt'; value: number; } const ROWS_PER_PAGE = 50; const CHART_COLORS = ['#6366f1', '#ec4899', '#10b981', '#f59e0b', '#8b5cf6', '#0ea5e9']; // Available grouping dimensions const DIMENSION_OPTIONS = [ { label: 'Product Line', value: 'line' }, { label: 'Customer', value: 'customer' }, { label: 'SKU', value: 'sku' }, { label: 'Title', value: 'title' }, { label: 'ASIN', value: 'asin' }, ]; // Tooltip for single-period view with Week-over-Week comparison const WoWTooltip = ({ active, payload, label, data }: any) => { if (active && payload && payload.length && data) { const currentIndex = data.findIndex((d: any) => d.name === label); const prevData = currentIndex > 0 ? data[currentIndex - 1] : null; return (

{label}

{payload.map((p: any) => { let wowEl = null; if (prevData) { const prevValue = prevData[p.dataKey]; const currentValue = p.value; if (prevValue != null && prevValue > 0) { const pct = ((currentValue - prevValue) / prevValue) * 100; wowEl = ( = 0 ? 'text-emerald-400' : 'text-red-400'}`}> {pct >= 0 ? '▲' : '▼'} {Math.abs(pct).toFixed(1)}% ); } } return (
{p.name}:
{p.dataKey === 'sellOut' ? `€${Number(p.value).toLocaleString('de-DE', { minimumFractionDigits: 0, maximumFractionDigits: 0 })}` : `${Number(p.value).toLocaleString('de-DE')} u`} {wowEl}
); })}
); } return null; } // Tooltip for multi-year comparison view const ComparisonTooltip = ({ active, payload, label }: any) => { if (active && payload && payload.length) { interface YearData { sellOut?: number; units?: number; color?: string; } const dataByYear: { [year: string]: YearData } = {}; payload.forEach((p: any) => { const nameParts = p.name.split(' '); if (nameParts.length < 2) return; const year = nameParts[nameParts.length - 1]; const metric = nameParts.slice(0, nameParts.length - 1).join(' '); if (!dataByYear[year]) { dataByYear[year] = {}; } // Use the color from the Sell Out line for consistency for that year block if (metric.toLowerCase().includes('sell out')) { dataByYear[year].sellOut = p.value; dataByYear[year].color = p.stroke || p.color; } else if (metric.toLowerCase().includes('units')) { dataByYear[year].units = p.value; if (!dataByYear[year].color) { // fallback color from units line dataByYear[year].color = p.stroke || p.color; } } }); const sortedYears = Object.keys(dataByYear).sort((a, b) => parseInt(b) - parseInt(a)); return (

{label}

{sortedYears.map((year, index) => { const yearData = dataByYear[year]; const prevYear = sortedYears[index + 1]; const prevYearData = prevYear ? dataByYear[prevYear] : null; let sellOutGrowthEl = null; if (prevYearData && prevYearData.sellOut != null && prevYearData.sellOut !== 0 && yearData.sellOut != null) { const pct = ((yearData.sellOut - prevYearData.sellOut) / prevYearData.sellOut) * 100; sellOutGrowthEl = ( = 0 ? 'text-emerald-400' : 'text-red-400'}`}> {pct >= 0 ? '▲' : '▼'} {Math.abs(pct).toFixed(1)}% ); } let unitsGrowthEl = null; if (prevYearData && prevYearData.units != null && prevYearData.units !== 0 && yearData.units != null) { const pct = ((yearData.units - prevYearData.units) / prevYearData.units) * 100; unitsGrowthEl = ( = 0 ? 'text-emerald-400' : 'text-red-400'}`}> {pct >= 0 ? '▲' : '▼'} {Math.abs(pct).toFixed(1)}% ); } return (

{year}

{yearData.sellOut != null && (
Sell Out:
€{Number(yearData.sellOut).toLocaleString('de-DE', { minimumFractionDigits: 0, maximumFractionDigits: 0 })} {sellOutGrowthEl}
)} {yearData.units != null && (
Units:
{Number(yearData.units).toLocaleString('de-DE')} u {unitsGrowthEl}
)}
); })}
); } return null; }; // Reusable Expandable Chart Card const ExpandableChartCard: React.FC<{ title: string; children: React.ReactNode; className?: string }> = ({ title, children, className }) => { const [isExpanded, setIsExpanded] = useState(false); const toggleExpand = () => setIsExpanded(!isExpanded); if (isExpanded) { return (

{title}

{children}
); } return (

{title}

{children}
); }; const DataGrid: React.FC = ({ data, hasCustomerFilter, adsData, stockMap }) => { const [currentPage, setCurrentPage] = useState(1); const [sortConfig, setSortConfig] = useState({ key: null, direction: 'desc' }); const [showChart, setShowChart] = useState(true); const [visibleMetrics, setVisibleMetrics] = useState<('sellOut' | 'units')[]>(['sellOut', 'units']); const [showAdsMetrics, setShowAdsMetrics] = useState(true); const [showAttributedSales, setShowAttributedSales] = useState(false); const [showDimensionMenu, setShowDimensionMenu] = useState(false); const dimensionRef = useRef(null); // Close dimension menu on click outside useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (dimensionRef.current && !dimensionRef.current.contains(event.target as Node)) { setShowDimensionMenu(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); }, []); // Calculate Ads Summary for the Grid const adsSummary = useMemo(() => { if (!adsData || adsData.length === 0) return null; const totals = adsData.reduce((acc, ad) => ({ cost: acc.cost + ad.cost, attributedSales: acc.attributedSales + ad.attributedSales30d, clicks: acc.clicks + ad.clicks, impressions: acc.impressions + ad.impressions, }), { cost: 0, attributedSales: 0, clicks: 0, impressions: 0 }); return { totalSpend: totals.cost, attributedSales: totals.attributedSales, acos: totals.attributedSales > 0 ? (totals.cost / totals.attributedSales) * 100 : 0, roas: totals.cost > 0 ? totals.attributedSales / totals.cost : 0, recordCount: adsData.length, }; }, [adsData]); // State for dynamic grouping const [selectedDimensions, setSelectedDimensions] = useState(['line', 'customer', 'sku', 'title']); // State for Advanced Filtering const [showFilterBuilder, setShowFilterBuilder] = useState(false); const [rowFilters, setRowFilters] = useState([]); // Temp state for new filter inputs const [newFilterMetric, setNewFilterMetric] = useState(''); const [newFilterOperator, setNewFilterOperator] = useState<'gt' | 'lt'>('gt'); const [newFilterValue, setNewFilterValue] = useState(''); // Effective dimensions for rendering const effectiveDimensions = useMemo(() => selectedDimensions.length > 0 ? selectedDimensions : ['customer'], [selectedDimensions]); // Transform flat data into Pivot structure const pivotRows = useMemo(() => { // Apply Pan-EU grouping when no customer filter is applied const processedData = applyPanEUGrouping(data as SalesRecord[], hasCustomerFilter); // pivotSalesData now handles ads aggregation correctly because it receives CombinedKPIs const { rows } = pivotSalesData(processedData, effectiveDimensions); return rows; }, [data, effectiveDimensions, hasCustomerFilter]); const { years } = useMemo(() => { // We still need unique years for columns const yearsSet = new Set(data.map(d => String(d.year))); return { years: Array.from(yearsSet).sort((a, b) => parseInt(b) - parseInt(a)) }; }, [data]); // Data for the time series chart, supporting single and multi-year comparison const { chartData, uniqueYears, isComparisonView, chartTitle } = useMemo(() => { const yearsInView = Array.from(new Set(data.map(d => d.year.toString()))).sort((a: string, b: string) => parseInt(b) - parseInt(a)); const isMultiYear = yearsInView.length > 1; // Get base sales data let salesChartData = isMultiYear ? aggregateForComparisonTimeSeries(data) : aggregateForTimeSeries(data); // Aggregate ads data by week/year and merge into chartData if (adsData && adsData.length > 0) { const adsMap = new Map(); adsData.forEach(ad => { if (ad.week >= 1 && ad.week <= 53) { if (!adsMap.has(ad.week)) { adsMap.set(ad.week, {}); } const weekData = adsMap.get(ad.week)!; const adSpendKey = `${ad.year}_adSpend`; const attrSalesKey = `${ad.year}_attributedSales`; weekData[adSpendKey] = (weekData[adSpendKey] || 0) + ad.cost; weekData[attrSalesKey] = (weekData[attrSalesKey] || 0) + ad.attributedSales30d; } }); // Merge ads data into sales chart data salesChartData = salesChartData.map(point => { const adsWeekData = adsMap.get(point.week) || {}; return { ...point, ...adsWeekData }; }); } return { chartData: salesChartData, uniqueYears: yearsInView, isComparisonView: isMultiYear, chartTitle: isMultiYear ? `Weekly Sales Comparison: ${yearsInView.join(' vs ')}` : `Weekly Sales Evolution ${yearsInView[0] || ''}` }; }, [data, adsData]); // Filter Options based on available data const metricOptions = useMemo(() => { const options = []; // Totals years.forEach(y => { options.push({ label: `Total Sell Out ${y} (€)`, value: `total_sellOut_${y}` }); options.push({ label: `Total Units ${y}`, value: `total_units_${y}` }); }); // Growth (Latest vs Previous) if (years.length >= 2) { options.push({ label: `Growth % Sell Out (${years[0]} vs ${years[1]})`, value: 'growth_sellOut' }); options.push({ label: `Growth % Units (${years[0]} vs ${years[1]})`, value: 'growth_units' }); } return options; }, [years]); // Apply Advanced Row Filters THEN Sort const processedRows = useMemo(() => { let result = pivotRows; // 1. Filter if (rowFilters.length > 0) { const latestYear = years[0]; const prevYear = years[1]; result = result.filter(row => { return rowFilters.every(filter => { let rowValue = 0; if (filter.metric.startsWith('total_sellOut_')) { const y = filter.metric.split('_')[2]; rowValue = row.totalsByYear[y]?.sellOut || 0; } else if (filter.metric.startsWith('total_units_')) { const y = filter.metric.split('_')[2]; rowValue = row.totalsByYear[y]?.units || 0; } else if (filter.metric === 'growth_sellOut') { if (!prevYear) return true; const curr = row.totalsByYear[latestYear]?.sellOut || 0; const prev = row.totalsByYear[prevYear]?.sellOut || 0; if (prev === 0) return curr > 0; rowValue = ((curr - prev) / prev) * 100; } else if (filter.metric === 'growth_units') { if (!prevYear) return true; const curr = row.totalsByYear[latestYear]?.units || 0; const prev = row.totalsByYear[prevYear]?.units || 0; if (prev === 0) return curr > 0; rowValue = ((curr - prev) / prev) * 100; } if (filter.operator === 'gt') return rowValue > filter.value; if (filter.operator === 'lt') return rowValue < filter.value; return true; }); }); } // 2. Sort if (sortConfig.key) { // Create a copy to avoid mutating the original array and ensure React detects the change result = [...result].sort((a, b) => { let valA: number | string = ''; let valB: number | string = ''; // Handle sorting by dimensions if (['customer', 'line', 'sku', 'title', 'articleName', 'asin'].includes(sortConfig.key as string)) { valA = a[sortConfig.key as keyof PivotRow] as string || ''; valB = b[sortConfig.key as keyof PivotRow] as string || ''; } // Handle sorting by Total Metrics (total_sellOut_2023) else if ((sortConfig.key as string).startsWith('total_')) { const parts = (sortConfig.key as string).split('_'); // parts[1] = metric (sellOut/units), parts[2] = year if (parts.length === 3) { const y = parts[2]; const m = parts[1] as 'sellOut' | 'units'; valA = a.totalsByYear[y]?.[m] || 0; valB = b.totalsByYear[y]?.[m] || 0; } } // Handle sorting by Growth Percentages (growth_sellOut_2024_2023) else if ((sortConfig.key as string).startsWith('growth_')) { const parts = (sortConfig.key as string).split('_'); // parts[1] = metric (sellOut/units), parts[2] = current year, parts[3] = previous year if (parts.length === 4) { const metric = parts[1] as 'sellOut' | 'units'; const currentYear = parts[2]; const prevYear = parts[3]; const currA = a.totalsByYear[currentYear]?.[metric] || 0; const prevA = a.totalsByYear[prevYear]?.[metric] || 0; valA = prevA !== 0 ? ((currA - prevA) / prevA) * 100 : (currA > 0 ? 100 : 0); const currB = b.totalsByYear[currentYear]?.[metric] || 0; const prevB = b.totalsByYear[prevYear]?.[metric] || 0; valB = prevB !== 0 ? ((currB - prevB) / prevB) * 100 : (currB > 0 ? 100 : 0); } } // Handle sorting by Ads Metrics (adSpend_2025, tacos_2025) else if ((sortConfig.key as string).includes('_') && !((sortConfig.key as string).startsWith('total_') || (sortConfig.key as string).startsWith('growth_'))) { const parts = (sortConfig.key as string).split('_'); if (parts.length === 2) { const metric = parts[0] as 'adSpend' | 'tacos'; const year = parts[1]; valA = a.adsByYear?.[year]?.[metric] || 0; valB = b.adsByYear?.[year]?.[metric] || 0; } } if (valA < valB) return sortConfig.direction === 'asc' ? -1 : 1; if (valA > valB) return sortConfig.direction === 'asc' ? 1 : -1; return 0; }); } return result; }, [pivotRows, rowFilters, sortConfig, years]); // Calculate aggregated totals for the header row const filteredTotalsByYear = useMemo(() => { const result: { [year: string]: { sellOut: number, units: number, sellOutGrowth?: number, unitsGrowth?: number } } = {}; years.forEach((year, index) => { const totals = processedRows.reduce( (acc, row) => { const data = row.totalsByYear[year]; if (data) { acc.sellOut += data.sellOut; acc.units += data.units; } const ads = row.adsByYear?.[year]; if (ads) { acc.adSpend += ads.adSpend; acc.attributedSales += ads.attributedSales; } return acc; }, { sellOut: 0, units: 0, adSpend: 0, attributedSales: 0 } ); result[year] = totals; // Calculate YoY growth if previous year exists const prevYear = years[index + 1]; if (prevYear) { const prevYearTotals = processedRows.reduce( (acc, row) => { const data = row.totalsByYear[prevYear]; if (data) { acc.sellOut += data.sellOut; acc.units += data.units; } const ads = row.adsByYear?.[prevYear]; if (ads) { acc.adSpend += ads.adSpend; } return acc; }, { sellOut: 0, units: 0, adSpend: 0 } ); if (prevYearTotals.sellOut > 0) { result[year].sellOutGrowth = ((totals.sellOut - prevYearTotals.sellOut) / prevYearTotals.sellOut) * 100; } else if (totals.sellOut > 0) { result[year].sellOutGrowth = 100; } if (prevYearTotals.units > 0) { result[year].unitsGrowth = ((totals.units - prevYearTotals.units) / prevYearTotals.units) * 100; } else if (totals.units > 0) { result[year].unitsGrowth = 100; } if (prevYearTotals.adSpend > 0) { (result[year] as any).adSpendGrowth = ((totals.adSpend - prevYearTotals.adSpend) / prevYearTotals.adSpend) * 100; } else if (totals.adSpend > 0) { (result[year] as any).adSpendGrowth = 100; } } }); return result; }, [processedRows, years]); const paginatedRows = useMemo(() => { const start = (currentPage - 1) * ROWS_PER_PAGE; return processedRows.slice(start, start + ROWS_PER_PAGE); }, [processedRows, currentPage]); const totalPages = Math.ceil(processedRows.length / ROWS_PER_PAGE); const requestSort = (key: string) => { let direction: 'asc' | 'desc' = 'desc'; if (sortConfig.key === key && sortConfig.direction === 'desc') { direction = 'asc'; } setSortConfig({ key, direction }); }; const getSortIcon = (key: string) => { if (sortConfig.key !== key) return ; return {sortConfig.direction === 'asc' ? '↑' : '↓'}; }; const handleExport = () => { generateXLSX(processedRows, effectiveDimensions, years); }; const addFilter = () => { if (newFilterMetric && newFilterValue) { setRowFilters([ ...rowFilters, { id: Date.now().toString(), metric: newFilterMetric, operator: newFilterOperator, value: parseFloat(newFilterValue) } ]); setNewFilterMetric(''); setNewFilterValue(''); setShowFilterBuilder(false); } }; const removeFilter = (id: string) => { setRowFilters(rowFilters.filter(f => f.id !== id)); }; // Reset pagination when filters change useEffect(() => { setCurrentPage(1); }, [rowFilters, data, effectiveDimensions]); return (
{/* 1. Time Series Chart Section */} {showChart && chartData.length > 0 && (
`€${(val / 1000).toFixed(0)}k`} /> isComparisonView ? : } /> {isComparisonView ? ( uniqueYears.flatMap((year, idx) => [ visibleMetrics.includes('sellOut') && ( ), visibleMetrics.includes('units') && ( ), // Ad Spend line (only when ads data exists and showAdsMetrics is on) showAdsMetrics && adsSummary && ( ), // Attributed Sales line showAttributedSales && adsSummary && ( ) ]) ) : ( [ visibleMetrics.includes('sellOut') && ( ), visibleMetrics.includes('units') && ( ) ] )}
)} {/* 2. Controls & Grid */}
{/* Toolbar */}
{/* Dimensions Selector */}
{showDimensionMenu && (
Select Columns
{DIMENSION_OPTIONS.map(dim => ( ))}

The table will automatically refresh when you toggle these options.

)}
{/* Filter Builder Trigger */} {/* Chart Toggle */} {/* Ads Toggle - Only show when ads data is loaded */} {adsSummary && ( )} {/* Attributed Sales Toggle - Only show when ads data is loaded and ads metrics are shown */} {adsSummary && ( )}
Showing {processedRows.length} rows
{/* Filter Builder Panel */} {showFilterBuilder && (
setNewFilterValue(e.target.value)} placeholder="0" className="w-full bg-slate-950 border border-slate-700 rounded-lg px-3 py-2 text-sm text-slate-200 focus:outline-none focus:border-primary" />
{/* Active Filters Chips */} {rowFilters.length > 0 && (
{rowFilters.map(filter => { const metricLabel = metricOptions.find(m => m.value === filter.metric)?.label || filter.metric; return (
{metricLabel} {filter.operator === 'gt' ? '>' : '<'} {filter.value}
); })}
)}
)}
{/* Ads Performance Summary - Shows when ads data is loaded */} {adsSummary && showAdsMetrics && (
Advertising Data: {adsSummary.recordCount.toLocaleString('de-DE')} records
Ad Spend: €{adsSummary.totalSpend.toLocaleString('de-DE', { maximumFractionDigits: 0 })}
Attr. Sales: €{adsSummary.attributedSales.toLocaleString('de-DE', { maximumFractionDigits: 0 })}
ACOS: {adsSummary.acos.toFixed(1)}%
ROAS: = 3 ? 'text-emerald-400' : adsSummary.roas >= 2 ? 'text-amber-400' : 'text-red-400'}`}> {adsSummary.roas.toFixed(2)}x
)} {/* Data Table */}
{/* NEW: Filtered Totals Header Row */} {years.map((year, index) => { const totals = filteredTotalsByYear[year]; const prevYear = years[index + 1]; return ( {prevYear && } {showAdsMetrics && adsSummary && ( <> )} ); })} {/* Dynamic Dimension Headers */} {effectiveDimensions.map(dim => { const label = DIMENSION_OPTIONS.find(d => d.value === dim)?.label || dim; return ( ); })} {/* Data Columns: Year Groups */} {years.map((year, index) => { const prevYear = years[index + 1]; // Since years are sorted desc: 2025, 2024... next index is prev year return ( {/* Sell Out & Units */} {/* Growth Columns (if prev year exists) */} {prevYear && ( <> )} {/* Ads Columns - Only show when ads data exists and toggle is on */} {showAdsMetrics && adsSummary && ( <> )} ); })} {paginatedRows.map((row) => ( {effectiveDimensions.map(dim => ( ))} {/* Metric Values */} {years.map((year, index) => { const data = row.totalsByYear[year]; const prevYear = years[index + 1]; const prevData = prevYear ? row.totalsByYear[prevYear] : null; // Calculate Growth let sellOutGrowth = null; let unitsGrowth = null; if (prevYear) { const currSO = data?.sellOut || 0; const prevSO = prevData?.sellOut || 0; if (prevSO !== 0) sellOutGrowth = ((currSO - prevSO) / prevSO) * 100; else if (currSO > 0) sellOutGrowth = 100; // New entry const currUnits = data?.units || 0; const prevUnits = prevData?.units || 0; if (prevUnits !== 0) unitsGrowth = ((currUnits - prevUnits) / prevUnits) * 100; else if (currUnits > 0) unitsGrowth = 100; } return ( {/* Growth Cells */} {prevYear && ( <> )} {/* Ads Data Cells */} {showAdsMetrics && adsSummary && (() => { const adsYearData = row.adsByYear?.[year]; return ( <> ); })()} ); })} ))} {paginatedRows.length === 0 && ( )}
Filtered Totals
€{totals.sellOut.toLocaleString('de-DE', { maximumFractionDigits: 0 })} {totals.sellOutGrowth !== undefined && ( = 0 ? 'text-emerald-400' : 'text-red-400'}`}> {totals.sellOutGrowth >= 0 ? '↑' : '↓'} {Math.abs(totals.sellOutGrowth).toFixed(1)}% )}
{totals.units.toLocaleString('de-DE')} {totals.unitsGrowth !== undefined && ( = 0 ? 'text-emerald-400' : 'text-red-400'}`}> {totals.unitsGrowth >= 0 ? '↑' : '↓'} {Math.abs(totals.unitsGrowth).toFixed(1)}% )}
€{totals.adSpend.toLocaleString('de-DE', { maximumFractionDigits: 0 })} {(totals as any).adSpendGrowth !== undefined && ( {(totals as any).adSpendGrowth >= 0 ? '↑' : '↓'} {Math.abs((totals as any).adSpendGrowth).toFixed(1)}% )}
{totals.sellOut > 0 ? ((totals.adSpend / totals.sellOut) * 100).toFixed(2) : '0.00'}% TACOS
requestSort(dim)} >
{label} {getSortIcon(dim)}
requestSort(`total_sellOut_${year}`)} >
Sell Out {year} {getSortIcon(`total_sellOut_${year}`)}
requestSort(`total_units_${year}`)} >
Units {year} {getSortIcon(`total_units_${year}`)}
requestSort(`growth_sellOut_${year}_${prevYear}`)} >
S.O. Δ% {getSortIcon(`growth_sellOut_${year}_${prevYear}`)}
requestSort(`growth_units_${year}_${prevYear}`)} >
Units Δ% {getSortIcon(`growth_units_${year}_${prevYear}`)}
requestSort(`adSpend_${year}`)} >
Ad Spend {year} {getSortIcon(`adSpend_${year}`)}
requestSort(`tacos_${year}`)} >
TACOS {year} {getSortIcon(`tacos_${year}`)}
{dim === 'title' ?
{row.title || '-'} {stockMap && ( )}
: (row[dim as keyof PivotRow] as string) || '-' }
{data ? `€${data.sellOut.toLocaleString('de-DE', { maximumFractionDigits: 0 })}` : '-'} {data ? data.units.toLocaleString('de-DE') : '-'} = 0 ? 'text-emerald-500' : 'text-red-500') : 'text-slate-600'}`}> {sellOutGrowth !== null ? ( {sellOutGrowth >= 0 ? '↑' : '↓'} {Math.abs(sellOutGrowth).toFixed(0)}% ) : '-'} = 0 ? 'text-emerald-500' : 'text-red-500') : 'text-slate-600'}`}> {unitsGrowth !== null ? ( {unitsGrowth >= 0 ? '↑' : '↓'} {Math.abs(unitsGrowth).toFixed(0)}% ) : '-'} {adsYearData ? `€${adsYearData.adSpend.toLocaleString('de-DE', { maximumFractionDigits: 0 })}` : '-'} {adsYearData ? `${adsYearData.tacos.toFixed(1)}%` : '-'}
No data matches your filters.
{/* Pagination */}
Page {currentPage} of {totalPages || 1}
); }; export default DataGrid;