mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 14:05:24 +02:00
fix: resolve syntax error in App.tsx (handleTrafficFetch)
This commit is contained in:
@@ -125,14 +125,22 @@ const App: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleTrafficFetch = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
console.log('[App] Fetching traffic from /api/fetch-traffic...');
|
||||||
|
const response = await fetch('/api/fetch-traffic');
|
||||||
|
if (!response.ok) throw new Error(`Failed to fetch traffic: ${response.status}`);
|
||||||
|
|
||||||
|
const buffer = await response.arrayBuffer();
|
||||||
|
const data = await processTrafficExcel(buffer);
|
||||||
setTrafficData(data);
|
setTrafficData(data);
|
||||||
console.log('[App] Successfully loaded', data.length, 'traffic records');
|
console.log('[App] Successfully loaded', data.length, 'traffic records');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to fetch/parse Traffic", error);
|
console.error("Failed to fetch/parse Traffic", error);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleStockFetch = useCallback(async () => {
|
const handleStockFetch = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
console.log('[App] Fetching stock from /api/fetch-stock...');
|
console.log('[App] Fetching stock from /api/fetch-stock...');
|
||||||
const response = await fetch('/api/fetch-stock');
|
const response = await fetch('/api/fetch-stock');
|
||||||
@@ -145,10 +153,10 @@ const handleStockFetch = useCallback(async () => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to fetch/parse Stock", error);
|
console.error("Failed to fetch/parse Stock", error);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
const handleForecastFetch = useCallback(async (sales: SalesRecord[], globalSales: SalesRecord[], activeFilters: FilterState) => {
|
const handleForecastFetch = useCallback(async (sales: SalesRecord[], globalSales: SalesRecord[], activeFilters: FilterState) => {
|
||||||
try {
|
try {
|
||||||
const isUK = activeFilters.customer.includes('Amazon UK');
|
const isUK = activeFilters.customer.includes('Amazon UK');
|
||||||
const filename = isUK ? '/fc UK 26.xlsx' : '/fc 26.xlsx';
|
const filename = isUK ? '/fc UK 26.xlsx' : '/fc 26.xlsx';
|
||||||
@@ -178,10 +186,10 @@ const handleForecastFetch = useCallback(async (sales: SalesRecord[], globalSales
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn("Forecast fetch failed:", error);
|
console.warn("Forecast fetch failed:", error);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Update forecast when filters change
|
// Update forecast when filters change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (rawData.length > 0) {
|
if (rawData.length > 0) {
|
||||||
// Filter rawData to respect customer selections but keep all years (2025 for seasonality, 2026 for actuals)
|
// Filter rawData to respect customer selections but keep all years (2025 for seasonality, 2026 for actuals)
|
||||||
const forecastRelevantData = filterData(rawData, {
|
const forecastRelevantData = filterData(rawData, {
|
||||||
@@ -190,9 +198,9 @@ useEffect(() => {
|
|||||||
});
|
});
|
||||||
handleForecastFetch(forecastRelevantData, rawData, filters);
|
handleForecastFetch(forecastRelevantData, rawData, filters);
|
||||||
}
|
}
|
||||||
}, [filters, rawData, handleForecastFetch]);
|
}, [filters, rawData, handleForecastFetch]);
|
||||||
|
|
||||||
const initializeData = (data: SalesRecord[]) => {
|
const initializeData = (data: SalesRecord[]) => {
|
||||||
setRawData(data);
|
setRawData(data);
|
||||||
setFilters({
|
setFilters({
|
||||||
customer: [],
|
customer: [],
|
||||||
@@ -204,19 +212,19 @@ const initializeData = (data: SalesRecord[]) => {
|
|||||||
title: [],
|
title: [],
|
||||||
week: [],
|
week: [],
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSkuDrillDown = useCallback((sku: string) => {
|
const handleSkuDrillDown = useCallback((sku: string) => {
|
||||||
if (!sku) return;
|
if (!sku) return;
|
||||||
setFilters(prev => ({
|
setFilters(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
sku: [sku]
|
sku: [sku]
|
||||||
}));
|
}));
|
||||||
setView('ads');
|
setView('ads');
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 1. Initial Load from Cache (IndexedDB) or Auto-Fetch Permanent URL
|
// 1. Initial Load from Cache (IndexedDB) or Auto-Fetch Permanent URL
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const initApp = async () => {
|
const initApp = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
@@ -278,10 +286,10 @@ useEffect(() => {
|
|||||||
handleForecastFetch(cachedData || [], cachedData || [], filters);
|
handleForecastFetch(cachedData || [], cachedData || [], filters);
|
||||||
};
|
};
|
||||||
initApp();
|
initApp();
|
||||||
}, [handleDataFetch]);
|
}, [handleDataFetch]);
|
||||||
|
|
||||||
// Handle uploaded Sales file (Manual)
|
// Handle uploaded Sales file (Manual)
|
||||||
const handleSalesUpload = async (file: File) => {
|
const handleSalesUpload = async (file: File) => {
|
||||||
setSyncing(true);
|
setSyncing(true);
|
||||||
try {
|
try {
|
||||||
const data = await processCSV(file);
|
const data = await processCSV(file);
|
||||||
@@ -297,10 +305,10 @@ const handleSalesUpload = async (file: File) => {
|
|||||||
} finally {
|
} finally {
|
||||||
setSyncing(false);
|
setSyncing(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle uploaded Ads file (Manual) - Supports both CSV and Excel
|
// Handle uploaded Ads file (Manual) - Supports both CSV and Excel
|
||||||
const handleAdsUpload = async (file: File) => {
|
const handleAdsUpload = async (file: File) => {
|
||||||
setSyncing(true);
|
setSyncing(true);
|
||||||
try {
|
try {
|
||||||
const isExcel = file.name.endsWith('.xlsx') || file.name.endsWith('.xls');
|
const isExcel = file.name.endsWith('.xlsx') || file.name.endsWith('.xls');
|
||||||
@@ -315,10 +323,10 @@ const handleAdsUpload = async (file: File) => {
|
|||||||
} finally {
|
} finally {
|
||||||
setSyncing(false);
|
setSyncing(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle uploaded Traffic file (Manual)
|
// Handle uploaded Traffic file (Manual)
|
||||||
const handleTrafficUpload = async (file: File) => {
|
const handleTrafficUpload = async (file: File) => {
|
||||||
setSyncing(true);
|
setSyncing(true);
|
||||||
try {
|
try {
|
||||||
const data = await processTrafficExcel(file);
|
const data = await processTrafficExcel(file);
|
||||||
@@ -331,10 +339,10 @@ const handleTrafficUpload = async (file: File) => {
|
|||||||
} finally {
|
} finally {
|
||||||
setSyncing(false);
|
setSyncing(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 2. Schedule Auto-Refresh (Background)
|
// 2. Schedule Auto-Refresh (Background)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkAndRefresh = () => {
|
const checkAndRefresh = () => {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const today = now.toISOString().split('T')[0]; // YYYY-MM-DD
|
const today = now.toISOString().split('T')[0]; // YYYY-MM-DD
|
||||||
@@ -361,11 +369,11 @@ useEffect(() => {
|
|||||||
const interval = setInterval(checkAndRefresh, 15 * 60 * 1000);
|
const interval = setInterval(checkAndRefresh, 15 * 60 * 1000);
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}, [handleDataFetch]);
|
}, [handleDataFetch]);
|
||||||
|
|
||||||
|
|
||||||
// Derive Data
|
// Derive Data
|
||||||
const globalAsinMetadata = useMemo(() => {
|
const globalAsinMetadata = useMemo(() => {
|
||||||
const metaMap = new Map<string, { sku: string; title: string; line: string }>();
|
const metaMap = new Map<string, { sku: string; title: string; line: string }>();
|
||||||
rawData.forEach(r => {
|
rawData.forEach(r => {
|
||||||
const asin = r.asin.trim().toUpperCase();
|
const asin = r.asin.trim().toUpperCase();
|
||||||
@@ -376,14 +384,14 @@ const globalAsinMetadata = useMemo(() => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
return metaMap;
|
return metaMap;
|
||||||
}, [rawData]);
|
}, [rawData]);
|
||||||
|
|
||||||
const filteredData = useMemo(() => filterData(rawData, filters), [rawData, filters]);
|
const filteredData = useMemo(() => filterData(rawData, filters), [rawData, filters]);
|
||||||
const filteredAdsData = useMemo(() => filterAdsData(adsData, filters, globalAsinMetadata), [adsData, filters, globalAsinMetadata]);
|
const filteredAdsData = useMemo(() => filterAdsData(adsData, filters, globalAsinMetadata), [adsData, filters, globalAsinMetadata]);
|
||||||
const aggregatedData = useMemo(() => aggregateData(filteredData), [filteredData]);
|
const aggregatedData = useMemo(() => aggregateData(filteredData), [filteredData]);
|
||||||
|
|
||||||
// Calculate country-aware Top 50 Best Sellers for 2025
|
// Calculate country-aware Top 50 Best Sellers for 2025
|
||||||
const top50Ranking2025 = useMemo(() => {
|
const top50Ranking2025 = useMemo(() => {
|
||||||
// Filter only 2025 data
|
// Filter only 2025 data
|
||||||
const data2025 = rawData.filter(r => r.year === 2025);
|
const data2025 = rawData.filter(r => r.year === 2025);
|
||||||
|
|
||||||
@@ -412,15 +420,15 @@ const top50Ranking2025 = useMemo(() => {
|
|||||||
eu: calculateTop50(euData),
|
eu: calculateTop50(euData),
|
||||||
uk: calculateTop50(ukData)
|
uk: calculateTop50(ukData)
|
||||||
};
|
};
|
||||||
}, [rawData]);
|
}, [rawData]);
|
||||||
|
|
||||||
// Combine Sales & Ads Data dynamically based on current filters
|
// Combine Sales & Ads Data dynamically based on current filters
|
||||||
const combinedAdsData = useMemo(() => {
|
const combinedAdsData = useMemo(() => {
|
||||||
return mergeSalesAndAdsData(filteredData, filteredAdsData, globalAsinMetadata, trafficData);
|
return mergeSalesAndAdsData(filteredData, filteredAdsData, globalAsinMetadata, trafficData);
|
||||||
}, [filteredData, filteredAdsData, globalAsinMetadata, trafficData]);
|
}, [filteredData, filteredAdsData, globalAsinMetadata, trafficData]);
|
||||||
|
|
||||||
// Derive Context Data (Product Line Context when drilling down)
|
// Derive Context Data (Product Line Context when drilling down)
|
||||||
const contextAggregatedData = useMemo(() => {
|
const contextAggregatedData = useMemo(() => {
|
||||||
// Check if we are filtering by specific items (SKU, ASIN, Title)
|
// Check if we are filtering by specific items (SKU, ASIN, Title)
|
||||||
const hasItemFilters = filters.sku.length > 0 || filters.asin.length > 0 || filters.title.length > 0;
|
const hasItemFilters = filters.sku.length > 0 || filters.asin.length > 0 || filters.title.length > 0;
|
||||||
|
|
||||||
@@ -444,11 +452,11 @@ const contextAggregatedData = useMemo(() => {
|
|||||||
const broadData = filterData(rawData, contextFilters);
|
const broadData = filterData(rawData, contextFilters);
|
||||||
return aggregateData(broadData);
|
return aggregateData(broadData);
|
||||||
|
|
||||||
}, [rawData, filters, filteredData]);
|
}, [rawData, filters, filteredData]);
|
||||||
|
|
||||||
|
|
||||||
// Derive Options for Filter Dropdowns
|
// Derive Options for Filter Dropdowns
|
||||||
const filterOptions = useMemo(() => {
|
const filterOptions = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
customer: getUniqueValues(rawData, 'customer'),
|
customer: getUniqueValues(rawData, 'customer'),
|
||||||
year: getUniqueValues(rawData, 'year'),
|
year: getUniqueValues(rawData, 'year'),
|
||||||
@@ -459,17 +467,17 @@ const filterOptions = useMemo(() => {
|
|||||||
title: getUniqueValues(rawData, 'title'),
|
title: getUniqueValues(rawData, 'title'),
|
||||||
week: Array.from(new Set(rawData.map(r => r.week).filter(w => w !== undefined))).sort((a, b) => (a as number) - (b as number)).map(w => `W${w}`),
|
week: Array.from(new Set(rawData.map(r => r.week).filter(w => w !== undefined))).sort((a, b) => (a as number) - (b as number)).map(w => `W${w}`),
|
||||||
};
|
};
|
||||||
}, [rawData]);
|
}, [rawData]);
|
||||||
|
|
||||||
const handleFilterChange = (key: keyof FilterState, value: string[]) => {
|
const handleFilterChange = (key: keyof FilterState, value: string[]) => {
|
||||||
setFilters(prev => ({ ...prev, [key]: value }));
|
setFilters(prev => ({ ...prev, [key]: value }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAskGemini = async (text: string) => {
|
const handleAskGemini = async (text: string) => {
|
||||||
return await queryGemini(text, aggregatedData, filteredData.length);
|
return await queryGemini(text, aggregatedData, filteredData.length);
|
||||||
};
|
};
|
||||||
|
|
||||||
const disconnectUrl = async () => {
|
const disconnectUrl = async () => {
|
||||||
// Allow disconnecting to clear data, but the app will likely re-connect on next reload due to "Permanent" requirement
|
// Allow disconnecting to clear data, but the app will likely re-connect on next reload due to "Permanent" requirement
|
||||||
localStorage.removeItem('craze_csv_url');
|
localStorage.removeItem('craze_csv_url');
|
||||||
await clearSalesData();
|
await clearSalesData();
|
||||||
@@ -477,9 +485,9 @@ const disconnectUrl = async () => {
|
|||||||
setActiveUrl(null);
|
setActiveUrl(null);
|
||||||
setRawData([]);
|
setRawData([]);
|
||||||
setAdsData([]);
|
setAdsData([]);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex flex-col bg-background text-slate-200">
|
<div className="min-h-screen flex flex-col bg-background text-slate-200">
|
||||||
|
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
@@ -646,7 +654,7 @@ return (
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|||||||
Reference in New Issue
Block a user