Add Buy Box Lost detection feature with visual warning badges

This commit is contained in:
Christian Vidal Wolf
2026-01-29 09:42:39 +01:00
parent e4fbdb60b1
commit 04b208969d
7 changed files with 138 additions and 8 deletions
+21 -1
View File
@@ -5,7 +5,7 @@ import Dashboard from './components/Dashboard';
import FilterBar from './components/FilterBar';
import AIChat from './components/AIChat';
import CrazeLogo from './components/CrazeLogo';
import { processCSV, processExcel, filterData, aggregateData, processAdsCSV, processAdsExcel, mergeSalesAndAdsData, processTrafficExcel, processStockExcel, filterAdsData, calculateForecastViewData, processVendorStockExcel, calculateVelocityMap, getUniqueValues, processForecastExcel } from './services/dataProcessor';
import { processCSV, processExcel, filterData, aggregateData, processAdsCSV, processAdsExcel, mergeSalesAndAdsData, processTrafficExcel, processStockExcel, filterAdsData, calculateForecastViewData, processVendorStockExcel, calculateVelocityMap, getUniqueValues, processForecastExcel, processBuyBoxExcel } from './services/dataProcessor';
import { SalesRecord, FilterState, AggregatedData, AdsRecord, TrafficRecord, ForecastRecord, ProductForecastData } from './types';
import { queryGemini } from './services/geminiService';
import { ChartIcon, TableIcon, UploadIcon, DownloadIcon, CloseIcon, TrendingIcon, MegaphoneIcon } from './components/Icons';
@@ -52,6 +52,7 @@ const App: React.FC = () => {
const [vendorStockMap, setVendorStockMap] = useState<Map<string, { eu: number; uk: number }>>(new Map());
const [cachedForecastRecords, setCachedForecastRecords] = useState<ForecastRecord[]>([]);
const [lastForecastFile, setLastForecastFile] = useState<string | null>(null);
const [buyBoxLostMap, setBuyBoxLostMap] = useState<Map<string, { countries: string[]; reasons: Record<string, string> }>>(new Map());
// Modal State
const [isDataModalOpen, setIsDataModalOpen] = useState(false);
@@ -197,6 +198,21 @@ const App: React.FC = () => {
}
}, []);
const handleBuyBoxFetch = useCallback(async () => {
try {
console.log('[App] Fetching Buy Box data from /Buy_Box_tracker.xlsx...');
const response = await fetch('/Buy_Box_tracker.xlsx');
if (!response.ok) throw new Error(`Failed to fetch Buy Box data: ${response.status}`);
const buffer = await response.arrayBuffer();
const data = await processBuyBoxExcel(buffer);
setBuyBoxLostMap(data);
console.log('[App] Successfully loaded Buy Box lost data for', data.size, 'ASINs');
} catch (error) {
console.error("Failed to fetch/parse Buy Box data", error);
}
}, []);
const handleForecastFetch = useCallback(async (sales: SalesRecord[], globalSales: SalesRecord[], activeFilters: FilterState) => {
try {
@@ -336,6 +352,7 @@ const App: React.FC = () => {
console.log("Fetching Stock data...");
handleStockFetch();
handleVendorStockFetch();
handleBuyBoxFetch();
// 1e. Fetch Forecast data
handleForecastFetch(cachedData || [], cachedData || [], filters);
@@ -694,6 +711,7 @@ const App: React.FC = () => {
top50Mode={filters.customer.includes('Amazon UK') ? 'uk' : 'eu'}
top50Ranking={top50Ranking2025}
velocityMap={velocityMap}
buyBoxLostMap={buyBoxLostMap}
/>
</div>
</Suspense>
@@ -719,6 +737,7 @@ const App: React.FC = () => {
wocFilter={filters.woc}
onWocFilterChange={(s) => setFilters(prev => ({ ...prev, woc: s }))}
top50Mode={filters.customer.includes('Amazon UK') ? 'uk' : 'eu'}
buyBoxLostMap={buyBoxLostMap}
/>
</div>
</Suspense>
@@ -738,6 +757,7 @@ const App: React.FC = () => {
wocFilter={filters.woc}
onWocFilterChange={(s) => setFilters(prev => ({ ...prev, woc: s }))}
top50Mode={filters.customer.includes('Amazon UK') ? 'uk' : 'eu'}
buyBoxLostMap={buyBoxLostMap}
/>
</div>
</Suspense>