mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 13:05:24 +02:00
feat(vendor): switch BSR charts to daily resolution for smoother trend lines
- Add date? field to BSRRecord (ISO string YYYY-MM-DD) - processBSRExcel now extracts and stores the Date column as an ISO date (handles both Excel serial numbers and string dates) - VendorDataView groups chart data by date (daily) when dates are available, falling back to weekly buckets — eliminates straight-line charts caused by only 2 weekly data points Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
702bb021f8
commit
7f5aa1b812
@@ -15,8 +15,9 @@ interface VendorDataViewProps {
|
||||
bsrData: BSRRecord[];
|
||||
}
|
||||
|
||||
interface WeeklyChartPoint {
|
||||
weekLabel: string;
|
||||
interface ChartPoint {
|
||||
label: string;
|
||||
sortKey: string;
|
||||
[key: string]: number | string | null;
|
||||
}
|
||||
|
||||
@@ -73,30 +74,48 @@ const VendorDataView: React.FC<VendorDataViewProps> = ({ bsrData }) => {
|
||||
return Array.from(m).sort();
|
||||
}, [filteredData]);
|
||||
|
||||
// Aggregate Data for Charts
|
||||
// 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(() => {
|
||||
const byWeek = new Map<number, BSRRecord[]>();
|
||||
// Group by date string (YYYY-MM-DD) or by week number
|
||||
const byBucket = new Map<string, BSRRecord[]>();
|
||||
|
||||
filteredData.forEach(r => {
|
||||
if (!byWeek.has(r.week)) byWeek.set(r.week, []);
|
||||
byWeek.get(r.week)!.push(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 sortedWeeks = Array.from(byWeek.keys()).sort((a, b) => a - b);
|
||||
const sortedKeys = Array.from(byBucket.keys()).sort();
|
||||
|
||||
const topBsrChartData: WeeklyChartPoint[] = [];
|
||||
const detailBsrChartData: WeeklyChartPoint[] = [];
|
||||
const ratingChartData: WeeklyChartPoint[] = [];
|
||||
const topBsrChartData: ChartPoint[] = [];
|
||||
const detailBsrChartData: ChartPoint[] = [];
|
||||
const ratingChartData: ChartPoint[] = [];
|
||||
|
||||
sortedWeeks.forEach(week => {
|
||||
const row = byWeek.get(week)!;
|
||||
const weekLabel = `Week ${week}`;
|
||||
sortedKeys.forEach(key => {
|
||||
const rows = byBucket.get(key)!;
|
||||
|
||||
const topPoint: WeeklyChartPoint = { weekLabel };
|
||||
const detailPoint: WeeklyChartPoint = { weekLabel };
|
||||
const ratingPoint: WeeklyChartPoint = { weekLabel };
|
||||
// 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 = row.filter(r => r.market === m);
|
||||
const marketRows = rows.filter(r => r.market === m);
|
||||
|
||||
// Top BSR
|
||||
const topBsrRows = marketRows.filter(r => r.topLevelBSR != null);
|
||||
@@ -123,7 +142,7 @@ const VendorDataView: React.FC<VendorDataViewProps> = ({ bsrData }) => {
|
||||
});
|
||||
|
||||
return { topBsrChartData, detailBsrChartData, ratingChartData };
|
||||
}, [filteredData, activeMarkets]);
|
||||
}, [filteredData, activeMarkets, useDailyResolution]);
|
||||
|
||||
if (bsrData.length === 0) {
|
||||
return (
|
||||
@@ -141,6 +160,8 @@ const VendorDataView: React.FC<VendorDataViewProps> = ({ bsrData }) => {
|
||||
);
|
||||
}
|
||||
|
||||
const resolutionLabel = useDailyResolution ? 'Daily Avg' : 'Weekly Avg';
|
||||
|
||||
return (
|
||||
<div className="space-y-6 p-4 pb-24 md:pb-4">
|
||||
{/* Filters */}
|
||||
@@ -188,12 +209,12 @@ const VendorDataView: React.FC<VendorDataViewProps> = ({ bsrData }) => {
|
||||
|
||||
{/* 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 (Weekly Avg)</h3>
|
||||
<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}>
|
||||
<LineChart data={topBsrChartData}>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#334155" />
|
||||
<XAxis dataKey="weekLabel" tick={{ fill: '#94a3b8', fontSize: 11 }} interval="preserveStartEnd" />
|
||||
<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' }}
|
||||
@@ -218,12 +239,12 @@ const VendorDataView: React.FC<VendorDataViewProps> = ({ bsrData }) => {
|
||||
|
||||
{/* 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 (Weekly Avg)</h3>
|
||||
<h3 className="text-lg font-bold text-slate-200 mb-4">Detail 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}>
|
||||
<LineChart data={detailBsrChartData}>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#334155" />
|
||||
<XAxis dataKey="weekLabel" tick={{ fill: '#94a3b8', fontSize: 11 }} interval="preserveStartEnd" />
|
||||
<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' }}
|
||||
@@ -248,12 +269,12 @@ const VendorDataView: React.FC<VendorDataViewProps> = ({ bsrData }) => {
|
||||
|
||||
{/* 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 (Weekly Avg)</h3>
|
||||
<h3 className="text-lg font-bold text-slate-200 mb-4">Average Rating ({resolutionLabel})</h3>
|
||||
<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="weekLabel" tick={{ fill: '#94a3b8', fontSize: 11 }} interval="preserveStartEnd" />
|
||||
<XAxis dataKey="label" tick={{ fill: '#94a3b8', fontSize: 11 }} interval="preserveStartEnd" />
|
||||
<YAxis domain={[0, 5]} tick={{ fill: '#94a3b8', fontSize: 11 }} />
|
||||
<Tooltip
|
||||
contentStyle={{ backgroundColor: '#1e293b', border: '1px solid #334155', borderRadius: '8px' }}
|
||||
|
||||
Reference in New Issue
Block a user