feat: integrate Amazon Vendor Stock data across all views and fix local data loading

This commit is contained in:
Christian Vidal Wolf
2026-01-28 09:46:59 +01:00
parent 49decf9b9f
commit 97071c7b62
10 changed files with 226 additions and 13 deletions
+25 -4
View File
@@ -6,7 +6,7 @@ import FilterBar from './components/FilterBar';
import AIChat from './components/AIChat';
import CrazeLogo from './components/CrazeLogo';
import { SalesRecord, FilterState, AggregatedData, AdsRecord, TrafficRecord, ForecastRecord, ProductForecastData } from './types';
import { processCSV, filterData, filterAdsData, aggregateData, getUniqueValues, processAdsCSV, processAdsExcel, processTrafficExcel, mergeSalesAndAdsData, processForecastExcel, calculateForecastViewData, processStockExcel } from './services/dataProcessor';
import { processCSV, filterData, filterAdsData, aggregateData, getUniqueValues, processAdsCSV, processAdsExcel, processTrafficExcel, mergeSalesAndAdsData, processForecastExcel, calculateForecastViewData, processStockExcel, processVendorStockExcel } from './services/dataProcessor';
import { queryGemini } from './services/geminiService';
import { ChartIcon, TableIcon, UploadIcon, DownloadIcon, CloseIcon, TrendingIcon, MegaphoneIcon } from './components/Icons';
import { loadSalesData, saveSalesData, clearSalesData, loadAdsData, saveAdsData, clearAdsData } from './services/storage';
@@ -49,6 +49,7 @@ const App: React.FC = () => {
const [activeUrl, setActiveUrl] = useState<string | null>(() => localStorage.getItem('craze_csv_url') || PERMANENT_DROPBOX_URL);
const [lastUpdated, setLastUpdated] = useState<string | null>(null);
const [stockMap, setStockMap] = useState<Map<string, number>>(new Map());
const [vendorStockMap, setVendorStockMap] = useState<Map<string, { eu: number; uk: number }>>(new Map());
// Modal State
const [isDataModalOpen, setIsDataModalOpen] = useState(false);
@@ -143,8 +144,8 @@ const App: React.FC = () => {
const handleStockFetch = useCallback(async () => {
try {
console.log('[App] Fetching stock from /api/fetch-stock...');
const response = await fetch('/api/fetch-stock');
console.log('[App] Fetching stock from /Item Availability.xlsx...');
const response = await fetch('/Item Availability.xlsx');
if (!response.ok) throw new Error(`Failed to fetch stock: ${response.status}`);
const buffer = await response.arrayBuffer();
@@ -156,6 +157,21 @@ const App: React.FC = () => {
}
}, []);
const handleVendorStockFetch = useCallback(async () => {
try {
console.log('[App] Fetching vendor stock from /Vendor Stock.xlsx...');
const response = await fetch('/Vendor Stock.xlsx');
if (!response.ok) throw new Error(`Failed to fetch vendor stock: ${response.status}`);
const buffer = await response.arrayBuffer();
const data = await processVendorStockExcel(buffer);
setVendorStockMap(data);
console.log('[App] Successfully loaded vendor stock for', data.size, 'ASINs');
} catch (error) {
console.error("Failed to fetch/parse Vendor Stock", error);
}
}, []);
const handleForecastFetch = useCallback(async (sales: SalesRecord[], globalSales: SalesRecord[], activeFilters: FilterState) => {
try {
@@ -283,6 +299,7 @@ const App: React.FC = () => {
// 1d. Always fetch Stock data
console.log("Fetching Stock data...");
handleStockFetch();
handleVendorStockFetch();
// 1e. Fetch Forecast data
handleForecastFetch(cachedData || [], cachedData || [], filters);
@@ -528,7 +545,7 @@ const App: React.FC = () => {
<div className="flex items-center gap-6">
{/* Force Sync Action */}
<button
onClick={() => { handleDataFetch(); handleAdsFetch(); handleTrafficFetch(); }}
onClick={() => { handleDataFetch(); handleAdsFetch(); handleTrafficFetch(); handleVendorStockFetch(); }}
disabled={syncing}
className={`flex items-center gap-2 px-4 py-2 rounded-xl text-sm font-black uppercase tracking-widest transition-all
${syncing
@@ -609,6 +626,7 @@ const App: React.FC = () => {
hasCustomerFilter={filters.customer.length > 0}
adsData={filteredAdsData}
stockMap={stockMap}
vendorStockMap={vendorStockMap}
top50Ranking={top50Ranking2025}
top50Mode={filters.customer.includes('Amazon UK') ? 'uk' : 'eu'}
/>
@@ -620,6 +638,7 @@ const App: React.FC = () => {
data={combinedAdsData}
onDrillDown={handleSkuDrillDown}
stockMap={stockMap}
vendorStockMap={vendorStockMap}
stockFilter={filters.stock}
onStockFilterChange={(s) => setFilters(prev => ({ ...prev, stock: s }))}
customerFilters={filters.customer}
@@ -640,6 +659,7 @@ const App: React.FC = () => {
filters={filters}
top50Ranking={top50Ranking2025}
stockMap={stockMap}
vendorStockMap={vendorStockMap}
stockFilter={filters.stock}
onStockFilterChange={(s) => setFilters(prev => ({ ...prev, stock: s }))}
top50Mode={filters.customer.includes('Amazon UK') ? 'uk' : 'eu'}
@@ -653,6 +673,7 @@ const App: React.FC = () => {
filters={filters}
top50Ranking={top50Ranking2025}
stockMap={stockMap}
vendorStockMap={vendorStockMap}
stockFilter={filters.stock}
onStockFilterChange={(s) => setFilters(prev => ({ ...prev, stock: s }))}
top50Mode={filters.customer.includes('Amazon UK') ? 'uk' : 'eu'}