mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 15:35:22 +02:00
feat: Integrate advertising data and dashboard
Adds functionality to process and display advertising data alongside sales data. This includes: - Introducing a new `AdvertisingDashboard` component. - Modifying `FileUpload` to accept advertising CSVs. - Updating `App.tsx` to manage and display both sales and ad data. - Enhancing `dataProcessor.ts` to handle advertising CSV parsing and merging. - Adding a `MegaphoneIcon` for advertising-related UI elements. - Defining new types for advertising records and combined KPIs.
This commit is contained in:
@@ -4,13 +4,14 @@ import FileUpload from './components/FileUpload';
|
||||
import Dashboard from './components/Dashboard';
|
||||
import DataGrid from './components/DataGrid';
|
||||
import TopMovers from './components/TopMovers';
|
||||
import AdvertisingDashboard from './components/AdvertisingDashboard'; // Imported
|
||||
import FilterBar from './components/FilterBar';
|
||||
import AIChat from './components/AIChat';
|
||||
import CrazeLogo from './components/CrazeLogo';
|
||||
import { SalesRecord, FilterState, AggregatedData } from './types';
|
||||
import { processCSV, filterData, aggregateData, getUniqueValues } from './services/dataProcessor';
|
||||
import { SalesRecord, FilterState, AggregatedData, AdsRecord } from './types'; // Imported AdsRecord
|
||||
import { processCSV, filterData, aggregateData, getUniqueValues, processAdsCSV, mergeSalesAndAdsData } from './services/dataProcessor'; // Imported new processors
|
||||
import { queryGemini } from './services/geminiService';
|
||||
import { ChartIcon, TableIcon, UploadIcon, DownloadIcon, CloseIcon, TrendingIcon } from './components/Icons';
|
||||
import { ChartIcon, TableIcon, UploadIcon, DownloadIcon, CloseIcon, TrendingIcon, MegaphoneIcon } from './components/Icons'; // Imported MegaphoneIcon
|
||||
import { loadSalesData, saveSalesData, clearSalesData } from './services/storage';
|
||||
|
||||
// New Refresh Icon
|
||||
@@ -26,9 +27,10 @@ const PERMANENT_DROPBOX_URL = "https://www.dropbox.com/scl/fi/b9zxn4z5i7sxwfakk5
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [rawData, setRawData] = useState<SalesRecord[]>([]);
|
||||
const [adsData, setAdsData] = useState<AdsRecord[]>([]); // New Ads State
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [syncing, setSyncing] = useState(false);
|
||||
const [view, setView] = useState<'dashboard' | 'table' | 'movers'>('dashboard');
|
||||
const [view, setView] = useState<'dashboard' | 'table' | 'movers' | 'ads'>('dashboard'); // Added 'ads' view
|
||||
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);
|
||||
@@ -131,8 +133,8 @@ const App: React.FC = () => {
|
||||
initApp();
|
||||
}, [handleUrlFetch]);
|
||||
|
||||
// Handle uploaded file (Manual)
|
||||
const handleFileUpload = async (file: File) => {
|
||||
// Handle uploaded Sales file (Manual)
|
||||
const handleSalesUpload = async (file: File) => {
|
||||
setSyncing(true);
|
||||
try {
|
||||
const data = await processCSV(file);
|
||||
@@ -148,6 +150,23 @@ const App: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// Handle uploaded Ads file (Manual)
|
||||
const handleAdsUpload = async (file: File) => {
|
||||
setSyncing(true);
|
||||
try {
|
||||
const data = await processAdsCSV(file);
|
||||
setAdsData(data);
|
||||
console.log("Ads loaded:", data.length);
|
||||
setIsDataModalOpen(false);
|
||||
setView('ads'); // Switch to ads view automatically
|
||||
} catch (error) {
|
||||
console.error("Failed to parse Ads CSV", error);
|
||||
alert("Error parsing Ads CSV. Please check the format.");
|
||||
} finally {
|
||||
setSyncing(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 2. Schedule Auto-Refresh (Background)
|
||||
useEffect(() => {
|
||||
const checkAndRefresh = () => {
|
||||
@@ -180,6 +199,11 @@ const App: React.FC = () => {
|
||||
// Derive Data
|
||||
const filteredData = useMemo(() => filterData(rawData, filters), [rawData, filters]);
|
||||
const aggregatedData = useMemo(() => aggregateData(filteredData), [filteredData]);
|
||||
|
||||
// Combine Sales & Ads Data dynamically based on current filters
|
||||
const combinedAdsData = useMemo(() => {
|
||||
return mergeSalesAndAdsData(filteredData, adsData);
|
||||
}, [filteredData, adsData]);
|
||||
|
||||
// Derive Context Data (Product Line Context when drilling down)
|
||||
const contextAggregatedData = useMemo(() => {
|
||||
@@ -290,24 +314,31 @@ const App: React.FC = () => {
|
||||
<div className="flex bg-slate-900 rounded-lg p-1 border border-border">
|
||||
<button
|
||||
onClick={() => setView('dashboard')}
|
||||
className={`flex items-center gap-2 px-4 py-2 rounded-md text-sm font-medium transition-all
|
||||
className={`flex items-center gap-2 px-3 py-2 rounded-md text-sm font-medium transition-all
|
||||
${view === 'dashboard' ? '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">Dashboard</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setView('table')}
|
||||
className={`flex items-center gap-2 px-4 py-2 rounded-md text-sm font-medium transition-all
|
||||
className={`flex items-center gap-2 px-3 py-2 rounded-md text-sm font-medium transition-all
|
||||
${view === 'table' ? 'bg-slate-800 text-white shadow-sm ring-1 ring-white/10' : 'text-slate-400 hover:text-white hover:bg-slate-800/50'}`}
|
||||
>
|
||||
<TableIcon /> <span className="hidden sm:inline">Data Grid</span>
|
||||
<TableIcon /> <span className="hidden sm:inline">Grid</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setView('movers')}
|
||||
className={`flex items-center gap-2 px-4 py-2 rounded-md text-sm font-medium transition-all
|
||||
className={`flex items-center gap-2 px-3 py-2 rounded-md text-sm font-medium transition-all
|
||||
${view === 'movers' ? 'bg-slate-800 text-white shadow-sm ring-1 ring-white/10' : 'text-slate-400 hover:text-white hover:bg-slate-800/50'}`}
|
||||
>
|
||||
<TrendingIcon /> <span className="hidden sm:inline">Top Movers</span>
|
||||
<TrendingIcon /> <span className="hidden sm:inline">Movers</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setView('ads')}
|
||||
className={`flex items-center gap-2 px-3 py-2 rounded-md text-sm font-medium transition-all
|
||||
${view === 'ads' ? 'bg-slate-800 text-white shadow-sm ring-1 ring-white/10' : 'text-slate-400 hover:text-white hover:bg-slate-800/50'}`}
|
||||
>
|
||||
<MegaphoneIcon /> <span className="hidden sm:inline">Ads</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -335,6 +366,7 @@ const App: React.FC = () => {
|
||||
)}
|
||||
{view === 'table' && <DataGrid data={filteredData} />}
|
||||
{view === 'movers' && <TopMovers data={filteredData} />}
|
||||
{view === 'ads' && <AdvertisingDashboard data={combinedAdsData} />}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
@@ -356,7 +388,8 @@ const App: React.FC = () => {
|
||||
|
||||
<div className="p-6">
|
||||
<FileUpload
|
||||
onFileUpload={handleFileUpload}
|
||||
onSalesUpload={handleSalesUpload} // CORRECTED: Was handleFileUpload
|
||||
onAdsUpload={handleAdsUpload} // ADDED: Missing prop causing error
|
||||
onUrlSubmit={handleUrlFetch}
|
||||
isLoading={syncing}
|
||||
activeUrl={activeUrl}
|
||||
|
||||
Reference in New Issue
Block a user