mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 15:45:24 +02:00
feat: Integrate PapaParse for CSV handling
Adds papaparse as a dependency and updates the data processing service to use it for more robust CSV file parsing. This replaces manual CSV parsing logic with a dedicated library, improving reliability and handling of various CSV formats. Also renames the `MoversIcon` to `TrendingIcon` to better reflect its usage in indicating trending performance metrics.
This commit is contained in:
@@ -1,15 +1,16 @@
|
||||
|
||||
import React, { useState, useMemo, useEffect, useCallback } from 'react';
|
||||
import FileUpload from './components/FileUpload';
|
||||
import Dashboard from './components/Dashboard';
|
||||
import DataGrid from './components/DataGrid';
|
||||
import TopMovers from './components/TopMovers';
|
||||
import FilterBar from './components/FilterBar';
|
||||
import AIChat from './components/AIChat';
|
||||
import CrazeLogo from './components/CrazeLogo';
|
||||
import TopMoversPage from './components/TopMoversPage'; // New import
|
||||
import { SalesRecord, FilterState, AggregatedData } from './types';
|
||||
import { processCSV, processExcel, filterData, aggregateData, getUniqueValues } from './services/dataProcessor';
|
||||
import { processCSV, filterData, aggregateData, getUniqueValues } from './services/dataProcessor';
|
||||
import { queryGemini } from './services/geminiService';
|
||||
import { ChartIcon, TableIcon, UploadIcon, DownloadIcon, CloseIcon } from './components/Icons';
|
||||
import { ChartIcon, TableIcon, UploadIcon, DownloadIcon, CloseIcon, TrendingIcon } from './components/Icons';
|
||||
import { loadSalesData, saveSalesData, clearSalesData } from './services/storage';
|
||||
|
||||
// New Refresh Icon
|
||||
@@ -19,13 +20,6 @@ const RefreshIcon = ({ className }: { className?: string }) => (
|
||||
</svg>
|
||||
);
|
||||
|
||||
// New Movers Icon
|
||||
const MoversIcon = ({ className }: { className?: string }) => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className={className}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M2.25 18 9 11.25l4.306 4.305a11.164 11.164 0 0 0 5.814-5.815L21.75 6m0 0-3.5-3.5m3.5 3.5-3.5 3.5" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
// 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&dl=0";
|
||||
@@ -34,14 +28,11 @@ const App: React.FC = () => {
|
||||
const [rawData, setRawData] = useState<SalesRecord[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [syncing, setSyncing] = useState(false);
|
||||
const [view, setView] = useState<'dashboard' | 'table' | 'topMovers'>('dashboard'); // Added 'topMovers'
|
||||
const [view, setView] = useState<'dashboard' | 'table' | 'movers'>('dashboard');
|
||||
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);
|
||||
|
||||
// API Key State
|
||||
const [apiKey, setApiKey] = useState<string>(() => localStorage.getItem('gemini_api_key') || '');
|
||||
|
||||
// Modal State
|
||||
const [isDataModalOpen, setIsDataModalOpen] = useState(false);
|
||||
|
||||
@@ -56,16 +47,6 @@ const App: React.FC = () => {
|
||||
title: [],
|
||||
});
|
||||
|
||||
// Handle API Key Change
|
||||
const handleApiKeyChange = (key: string) => {
|
||||
setApiKey(key);
|
||||
if (key) {
|
||||
localStorage.setItem('gemini_api_key', key);
|
||||
} else {
|
||||
localStorage.removeItem('gemini_api_key');
|
||||
}
|
||||
};
|
||||
|
||||
// Handle URL Fetch (Auto/Manual)
|
||||
const handleUrlFetch = useCallback(async (url: string) => {
|
||||
setSyncing(true);
|
||||
@@ -79,6 +60,8 @@ const App: React.FC = () => {
|
||||
}
|
||||
|
||||
// Use a CORS proxy to bypass browser's same-origin policy restrictions.
|
||||
// This is necessary because Dropbox does not send the required CORS headers
|
||||
// for direct client-side fetching from another domain.
|
||||
const proxyUrl = `https://corsproxy.io/?${encodeURIComponent(directUrl)}`;
|
||||
|
||||
const response = await fetch(proxyUrl);
|
||||
@@ -152,24 +135,14 @@ const App: React.FC = () => {
|
||||
const handleFileUpload = async (file: File) => {
|
||||
setSyncing(true);
|
||||
try {
|
||||
let data: SalesRecord[] = [];
|
||||
const lowerName = file.name.toLowerCase();
|
||||
|
||||
if (lowerName.endsWith('.csv')) {
|
||||
data = await processCSV(file);
|
||||
} else if (lowerName.endsWith('.xlsx') || lowerName.endsWith('.xls')) {
|
||||
data = await processExcel(file);
|
||||
} else {
|
||||
throw new Error("Unsupported file format");
|
||||
}
|
||||
|
||||
const data = await processCSV(file);
|
||||
await saveSalesData(data);
|
||||
initializeData(data);
|
||||
setLastUpdated(new Date().toISOString());
|
||||
setIsDataModalOpen(false);
|
||||
} catch (error) {
|
||||
console.error("Failed to parse file", error);
|
||||
alert("Error parsing file. Please check format (CSV or Excel).");
|
||||
console.error("Failed to parse CSV", error);
|
||||
alert("Error parsing CSV. Please check the format.");
|
||||
} finally {
|
||||
setSyncing(false);
|
||||
}
|
||||
@@ -254,7 +227,7 @@ const App: React.FC = () => {
|
||||
};
|
||||
|
||||
const handleAskGemini = async (text: string) => {
|
||||
return await queryGemini(apiKey, text, aggregatedData, filteredData.length);
|
||||
return await queryGemini(text, aggregatedData, filteredData.length);
|
||||
};
|
||||
|
||||
const disconnectUrl = async () => {
|
||||
@@ -329,12 +302,12 @@ const App: React.FC = () => {
|
||||
>
|
||||
<TableIcon /> <span className="hidden sm:inline">Data Grid</span>
|
||||
</button>
|
||||
<button // New Top Movers Button
|
||||
onClick={() => setView('topMovers')}
|
||||
<button
|
||||
onClick={() => setView('movers')}
|
||||
className={`flex items-center gap-2 px-4 py-2 rounded-md text-sm font-medium transition-all
|
||||
${view === 'topMovers' ? 'bg-slate-800 text-white shadow-sm ring-1 ring-white/10' : 'text-slate-400 hover:text-white hover:bg-slate-800/50'}`}
|
||||
${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'}`}
|
||||
>
|
||||
<MoversIcon className="w-5 h-5" /> <span className="hidden sm:inline">Top Movers</span>
|
||||
<TrendingIcon /> <span className="hidden sm:inline">Top Movers</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -352,37 +325,23 @@ const App: React.FC = () => {
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{/* FilterBar is now relevant for all views including Top Movers */}
|
||||
{(view === 'dashboard' || view === 'table' || view === 'topMovers') && (
|
||||
<FilterBar filters={filters} onFilterChange={handleFilterChange} options={filterOptions} />
|
||||
)}
|
||||
|
||||
<FilterBar filters={filters} onFilterChange={handleFilterChange} options={filterOptions} />
|
||||
<div className="mt-6">
|
||||
{view === 'dashboard' ? (
|
||||
{view === 'dashboard' && (
|
||||
<Dashboard
|
||||
data={aggregatedData}
|
||||
contextData={contextAggregatedData} // Pass context data
|
||||
/>
|
||||
) : view === 'table' ? (
|
||||
<DataGrid data={filteredData} />
|
||||
) : ( // New Top Movers View
|
||||
<TopMoversPage
|
||||
filteredData={filteredData} // Pass globally filtered data
|
||||
contextData={contextAggregatedData}
|
||||
/>
|
||||
)}
|
||||
{view === 'table' && <DataGrid data={filteredData} />}
|
||||
{view === 'movers' && <TopMovers data={filteredData} />}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</main>
|
||||
|
||||
{/* Chat Assistant - Now with API Key Props */}
|
||||
<AIChat
|
||||
onSendMessage={handleAskGemini}
|
||||
isOpen={isChatOpen}
|
||||
setIsOpen={setIsChatOpen}
|
||||
apiKey={apiKey}
|
||||
onApiKeyChange={handleApiKeyChange}
|
||||
/>
|
||||
{/* Chat Assistant */}
|
||||
<AIChat onSendMessage={handleAskGemini} isOpen={isChatOpen} setIsOpen={setIsChatOpen} />
|
||||
|
||||
{/* DATA MODAL */}
|
||||
{isDataModalOpen && (
|
||||
@@ -413,4 +372,4 @@ const App: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
export default App;
|
||||
|
||||
Reference in New Issue
Block a user