mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 12:35:24 +02:00
feat: automatic ads data loading and attributed sales line in chart
This commit is contained in:
@@ -9,10 +9,10 @@ import FilterBar from './components/FilterBar';
|
||||
import AIChat from './components/AIChat';
|
||||
import CrazeLogo from './components/CrazeLogo';
|
||||
import { SalesRecord, FilterState, AggregatedData, AdsRecord } from './types'; // Imported AdsRecord
|
||||
import { processCSV, filterData, aggregateData, getUniqueValues, processAdsCSV, processAdsExcel, mergeSalesAndAdsData } from './services/dataProcessor'; // Imported new processors
|
||||
import { processCSV, filterData, filterAdsData, aggregateData, getUniqueValues, processAdsCSV, processAdsExcel, mergeSalesAndAdsData } from './services/dataProcessor';
|
||||
import { queryGemini } from './services/geminiService';
|
||||
import { ChartIcon, TableIcon, UploadIcon, DownloadIcon, CloseIcon, TrendingIcon } from './components/Icons';
|
||||
import { loadSalesData, saveSalesData, clearSalesData } from './services/storage';
|
||||
import { loadSalesData, saveSalesData, clearSalesData, loadAdsData, saveAdsData, clearAdsData } from './services/storage';
|
||||
|
||||
// New Refresh Icon
|
||||
const RefreshIcon = ({ className }: { className?: string }) => (
|
||||
@@ -24,6 +24,7 @@ const RefreshIcon = ({ className }: { className?: string }) => (
|
||||
// Hardcoded Permanent URL for Auto-Loading
|
||||
// Using the original share link to leverage Dropbox's redirect for robust fetching.
|
||||
const PERMANENT_DROPBOX_URL = "https://www.dropbox.com/scl/fi/b9zxn4z5i7sxwfakk5g5y/Amazon-Sell-Out-2023-2025.csv?rlkey=uoto6v0mm99py8nszy8ldtez8&st=pzn1zkrg&dl=0";
|
||||
const PERMANENT_ADS_URL = "https://www.dropbox.com/scl/fi/wng8tep7awhvzd65amwad/Ads-Weekly.xlsx?rlkey=kcmoq8dxgibsb2eb8zz47xvyo&st=z02m4w3g&dl=0";
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [rawData, setRawData] = useState<SalesRecord[]>([]);
|
||||
@@ -84,6 +85,29 @@ const App: React.FC = () => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleAdsFetch = useCallback(async () => {
|
||||
setSyncing(true);
|
||||
try {
|
||||
console.log('[App] Fetching ads from /api/fetch-ads...');
|
||||
const response = await fetch('/api/fetch-ads');
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch Ads: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const buffer = await response.arrayBuffer();
|
||||
const data = await processAdsExcel(buffer);
|
||||
|
||||
await saveAdsData(data);
|
||||
setAdsData(data);
|
||||
console.log('[App] Successfully loaded', data.length, 'ads records');
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch/parse Ads", error);
|
||||
} finally {
|
||||
setSyncing(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const initializeData = (data: SalesRecord[]) => {
|
||||
setRawData(data);
|
||||
setFilters({
|
||||
@@ -134,6 +158,16 @@ const App: React.FC = () => {
|
||||
console.error("Initial fetch failed.");
|
||||
});
|
||||
}
|
||||
|
||||
// 1b. Load Ads from cache or auto-fetch
|
||||
const { data: cachedAds, lastUpdated: adsLastUpdated } = await loadAdsData();
|
||||
if (cachedAds && cachedAds.length > 0) {
|
||||
console.log("Loaded ads from cache:", cachedAds.length, "records");
|
||||
setAdsData(cachedAds);
|
||||
} else {
|
||||
console.log("Fetching fresh Ads from URL...");
|
||||
handleAdsFetch();
|
||||
}
|
||||
};
|
||||
initApp();
|
||||
}, [handleDataFetch]);
|
||||
@@ -147,6 +181,8 @@ const App: React.FC = () => {
|
||||
initializeData(data);
|
||||
setLastUpdated(new Date().toISOString());
|
||||
setIsDataModalOpen(false);
|
||||
// Also trigger ads fetch if not loaded to maintain sync
|
||||
if (adsData.length === 0) handleAdsFetch();
|
||||
} catch (error) {
|
||||
console.error("Failed to parse CSV", error);
|
||||
alert("Error parsing CSV. Please check the format.");
|
||||
@@ -162,6 +198,7 @@ const App: React.FC = () => {
|
||||
const isExcel = file.name.endsWith('.xlsx') || file.name.endsWith('.xls');
|
||||
const data = isExcel ? await processAdsExcel(file) : await processAdsCSV(file);
|
||||
setAdsData(data);
|
||||
await saveAdsData(data);
|
||||
console.log("Ads loaded:", data.length, "records from", file.name);
|
||||
setIsDataModalOpen(false);
|
||||
} catch (error) {
|
||||
@@ -185,6 +222,8 @@ const App: React.FC = () => {
|
||||
handleDataFetch().then(() => {
|
||||
localStorage.setItem('craze_last_refresh_date', today);
|
||||
console.log("Daily refresh successful.");
|
||||
// Also refresh ads
|
||||
handleAdsFetch();
|
||||
}).catch(err => {
|
||||
console.error("Daily refresh failed, will retry later.", err);
|
||||
});
|
||||
@@ -203,6 +242,7 @@ const App: React.FC = () => {
|
||||
|
||||
// Derive Data
|
||||
const filteredData = useMemo(() => filterData(rawData, filters), [rawData, filters]);
|
||||
const filteredAdsData = useMemo(() => filterAdsData(adsData, filters), [adsData, filters]);
|
||||
const aggregatedData = useMemo(() => aggregateData(filteredData), [filteredData]);
|
||||
|
||||
// Combine Sales & Ads Data dynamically based on current filters
|
||||
@@ -264,8 +304,10 @@ const App: React.FC = () => {
|
||||
// Allow disconnecting to clear data, but the app will likely re-connect on next reload due to "Permanent" requirement
|
||||
localStorage.removeItem('craze_csv_url');
|
||||
await clearSalesData();
|
||||
await clearAdsData();
|
||||
setActiveUrl(null);
|
||||
setRawData([]);
|
||||
setAdsData([]);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -308,7 +350,10 @@ const App: React.FC = () => {
|
||||
|
||||
{/* NEW REFRESH BUTTON */}
|
||||
<button
|
||||
onClick={handleDataFetch}
|
||||
onClick={() => {
|
||||
handleDataFetch();
|
||||
handleAdsFetch();
|
||||
}}
|
||||
disabled={syncing}
|
||||
title="Refresh Data"
|
||||
className="p-3 rounded-lg bg-slate-800 border border-border text-slate-400 hover:text-white hover:bg-slate-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||
@@ -361,10 +406,10 @@ const App: React.FC = () => {
|
||||
<Dashboard
|
||||
data={aggregatedData}
|
||||
contextData={contextAggregatedData}
|
||||
adsData={adsData}
|
||||
adsData={filteredAdsData}
|
||||
/>
|
||||
)}
|
||||
{view === 'table' && <DataGrid data={filteredData} hasCustomerFilter={filters.customer.length > 0} adsData={adsData} />}
|
||||
{view === 'table' && <DataGrid data={filteredData} hasCustomerFilter={filters.customer.length > 0} adsData={filteredAdsData} />}
|
||||
{view === 'movers' && <TopMovers data={filteredData} />}
|
||||
</div>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user