mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 12:25:22 +02:00
Fix Ads data mismatch, optimize Forecast performance, and add Excel-style in-column stock filtering
This commit is contained in:
+41
-30
@@ -3,6 +3,7 @@ import * as XLSX from 'xlsx';
|
||||
import { ProductForecastData, FilterState } from '../types';
|
||||
import { DownloadIcon } from './Icons';
|
||||
import { StockBadge } from './StockBadge';
|
||||
import { InColumnStockFilter } from './InColumnStockFilter';
|
||||
import {
|
||||
BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer,
|
||||
LineChart, Line, Legend, ComposedChart, Area
|
||||
@@ -16,6 +17,8 @@ interface ForecastViewProps {
|
||||
uk: Map<string, number>;
|
||||
};
|
||||
stockMap?: Map<string, number>;
|
||||
stockFilter: string[];
|
||||
onStockFilterChange: (newFilters: string[]) => void;
|
||||
}
|
||||
|
||||
const MONTH_ORDER = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||
@@ -31,7 +34,7 @@ const Top50Badge: React.FC<{ rank: number; label: string; theme?: 'amber' | 'ind
|
||||
);
|
||||
};
|
||||
|
||||
const ForecastView: React.FC<ForecastViewProps> = ({ data, filters, top50Ranking, stockMap }) => {
|
||||
const ForecastView: React.FC<ForecastViewProps> = ({ data, filters, top50Ranking, stockMap, stockFilter, onStockFilterChange }) => {
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [showOnlyTop50, setShowOnlyTop50] = useState(false);
|
||||
const [top50Mode, setTop50Mode] = useState<'eu' | 'uk'>('eu');
|
||||
@@ -88,35 +91,31 @@ const ForecastView: React.FC<ForecastViewProps> = ({ data, filters, top50Ranking
|
||||
return result;
|
||||
}, [data, filters.line, filters.asin, filters.sku, filters.title]);
|
||||
|
||||
// Global Aggregate Data (respects filters)
|
||||
const globalMonthlyData = useMemo(() => {
|
||||
return MONTH_ORDER.map(m => {
|
||||
let forecast = 0;
|
||||
let actual = 0;
|
||||
baseFilteredData.forEach(p => {
|
||||
const monthPoint = p.monthlyData.find(md => md.month === m);
|
||||
if (monthPoint) {
|
||||
forecast += monthPoint.forecastUnits;
|
||||
actual += monthPoint.actualUnits;
|
||||
}
|
||||
});
|
||||
return { name: m, Forecast: forecast, Actual: actual };
|
||||
});
|
||||
}, [baseFilteredData]);
|
||||
|
||||
const globalSummary = useMemo(() => {
|
||||
// Global Aggregate Data (respects filters) - Single Pass Optimization
|
||||
const { globalMonthlyData, globalSummary } = useMemo(() => {
|
||||
const monthlyStats = MONTH_ORDER.map(m => ({ name: m, Forecast: 0, Actual: 0 }));
|
||||
let periodForecast = 0;
|
||||
let periodActual = 0;
|
||||
let annualForecast = 0;
|
||||
let annualActual = 0;
|
||||
|
||||
baseFilteredData.forEach(p => {
|
||||
annualForecast += p.annualForecast;
|
||||
p.monthlyData.forEach(md => {
|
||||
annualActual += md.actualUnits;
|
||||
annualForecast += (p.annualForecast || 0);
|
||||
p.monthlyData.forEach((md, idx) => {
|
||||
const units = (md.actualUnits || 0);
|
||||
const fc = (md.forecastUnits || 0);
|
||||
|
||||
annualActual += units;
|
||||
|
||||
// Update monthly stats (assuming MONTH_ORDER matches p.monthlyData order)
|
||||
if (monthlyStats[idx]) {
|
||||
monthlyStats[idx].Forecast += fc;
|
||||
monthlyStats[idx].Actual += units;
|
||||
}
|
||||
|
||||
if (activeMonths.includes(md.month)) {
|
||||
periodForecast += md.forecastUnits;
|
||||
periodActual += md.actualUnits;
|
||||
periodForecast += fc;
|
||||
periodActual += units;
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -125,15 +124,19 @@ const ForecastView: React.FC<ForecastViewProps> = ({ data, filters, top50Ranking
|
||||
const annualFulfillment = annualForecast > 0 ? (annualActual / annualForecast) * 100 : 0;
|
||||
|
||||
return {
|
||||
periodForecast,
|
||||
periodActual,
|
||||
periodFulfillment,
|
||||
annualForecast,
|
||||
annualActual,
|
||||
annualFulfillment
|
||||
globalMonthlyData: monthlyStats,
|
||||
globalSummary: {
|
||||
periodForecast,
|
||||
periodActual,
|
||||
periodFulfillment,
|
||||
annualForecast,
|
||||
annualActual,
|
||||
annualFulfillment
|
||||
}
|
||||
};
|
||||
}, [baseFilteredData, activeMonths]);
|
||||
|
||||
|
||||
const filteredProducts = useMemo(() => {
|
||||
let result = baseFilteredData;
|
||||
|
||||
@@ -357,7 +360,15 @@ const ForecastView: React.FC<ForecastViewProps> = ({ data, filters, top50Ranking
|
||||
<table className="w-full text-left text-sm whitespace-nowrap">
|
||||
<thead className="bg-slate-950 text-slate-500 uppercase text-[10px] font-black tracking-widest border-b border-border">
|
||||
<tr>
|
||||
<th className="px-6 py-4">Product Info</th>
|
||||
<th className="px-6 py-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<span>Product Info</span>
|
||||
<InColumnStockFilter
|
||||
currentFilters={stockFilter}
|
||||
onFilterChange={onStockFilterChange}
|
||||
/>
|
||||
</div>
|
||||
</th>
|
||||
<th className="px-6 py-4 text-right">Forecast (Period)</th>
|
||||
<th className="px-6 py-4 text-right">Actual Units Sales 2026</th>
|
||||
<th className="px-6 py-4 text-right">Fc 26 Achievement</th>
|
||||
|
||||
Reference in New Issue
Block a user