2026-03-03 17:15:02 +01:00
|
|
|
import React, { useMemo } from 'react';
|
|
|
|
|
import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
|
2026-03-02 15:22:12 +01:00
|
|
|
import { BSRRecord } from '../types';
|
2026-03-03 11:21:14 +01:00
|
|
|
import { BuyBoxWarningBadge } from './BuyBoxWarningBadge';
|
2026-02-20 13:19:25 +01:00
|
|
|
|
2026-03-02 15:22:12 +01:00
|
|
|
interface VendorDataViewProps {
|
|
|
|
|
bsrData: BSRRecord[];
|
2026-03-03 11:21:14 +01:00
|
|
|
asinMetadata?: Map<string, { sku: string; title: string; line: string }>;
|
|
|
|
|
buyBoxLostMap?: Map<string, { countries: string[]; reasons: Record<string, string> }>;
|
2026-03-02 15:22:12 +01:00
|
|
|
}
|
|
|
|
|
|
2026-03-02 18:53:45 +01:00
|
|
|
interface ChartPoint {
|
|
|
|
|
label: string;
|
|
|
|
|
sortKey: string;
|
2026-03-02 15:22:12 +01:00
|
|
|
[key: string]: number | string | null;
|
2026-02-20 13:19:25 +01:00
|
|
|
}
|
|
|
|
|
|
2026-03-03 11:21:14 +01:00
|
|
|
const VendorDataView: React.FC<VendorDataViewProps> = ({ bsrData = [], asinMetadata, buyBoxLostMap }) => {
|
2026-02-20 13:19:25 +01:00
|
|
|
const activeMarkets = useMemo(() => {
|
2026-03-03 10:27:12 +01:00
|
|
|
const m = new Set(bsrData.map(r => r.market));
|
2026-03-02 15:22:12 +01:00
|
|
|
return Array.from(m).sort();
|
2026-03-03 10:27:12 +01:00
|
|
|
}, [bsrData]);
|
2026-02-20 13:19:25 +01:00
|
|
|
|
2026-03-02 18:53:45 +01:00
|
|
|
const useDailyResolution = useMemo(() => {
|
2026-03-03 10:27:12 +01:00
|
|
|
const withDate = bsrData.filter(r => r.date).length;
|
|
|
|
|
return withDate > bsrData.length / 2;
|
|
|
|
|
}, [bsrData]);
|
2026-03-02 18:53:45 +01:00
|
|
|
|
2026-03-03 11:21:14 +01:00
|
|
|
const uniqueAsins = useMemo(() => {
|
|
|
|
|
const asinSet = new Set(bsrData.map(r => r.asin.trim().toUpperCase()));
|
|
|
|
|
return Array.from(asinSet);
|
|
|
|
|
}, [bsrData]);
|
|
|
|
|
|
|
|
|
|
const productInfo = useMemo(() => {
|
|
|
|
|
if (uniqueAsins.length !== 1) return null;
|
|
|
|
|
const asin = uniqueAsins[0];
|
|
|
|
|
const metadata = asinMetadata?.get(asin);
|
|
|
|
|
if (!metadata) return null;
|
|
|
|
|
return {
|
|
|
|
|
asin,
|
|
|
|
|
sku: metadata.sku,
|
|
|
|
|
title: metadata.title,
|
|
|
|
|
line: metadata.line,
|
|
|
|
|
};
|
|
|
|
|
}, [uniqueAsins, asinMetadata]);
|
|
|
|
|
|
2026-03-02 15:22:12 +01:00
|
|
|
const { topBsrChartData, detailBsrChartData, ratingChartData } = useMemo(() => {
|
2026-03-02 18:53:45 +01:00
|
|
|
const byBucket = new Map<string, BSRRecord[]>();
|
|
|
|
|
|
2026-03-03 10:27:12 +01:00
|
|
|
bsrData.forEach(r => {
|
2026-03-02 18:53:45 +01:00
|
|
|
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);
|
2026-03-02 15:22:12 +01:00
|
|
|
});
|
2026-02-20 13:19:25 +01:00
|
|
|
|
2026-03-02 18:53:45 +01:00
|
|
|
const sortedKeys = Array.from(byBucket.keys()).sort();
|
2026-03-02 15:22:12 +01:00
|
|
|
|
2026-03-02 18:53:45 +01:00
|
|
|
const topBsrChartData: ChartPoint[] = [];
|
|
|
|
|
const detailBsrChartData: ChartPoint[] = [];
|
|
|
|
|
const ratingChartData: ChartPoint[] = [];
|
2026-03-02 15:22:12 +01:00
|
|
|
|
2026-03-02 18:53:45 +01:00
|
|
|
sortedKeys.forEach(key => {
|
|
|
|
|
const rows = byBucket.get(key)!;
|
2026-03-02 15:22:12 +01:00
|
|
|
|
2026-03-02 18:53:45 +01:00
|
|
|
let label: string;
|
|
|
|
|
if (useDailyResolution && key.includes('-')) {
|
|
|
|
|
const d = new Date(key + 'T12:00:00Z');
|
|
|
|
|
label = d.toLocaleDateString('en-GB', { day: '2-digit', month: 'short' });
|
|
|
|
|
} else {
|
2026-03-03 17:15:02 +01:00
|
|
|
label = key;
|
2026-03-02 18:53:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const topPoint: ChartPoint = { label, sortKey: key };
|
|
|
|
|
const detailPoint: ChartPoint = { label, sortKey: key };
|
|
|
|
|
const ratingPoint: ChartPoint = { label, sortKey: key };
|
2026-03-02 15:22:12 +01:00
|
|
|
|
|
|
|
|
activeMarkets.forEach(m => {
|
2026-03-02 18:53:45 +01:00
|
|
|
const marketRows = rows.filter(r => r.market === m);
|
2026-03-02 15:22:12 +01:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
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 };
|
2026-03-03 10:27:12 +01:00
|
|
|
}, [bsrData, activeMarkets, useDailyResolution]);
|
2026-03-02 15:22:12 +01:00
|
|
|
|
|
|
|
|
if (bsrData.length === 0) {
|
2026-02-20 13:19:25 +01:00
|
|
|
return (
|
|
|
|
|
<div className="flex flex-col items-center justify-center h-[60vh] gap-4 text-center px-4">
|
|
|
|
|
<div className="p-4 bg-emerald-500/10 rounded-full text-emerald-400">
|
|
|
|
|
<svg className="w-12 h-12" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor">
|
|
|
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 0 1 3 19.875v-6.75ZM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V8.625ZM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V4.125Z" />
|
|
|
|
|
</svg>
|
|
|
|
|
</div>
|
2026-03-02 15:22:12 +01:00
|
|
|
<h2 className="text-xl font-bold text-slate-200">No BSR Data Yet</h2>
|
2026-02-20 13:19:25 +01:00
|
|
|
<p className="text-sm text-slate-400 max-w-md">
|
2026-03-02 15:22:12 +01:00
|
|
|
Ensure BSR.xlsx is present and loading to see Top Level BSR, Detail Level BSR, and Average Rating trends.
|
2026-02-20 13:19:25 +01:00
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-04 08:29:20 +01:00
|
|
|
// Exact color values based on the screenshot
|
|
|
|
|
const cardBg = '#161b24';
|
|
|
|
|
const orangeHex = '#f37526';
|
|
|
|
|
const cyanHex = '#2acbd6';
|
|
|
|
|
const purpleHex = '#7950f2';
|
|
|
|
|
const gridLineColor = '#ffffff';
|
2026-02-20 13:19:25 +01:00
|
|
|
|
2026-03-04 08:29:20 +01:00
|
|
|
return (
|
|
|
|
|
<div className="p-4 md:p-6 lg:px-8 pb-24 max-w-[800px] mx-auto space-y-6">
|
|
|
|
|
|
|
|
|
|
{/* Product Details Header Card */}
|
2026-03-03 11:21:14 +01:00
|
|
|
{productInfo && (
|
2026-03-04 08:29:20 +01:00
|
|
|
<div className="bg-[#161b24] border border-[#2a3040] rounded-2xl p-6 shadow-md relative">
|
2026-03-03 17:15:02 +01:00
|
|
|
<div className="flex items-start justify-between gap-4">
|
2026-03-04 08:29:20 +01:00
|
|
|
<div className="flex flex-col gap-3 flex-1">
|
2026-03-03 17:15:02 +01:00
|
|
|
<div>
|
2026-03-04 08:29:20 +01:00
|
|
|
<span className="inline-block bg-[#1f2b4a] text-[#4f86f7] text-[10px] font-bold uppercase tracking-wider px-2.5 py-1 rounded-[4px] mb-3">
|
|
|
|
|
PRODUCT DETAILS
|
2026-03-03 11:21:14 +01:00
|
|
|
</span>
|
2026-03-04 08:29:20 +01:00
|
|
|
<h2 className="text-xl md:text-2xl font-bold text-white mb-1">
|
2026-03-03 17:15:02 +01:00
|
|
|
{productInfo.title}
|
2026-03-04 08:29:20 +01:00
|
|
|
</h2>
|
2026-03-03 11:21:14 +01:00
|
|
|
</div>
|
2026-03-03 17:15:02 +01:00
|
|
|
|
2026-03-04 08:29:20 +01:00
|
|
|
<div className="flex items-center gap-6 mt-1">
|
|
|
|
|
<div className="text-[13px] md:text-sm">
|
|
|
|
|
<span className="text-[#64748b] font-semibold mr-2">SKU:</span>
|
|
|
|
|
<span className="text-[#e2e8f0] font-medium">{productInfo.sku}</span>
|
2026-03-03 11:21:14 +01:00
|
|
|
</div>
|
2026-03-04 08:29:20 +01:00
|
|
|
<div className="text-[13px] md:text-sm">
|
|
|
|
|
<span className="text-[#64748b] font-semibold mr-2">ASIN:</span>
|
|
|
|
|
<span className="text-[#4f86f7] font-medium tracking-wide">{productInfo.asin}</span>
|
2026-03-03 11:21:14 +01:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-03-03 17:15:02 +01:00
|
|
|
<div className="flex-shrink-0 pt-1">
|
2026-03-03 11:21:14 +01:00
|
|
|
<BuyBoxWarningBadge asin={productInfo.asin} buyBoxLostMap={buyBoxLostMap} />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
2026-03-02 15:22:12 +01:00
|
|
|
{/* Top Level BSR Trend Chart */}
|
2026-03-04 08:29:20 +01:00
|
|
|
<div className="bg-[#161b24] border border-[#2a3040] rounded-2xl p-6 shadow-md flex flex-col">
|
|
|
|
|
<div className="mb-4">
|
|
|
|
|
<h3 className="text-[17px] font-semibold text-white tracking-wide mb-1">Top Level BSR Trend</h3>
|
|
|
|
|
<p className="text-[13px] text-[#64748b]">Weekly Average Rank • Lower is better</p>
|
2026-03-03 17:15:02 +01:00
|
|
|
</div>
|
2026-03-04 08:29:20 +01:00
|
|
|
|
|
|
|
|
<div className="h-[220px] w-full mt-2">
|
2026-03-03 17:15:02 +01:00
|
|
|
<ResponsiveContainer width="100%" height="100%">
|
2026-03-04 08:29:20 +01:00
|
|
|
<AreaChart data={topBsrChartData} margin={{ top: 20, right: 10, left: 10, bottom: 0 }}>
|
2026-03-03 17:15:02 +01:00
|
|
|
<defs>
|
2026-03-04 08:29:20 +01:00
|
|
|
<linearGradient id="colorOrange" x1="0" y1="0" x2="0" y2="1">
|
|
|
|
|
<stop offset="5%" stopColor={orangeHex} stopOpacity={0.25} />
|
|
|
|
|
<stop offset="95%" stopColor={orangeHex} stopOpacity={0} />
|
2026-03-03 17:15:02 +01:00
|
|
|
</linearGradient>
|
|
|
|
|
</defs>
|
2026-03-04 08:29:20 +01:00
|
|
|
<CartesianGrid strokeDasharray="0" stroke={gridLineColor} strokeOpacity={0.03} vertical={false} />
|
|
|
|
|
<XAxis dataKey="label" hide />
|
|
|
|
|
<YAxis reversed domain={['auto', 'auto']} hide />
|
2026-03-03 17:15:02 +01:00
|
|
|
<Tooltip
|
2026-03-04 08:29:20 +01:00
|
|
|
contentStyle={{ backgroundColor: '#111827', border: '1px solid #374151', borderRadius: '8px', color: '#fff' }}
|
|
|
|
|
itemStyle={{ fontSize: '12px' }}
|
|
|
|
|
labelStyle={{ color: '#9ca3af', fontSize: '11px', marginBottom: '4px' }}
|
|
|
|
|
cursor={{ stroke: '#374151', strokeWidth: 1, strokeDasharray: '3 3' }}
|
2026-03-03 17:15:02 +01:00
|
|
|
/>
|
2026-03-04 08:29:20 +01:00
|
|
|
{activeMarkets.map((m, i) => (
|
2026-03-03 17:15:02 +01:00
|
|
|
<Area
|
|
|
|
|
key={m}
|
|
|
|
|
type="monotone"
|
|
|
|
|
dataKey={`${m}_bsr`}
|
|
|
|
|
name={`${m} BSR`}
|
2026-03-04 08:29:20 +01:00
|
|
|
stroke={orangeHex}
|
|
|
|
|
strokeWidth={2.5}
|
2026-03-03 17:15:02 +01:00
|
|
|
fillOpacity={1}
|
2026-03-04 08:29:20 +01:00
|
|
|
fill={i === 0 ? "url(#colorOrange)" : "transparent"}
|
|
|
|
|
activeDot={{ r: 6, strokeWidth: 0, fill: orangeHex }}
|
|
|
|
|
dot={{ r: 4, fill: cardBg, strokeWidth: 2, stroke: orangeHex }}
|
2026-03-03 17:15:02 +01:00
|
|
|
/>
|
|
|
|
|
))}
|
|
|
|
|
</AreaChart>
|
|
|
|
|
</ResponsiveContainer>
|
|
|
|
|
</div>
|
2026-03-04 08:29:20 +01:00
|
|
|
|
|
|
|
|
{/* Exact Legend matching screenshot */}
|
|
|
|
|
<div className="mt-6 flex justify-center items-center gap-2">
|
|
|
|
|
{activeMarkets.length > 0 && (
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<div className="w-2.5 h-2.5 rounded-full" style={{ backgroundColor: orangeHex }}></div>
|
|
|
|
|
<span className="text-[12px] text-[#8b9bb4]">{activeMarkets[0]} BSR</span>
|
2026-03-03 17:15:02 +01:00
|
|
|
</div>
|
2026-03-04 08:29:20 +01:00
|
|
|
)}
|
2026-03-03 17:15:02 +01:00
|
|
|
</div>
|
2026-02-20 13:19:25 +01:00
|
|
|
</div>
|
|
|
|
|
|
2026-03-02 15:22:12 +01:00
|
|
|
{/* Detail Level BSR Trend Chart */}
|
2026-03-04 08:29:20 +01:00
|
|
|
<div className="bg-[#161b24] border border-[#2a3040] rounded-2xl p-6 shadow-md flex flex-col">
|
|
|
|
|
<div className="mb-4">
|
|
|
|
|
<h3 className="text-[17px] font-semibold text-white tracking-wide mb-1">Detail Level BSR Trend</h3>
|
|
|
|
|
<p className="text-[13px] text-[#64748b]">Sub-category Performance • Filtered ASINs</p>
|
2026-03-03 17:15:02 +01:00
|
|
|
</div>
|
2026-03-04 08:29:20 +01:00
|
|
|
|
|
|
|
|
<div className="h-[220px] w-full mt-2">
|
2026-03-03 17:15:02 +01:00
|
|
|
<ResponsiveContainer width="100%" height="100%">
|
2026-03-04 08:29:20 +01:00
|
|
|
<AreaChart data={detailBsrChartData} margin={{ top: 20, right: 10, left: 10, bottom: 0 }}>
|
2026-03-03 17:15:02 +01:00
|
|
|
<defs>
|
2026-03-04 08:29:20 +01:00
|
|
|
<linearGradient id="colorCyan" x1="0" y1="0" x2="0" y2="1">
|
|
|
|
|
<stop offset="5%" stopColor={cyanHex} stopOpacity={0.25} />
|
|
|
|
|
<stop offset="95%" stopColor={cyanHex} stopOpacity={0} />
|
|
|
|
|
</linearGradient>
|
2026-03-03 17:15:02 +01:00
|
|
|
</defs>
|
2026-03-04 08:29:20 +01:00
|
|
|
<CartesianGrid strokeDasharray="0" stroke={gridLineColor} strokeOpacity={0.03} vertical={false} />
|
|
|
|
|
<XAxis dataKey="label" hide />
|
|
|
|
|
<YAxis reversed domain={['auto', 'auto']} hide />
|
2026-03-03 17:15:02 +01:00
|
|
|
<Tooltip
|
2026-03-04 08:29:20 +01:00
|
|
|
contentStyle={{ backgroundColor: '#111827', border: '1px solid #374151', borderRadius: '8px', color: '#fff' }}
|
|
|
|
|
itemStyle={{ fontSize: '12px' }}
|
|
|
|
|
labelStyle={{ color: '#9ca3af', fontSize: '11px', marginBottom: '4px' }}
|
|
|
|
|
cursor={{ stroke: '#374151', strokeWidth: 1, strokeDasharray: '3 3' }}
|
2026-03-03 17:15:02 +01:00
|
|
|
/>
|
2026-03-04 08:29:20 +01:00
|
|
|
{activeMarkets.map((m, i) => (
|
2026-03-03 17:15:02 +01:00
|
|
|
<Area
|
|
|
|
|
key={m}
|
|
|
|
|
type="monotone"
|
|
|
|
|
dataKey={`${m}_bsr`}
|
2026-03-04 08:29:20 +01:00
|
|
|
name="Category Rank"
|
|
|
|
|
stroke={cyanHex}
|
|
|
|
|
strokeWidth={2.5}
|
2026-03-03 17:15:02 +01:00
|
|
|
fillOpacity={1}
|
2026-03-04 08:29:20 +01:00
|
|
|
fill={i === 0 ? "url(#colorCyan)" : "transparent"}
|
|
|
|
|
activeDot={{ r: 6, strokeWidth: 0, fill: cyanHex }}
|
|
|
|
|
dot={{ r: 4, fill: cardBg, strokeWidth: 2, stroke: cyanHex }}
|
2026-03-03 17:15:02 +01:00
|
|
|
/>
|
|
|
|
|
))}
|
|
|
|
|
</AreaChart>
|
|
|
|
|
</ResponsiveContainer>
|
|
|
|
|
</div>
|
2026-03-04 08:29:20 +01:00
|
|
|
|
|
|
|
|
{/* Exact Legend matching screenshot */}
|
|
|
|
|
<div className="mt-6 flex justify-center items-center gap-2">
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<div className="w-2.5 h-2.5 rounded-full" style={{ backgroundColor: cyanHex }}></div>
|
|
|
|
|
<span className="text-[12px] text-[#8b9bb4]">Category Rank</span>
|
|
|
|
|
</div>
|
2026-03-03 17:15:02 +01:00
|
|
|
</div>
|
2026-03-02 15:22:12 +01:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Average Rating Chart */}
|
2026-03-04 08:29:20 +01:00
|
|
|
<div className="bg-[#161b24] border border-[#2a3040] rounded-2xl p-6 shadow-md flex flex-col">
|
|
|
|
|
<div className="mb-4">
|
|
|
|
|
<h3 className="text-[17px] font-semibold text-white tracking-wide mb-1">Average Rating</h3>
|
|
|
|
|
<p className="text-[13px] text-[#64748b]">Weekly Average (1.0 - 5.0)</p>
|
2026-03-03 17:15:02 +01:00
|
|
|
</div>
|
2026-03-04 08:29:20 +01:00
|
|
|
|
|
|
|
|
<div className="h-[220px] w-full mt-2">
|
2026-03-03 17:15:02 +01:00
|
|
|
<ResponsiveContainer width="100%" height="100%">
|
2026-03-04 08:29:20 +01:00
|
|
|
<AreaChart data={ratingChartData} margin={{ top: 20, right: 10, left: 10, bottom: 0 }}>
|
2026-03-03 17:15:02 +01:00
|
|
|
<defs>
|
2026-03-04 08:29:20 +01:00
|
|
|
<linearGradient id="colorPurple" x1="0" y1="0" x2="0" y2="1">
|
|
|
|
|
<stop offset="5%" stopColor={purpleHex} stopOpacity={0.25} />
|
|
|
|
|
<stop offset="95%" stopColor={purpleHex} stopOpacity={0} />
|
|
|
|
|
</linearGradient>
|
2026-03-03 17:15:02 +01:00
|
|
|
</defs>
|
2026-03-04 08:29:20 +01:00
|
|
|
<CartesianGrid strokeDasharray="0" stroke={gridLineColor} strokeOpacity={0.03} vertical={false} />
|
|
|
|
|
<XAxis dataKey="label" hide />
|
|
|
|
|
<YAxis domain={[0, 5]} hide />
|
2026-03-03 17:15:02 +01:00
|
|
|
<Tooltip
|
2026-03-04 08:29:20 +01:00
|
|
|
contentStyle={{ backgroundColor: '#111827', border: '1px solid #374151', borderRadius: '8px', color: '#fff' }}
|
|
|
|
|
itemStyle={{ fontSize: '12px' }}
|
|
|
|
|
labelStyle={{ color: '#9ca3af', fontSize: '11px', marginBottom: '4px' }}
|
|
|
|
|
cursor={{ stroke: '#374151', strokeWidth: 1, strokeDasharray: '3 3' }}
|
2026-03-03 17:15:02 +01:00
|
|
|
/>
|
2026-03-04 08:29:20 +01:00
|
|
|
{activeMarkets.map((m, i) => (
|
2026-03-03 17:15:02 +01:00
|
|
|
<Area
|
|
|
|
|
key={m}
|
|
|
|
|
type="monotone"
|
|
|
|
|
dataKey={`${m}_rating`}
|
2026-03-04 08:29:20 +01:00
|
|
|
name="Star Rating"
|
|
|
|
|
stroke={purpleHex}
|
|
|
|
|
strokeWidth={2.5}
|
2026-03-03 17:15:02 +01:00
|
|
|
fillOpacity={1}
|
2026-03-04 08:29:20 +01:00
|
|
|
fill={i === 0 ? "url(#colorPurple)" : "transparent"}
|
|
|
|
|
activeDot={{ r: 6, strokeWidth: 0, fill: purpleHex }}
|
|
|
|
|
dot={{ r: 4, fill: cardBg, strokeWidth: 2, stroke: purpleHex }}
|
2026-03-03 17:15:02 +01:00
|
|
|
/>
|
|
|
|
|
))}
|
|
|
|
|
</AreaChart>
|
|
|
|
|
</ResponsiveContainer>
|
|
|
|
|
</div>
|
2026-03-04 08:29:20 +01:00
|
|
|
|
|
|
|
|
{/* Exact Legend matching screenshot */}
|
|
|
|
|
<div className="mt-6 flex justify-center items-center gap-2">
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<div className="w-2.5 h-2.5 rounded-full" style={{ backgroundColor: purpleHex }}></div>
|
|
|
|
|
<span className="text-[12px] text-[#8b9bb4]">Star Rating</span>
|
|
|
|
|
</div>
|
2026-03-03 17:15:02 +01:00
|
|
|
</div>
|
2026-02-20 13:19:25 +01:00
|
|
|
</div>
|
2026-03-04 08:29:20 +01:00
|
|
|
|
2026-02-20 13:19:25 +01:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default VendorDataView;
|