Files
CrazeAnalytix/components/VendorDataView.tsx
T

218 lines
8.9 KiB
TypeScript
Raw Normal View History

import React, { useMemo } from 'react';
2026-03-02 15:22:12 +01:00
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
import { BSRRecord } from '../types';
const MARKET_COLORS: Record<string, string> = {
DE: '#3b82f6',
UK: '#ef4444',
IT: '#22c55e',
FR: '#a855f7',
ES: '#f59e0b',
};
2026-03-02 15:22:12 +01:00
interface VendorDataViewProps {
bsrData: BSRRecord[];
}
interface ChartPoint {
label: string;
sortKey: string;
2026-03-02 15:22:12 +01:00
[key: string]: number | string | null;
}
const VendorDataView: React.FC<VendorDataViewProps> = ({ bsrData = [] }) => {
2026-03-02 15:22:12 +01:00
// Determine active markets in filtered data for series generation
const activeMarkets = useMemo(() => {
const m = new Set(bsrData.map(r => r.market));
2026-03-02 15:22:12 +01:00
return Array.from(m).sort();
}, [bsrData]);
// Determine if daily resolution is available (>= half the records have a date)
const useDailyResolution = useMemo(() => {
const withDate = bsrData.filter(r => r.date).length;
return withDate > bsrData.length / 2;
}, [bsrData]);
// Aggregate Data for Charts — daily if dates available, else weekly
2026-03-02 15:22:12 +01:00
const { topBsrChartData, detailBsrChartData, ratingChartData } = useMemo(() => {
// Group by date string (YYYY-MM-DD) or by week number
const byBucket = new Map<string, BSRRecord[]>();
bsrData.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);
2026-03-02 15:22:12 +01:00
});
const sortedKeys = Array.from(byBucket.keys()).sort();
2026-03-02 15:22:12 +01:00
const topBsrChartData: ChartPoint[] = [];
const detailBsrChartData: ChartPoint[] = [];
const ratingChartData: ChartPoint[] = [];
2026-03-02 15:22:12 +01:00
sortedKeys.forEach(key => {
const rows = byBucket.get(key)!;
2026-03-02 15:22:12 +01:00
// 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 };
2026-03-02 15:22:12 +01:00
activeMarkets.forEach(m => {
const marketRows = rows.filter(r => r.market === m);
2026-03-02 15:22:12 +01:00
// 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 };
}, [bsrData, activeMarkets, useDailyResolution]);
2026-03-02 15:22:12 +01:00
if (bsrData.length === 0) {
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>
<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.
</p>
</div>
);
}
const resolutionLabel = useDailyResolution ? 'Daily Avg' : 'Weekly Avg';
return (
<div className="space-y-6 p-4 pb-24 md:pb-4">
<div className="flex flex-wrap gap-3 items-center">
<span className="text-slate-500 text-xs ml-auto">{bsrData.length.toLocaleString()} records filtered</span>
</div>
2026-03-02 15:22:12 +01:00
{/* Top Level BSR Trend Chart */}
<div className="bg-slate-900/50 border border-slate-800 rounded-xl p-4">
<h3 className="text-lg font-bold text-slate-200 mb-4">Top Level BSR Trend ({resolutionLabel})</h3>
<p className="text-xs text-slate-500 mb-3">Lower rank = better position. Averaged across filtered ASINs per market.</p>
<ResponsiveContainer width="100%" height={350}>
2026-03-02 15:22:12 +01:00
<LineChart data={topBsrChartData}>
<CartesianGrid strokeDasharray="3 3" stroke="#334155" />
<XAxis dataKey="label" tick={{ fill: '#94a3b8', fontSize: 11 }} interval="preserveStartEnd" />
<YAxis reversed tick={{ fill: '#94a3b8', fontSize: 11 }} />
<Tooltip
contentStyle={{ backgroundColor: '#1e293b', border: '1px solid #334155', borderRadius: '8px' }}
labelStyle={{ color: '#e2e8f0' }}
/>
<Legend />
{activeMarkets.map(m => (
<Line
key={m}
type="monotone"
dataKey={`${m}_bsr`}
name={`${m} BSR`}
stroke={MARKET_COLORS[m] || '#6b7280'}
strokeWidth={2}
dot={false}
connectNulls
/>
))}
</LineChart>
</ResponsiveContainer>
</div>
2026-03-02 15:22:12 +01:00
{/* Detail Level BSR Trend Chart */}
<div className="bg-slate-900/50 border border-slate-800 rounded-xl p-4">
<h3 className="text-lg font-bold text-slate-200 mb-4">Detail Level BSR Trend ({resolutionLabel})</h3>
2026-03-02 15:22:12 +01:00
<p className="text-xs text-slate-500 mb-3">Lower rank = better position. Averaged across filtered ASINs per market.</p>
<ResponsiveContainer width="100%" height={350}>
2026-03-02 15:22:12 +01:00
<LineChart data={detailBsrChartData}>
<CartesianGrid strokeDasharray="3 3" stroke="#334155" />
<XAxis dataKey="label" tick={{ fill: '#94a3b8', fontSize: 11 }} interval="preserveStartEnd" />
2026-03-02 15:22:12 +01:00
<YAxis reversed tick={{ fill: '#94a3b8', fontSize: 11 }} />
<Tooltip
contentStyle={{ backgroundColor: '#1e293b', border: '1px solid #334155', borderRadius: '8px' }}
labelStyle={{ color: '#e2e8f0' }}
/>
<Legend />
{activeMarkets.map(m => (
2026-03-02 15:22:12 +01:00
<Line
key={m}
2026-03-02 15:22:12 +01:00
type="monotone"
dataKey={`${m}_bsr`}
name={`${m} BSR`}
stroke={MARKET_COLORS[m] || '#6b7280'}
strokeWidth={2}
dot={false}
connectNulls
/>
))}
2026-03-02 15:22:12 +01:00
</LineChart>
</ResponsiveContainer>
</div>
{/* Average Rating Chart */}
<div className="bg-slate-900/50 border border-slate-800 rounded-xl p-4">
<h3 className="text-lg font-bold text-slate-200 mb-4">Average Rating ({resolutionLabel})</h3>
2026-03-02 15:22:12 +01:00
<p className="text-xs text-slate-500 mb-3">Averaged across filtered ASINs per market.</p>
<ResponsiveContainer width="100%" height={350}>
<LineChart data={ratingChartData}>
<CartesianGrid strokeDasharray="3 3" stroke="#334155" />
<XAxis dataKey="label" tick={{ fill: '#94a3b8', fontSize: 11 }} interval="preserveStartEnd" />
2026-03-02 15:22:12 +01:00
<YAxis domain={[0, 5]} tick={{ fill: '#94a3b8', fontSize: 11 }} />
<Tooltip
contentStyle={{ backgroundColor: '#1e293b', border: '1px solid #334155', borderRadius: '8px' }}
labelStyle={{ color: '#e2e8f0' }}
/>
<Legend />
{activeMarkets.map(m => (
<Line
key={m}
type="monotone"
dataKey={`${m}_rating`}
name={`${m} Rating`}
stroke={MARKET_COLORS[m] || '#6b7280'}
strokeWidth={2}
dot={false}
connectNulls
/>
))}
</LineChart>
</ResponsiveContainer>
</div>
</div>
);
};
export default VendorDataView;