mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 13:35:24 +02:00
feat: implement Forecast 2026 (Fc 26) tab with seasonality logic and actual sales comparison
This commit is contained in:
@@ -5,8 +5,8 @@ import Dashboard from './components/Dashboard';
|
||||
import FilterBar from './components/FilterBar';
|
||||
import AIChat from './components/AIChat';
|
||||
import CrazeLogo from './components/CrazeLogo';
|
||||
import { SalesRecord, FilterState, AggregatedData, AdsRecord, TrafficRecord } from './types';
|
||||
import { processCSV, filterData, filterAdsData, aggregateData, getUniqueValues, processAdsCSV, processAdsExcel, processTrafficExcel, mergeSalesAndAdsData } from './services/dataProcessor';
|
||||
import { SalesRecord, FilterState, AggregatedData, AdsRecord, TrafficRecord, ForecastRecord, ProductForecastData } from './types';
|
||||
import { processCSV, filterData, filterAdsData, aggregateData, getUniqueValues, processAdsCSV, processAdsExcel, processTrafficExcel, mergeSalesAndAdsData, processForecastExcel, calculateForecastViewData } 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';
|
||||
@@ -16,6 +16,7 @@ const DataGrid = lazy(() => import('./components/DataGrid'));
|
||||
const WeeklyGrid = lazy(() => import('./components/WeeklyGrid'));
|
||||
const TopMovers = lazy(() => import('./components/TopMovers'));
|
||||
const AdsPerformance = lazy(() => import('./components/AdsPerformance'));
|
||||
const ForecastView = lazy(() => import('./components/ForecastView'));
|
||||
|
||||
// Loading fallback component
|
||||
const LoadingSpinner = () => (
|
||||
@@ -42,7 +43,8 @@ const App: React.FC = () => {
|
||||
const [trafficData, setTrafficData] = useState<TrafficRecord[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [syncing, setSyncing] = useState(false);
|
||||
const [view, setView] = useState<'dashboard' | 'table' | 'weekly' | 'movers' | 'ads'>('dashboard'); // Added 'weekly' view
|
||||
const [view, setView] = useState<'dashboard' | 'table' | 'weekly' | 'movers' | 'ads' | 'forecast'>('dashboard'); // Added 'forecast' view
|
||||
const [forecastData, setForecastData] = useState<ProductForecastData[]>([]);
|
||||
const [isChatOpen, setIsChatOpen] = useState(false);
|
||||
const [activeUrl, setActiveUrl] = useState<string | null>(() => localStorage.getItem('craze_csv_url') || PERMANENT_DROPBOX_URL);
|
||||
const [lastUpdated, setLastUpdated] = useState<string | null>(null);
|
||||
@@ -86,6 +88,9 @@ const App: React.FC = () => {
|
||||
setIsDataModalOpen(false);
|
||||
|
||||
console.log('[App] Successfully loaded', data.length, 'rows');
|
||||
|
||||
// Refresh forecast too
|
||||
handleForecastFetch(data, globalAsinMetadata);
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch/parse CSV", error);
|
||||
alert("Error loading data. Please refresh the page.");
|
||||
@@ -138,6 +143,22 @@ const App: React.FC = () => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleForecastFetch = useCallback(async (sales: SalesRecord[], meta: Map<string, { sku: string; title: string; line: string }>) => {
|
||||
try {
|
||||
console.log('[App] Fetching forecast from /forecast.xlsx...');
|
||||
const response = await fetch('/forecast.xlsx');
|
||||
if (!response.ok) throw new Error("Forecast file not found");
|
||||
|
||||
const buffer = await response.arrayBuffer();
|
||||
const fcRecords = await processForecastExcel(buffer);
|
||||
const viewData = calculateForecastViewData(sales, fcRecords, meta);
|
||||
setForecastData(viewData);
|
||||
console.log('[App] Forecast loaded:', viewData.length, 'records');
|
||||
} catch (error) {
|
||||
console.warn("Forecast fetch failed (expected if local file not set up yet):", error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const initializeData = (data: SalesRecord[]) => {
|
||||
setRawData(data);
|
||||
setFilters({
|
||||
@@ -211,6 +232,9 @@ const App: React.FC = () => {
|
||||
// 1c. Always fetch Traffic data (no caching for now)
|
||||
console.log("Fetching Traffic data...");
|
||||
handleTrafficFetch();
|
||||
|
||||
// 1d. Fetch Forecast data
|
||||
handleForecastFetch(cachedData || [], globalAsinMetadata);
|
||||
};
|
||||
initApp();
|
||||
}, [handleDataFetch]);
|
||||
@@ -502,6 +526,13 @@ const App: React.FC = () => {
|
||||
>
|
||||
<MegaphoneIcon /> <span className="hidden sm:inline">Ads</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setView('forecast')}
|
||||
className={`flex items-center gap-2 px-3 py-2 rounded-md text-sm font-medium transition-all
|
||||
${view === 'forecast' ? 'bg-slate-800 text-white shadow-sm ring-1 ring-white/10' : 'text-slate-400 hover:text-white hover:bg-slate-800/50'}`}
|
||||
>
|
||||
<ChartIcon /> <span className="hidden sm:inline">Fc 26</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -532,6 +563,7 @@ const App: React.FC = () => {
|
||||
{view === 'weekly' && <WeeklyGrid data={combinedAdsData} top50Ranking={top50Ranking2025} onDrillDown={handleSkuDrillDown} />}
|
||||
{view === 'movers' && <TopMovers data={filteredData} />}
|
||||
{view === 'ads' && <AdsPerformance data={combinedAdsData} filters={filters} top50Ranking={top50Ranking2025} />}
|
||||
{view === 'forecast' && <ForecastView data={forecastData} />}
|
||||
</Suspense>
|
||||
</div>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user