import React, { useMemo, useState } from 'react'; import { CombinedKPIs } from '../types'; interface AdsPerformanceProps { data: CombinedKPIs[]; } type SortKey = keyof CombinedKPIs | 'acos' | 'roas' | 'tacos' | 'ctr' | 'cpc' | 'cvrUnits'; const AdsPerformance: React.FC = ({ data }) => { const [searchTerm, setSearchTerm] = useState(''); const [sortConfig, setSortConfig] = useState<{ key: SortKey; direction: 'asc' | 'desc' }>({ key: 'cost', direction: 'desc' }); // 1. Aggregate data by ASIN const aggregatedByAsin = useMemo(() => { const map = new Map(); data.forEach(item => { const asin = item.asin.trim().toUpperCase(); if (!map.has(asin)) { map.set(asin, { ...item }); } else { const existing = map.get(asin)!; existing.salesTotal += item.salesTotal; existing.unitsTotal += item.unitsTotal; existing.salesAds += item.salesAds; existing.unitsAds += item.unitsAds; existing.cost += item.cost; existing.clicks += item.clicks; existing.impressions += item.impressions; existing.conversions += item.conversions; existing.salesOrganic += item.salesOrganic; } }); return Array.from(map.values()).map(item => { // Recalculate derived metrics for the aggregate const salesTotal = item.salesTotal; const adSales = item.salesAds; const adCost = item.cost; const adClicks = item.clicks; const adImpressions = item.impressions; const adConversions = item.conversions; return { ...item, acos: adSales > 0 ? (adCost / adSales) * 100 : 0, tacos: salesTotal > 0 ? (adCost / salesTotal) * 100 : 0, roas: adCost > 0 ? adSales / adCost : 0, ctr: adImpressions > 0 ? (adClicks / adImpressions) * 100 : 0, cpc: adClicks > 0 ? adCost / adClicks : 0, cvrUnits: adClicks > 0 ? (adConversions / adClicks) * 100 : 0, }; }); }, [data]); // 2. Global Totals for Summary Header const totals = useMemo(() => { return aggregatedByAsin.reduce((acc, curr) => ({ salesTotal: acc.salesTotal + curr.salesTotal, salesAds: acc.salesAds + curr.salesAds, salesOrganic: acc.salesOrganic + curr.salesOrganic, cost: acc.cost + curr.cost, impressions: acc.impressions + curr.impressions, clicks: acc.clicks + curr.clicks, conversions: acc.conversions + curr.conversions, }), { salesTotal: 0, salesAds: 0, salesOrganic: 0, cost: 0, impressions: 0, clicks: 0, conversions: 0, }); }, [aggregatedByAsin]); const globalMetrics = { acos: totals.salesAds > 0 ? (totals.cost / totals.salesAds) * 100 : 0, tacos: totals.salesTotal > 0 ? (totals.cost / totals.salesTotal) * 100 : 0, roas: totals.cost > 0 ? totals.salesAds / totals.cost : 0, ctr: totals.impressions > 0 ? (totals.clicks / totals.impressions) * 100 : 0, cpc: totals.clicks > 0 ? totals.cost / totals.clicks : 0, cvr: totals.clicks > 0 ? (totals.conversions / totals.clicks) * 100 : 0, }; // 3. Filter and Sort const filteredAndSorted = useMemo(() => { let result = aggregatedByAsin.filter(item => item.asin.toLowerCase().includes(searchTerm.toLowerCase()) || item.sku.toLowerCase().includes(searchTerm.toLowerCase()) || item.title.toLowerCase().includes(searchTerm.toLowerCase()) ); result.sort((a, b) => { const valA = a[sortConfig.key] as number; const valB = b[sortConfig.key] as number; return sortConfig.direction === 'asc' ? valA - valB : valB - valA; }); return result; }, [aggregatedByAsin, searchTerm, sortConfig]); const handleSort = (key: SortKey) => { setSortConfig(prev => ({ key, direction: prev.key === key && prev.direction === 'desc' ? 'asc' : 'desc' })); }; const formatCurrency = (val: number) => new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 0 }).format(val); const formatNumber = (val: number) => new Intl.NumberFormat('en-US').format(val); const formatPercent = (val: number) => `${val.toFixed(2)}%`; return (
{/* Header */}

Performance Overview

{aggregatedByAsin.length} records processed • All • All Years
{/* Summary Grid */}
{/* Table Section */}

Product Inventory

Displaying total accumulated data for selected filters.

setSearchTerm(e.target.value)} className="bg-[#0B0E14] border border-white/10 rounded-lg px-3 py-1.5 text-xs text-white focus:outline-none focus:ring-1 focus:ring-indigo-500 w-64" />
{filteredAndSorted.map((row) => ( ))}
PRODUCT
{row.asin} SKU: {row.sku} {row.title}
{formatNumber(row.salesTotal)} {formatNumber(row.salesAds)} {formatNumber(row.salesOrganic)} {formatNumber(row.cost)} {formatNumber(row.impressions)} {formatNumber(row.clicks)} {formatNumber(row.conversions)} ${row.cpc.toFixed(2)} {row.ctr.toFixed(2)}% {row.cvrUnits.toFixed(2)}% {row.acos.toFixed(2)}% {row.roas.toFixed(2)} {row.tacos.toFixed(2)}%
); }; const SummaryCard = ({ label, value, color = "text-white" }: { label: string; value: string; color?: string }) => (
{label} {value}
); const SortableHeader = ({ label, sortKey, activeSort, onSort }: { label: string; sortKey: SortKey; activeSort: { key: SortKey; direction: 'asc' | 'desc' }; onSort: (key: SortKey) => void; }) => { const isActive = activeSort.key === sortKey; return ( onSort(sortKey)} className={`p-4 font-black text-slate-400 uppercase tracking-widest text-right cursor-pointer hover:text-white transition-colors group min-w-[100px] whitespace-nowrap`} >
{label}
); }; export default AdsPerformance;