mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 13:05:24 +02:00
Feature: Added Excel export to Ads and Forecast views
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
import React, { useMemo, useState } from 'react';
|
import React, { useMemo, useState, useCallback } from 'react';
|
||||||
|
import * as XLSX from 'xlsx';
|
||||||
import { CombinedKPIs, FilterState } from '../types';
|
import { CombinedKPIs, FilterState } from '../types';
|
||||||
|
import { DownloadIcon } from './Icons';
|
||||||
|
|
||||||
interface AdsPerformanceProps {
|
interface AdsPerformanceProps {
|
||||||
data: CombinedKPIs[];
|
data: CombinedKPIs[];
|
||||||
@@ -215,6 +217,44 @@ const AdsPerformance: React.FC<AdsPerformanceProps> = ({ data, filters, top50Ran
|
|||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleExportExcel = useCallback(() => {
|
||||||
|
const exportData = filteredAndSorted.map(row => {
|
||||||
|
const rowData: any = {
|
||||||
|
ASIN: row.asin,
|
||||||
|
SKU: row.sku,
|
||||||
|
Title: row.title,
|
||||||
|
'Total Sales': Math.round(row.salesTotal),
|
||||||
|
'Ads Sales': Math.round(row.salesAds),
|
||||||
|
'Organic Sales': Math.round(row.salesOrganic),
|
||||||
|
'Ad Spend': Math.round(row.cost),
|
||||||
|
Impressions: row.impressions,
|
||||||
|
Clicks: row.clicks,
|
||||||
|
Orders: row.conversions,
|
||||||
|
CPC: Number(row.cpc.toFixed(2)),
|
||||||
|
'CTR (%)': Number(row.ctr.toFixed(2)),
|
||||||
|
'CVR (%)': Number(row.cvrUnits.toFixed(2)),
|
||||||
|
'ACOS (%)': Number(row.acos.toFixed(2)),
|
||||||
|
ROAS: Number(row.roas.toFixed(2)),
|
||||||
|
'TACOS (%)': Number(row.tacos.toFixed(2)),
|
||||||
|
'Glance Views': row.glanceViews || 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (row.prevData) {
|
||||||
|
rowData['Prev Total Sales'] = Math.round(row.prevData.salesTotal);
|
||||||
|
rowData['Prev Ads Sales'] = Math.round(row.prevData.salesAds);
|
||||||
|
rowData['Prev Ad Spend'] = Math.round(row.prevData.cost);
|
||||||
|
rowData['Growth Sales (%)'] = row.prevData.salesTotal > 0 ? Number((((row.salesTotal - row.prevData.salesTotal) / row.prevData.salesTotal) * 100).toFixed(1)) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rowData;
|
||||||
|
});
|
||||||
|
|
||||||
|
const ws = XLSX.utils.json_to_sheet(exportData);
|
||||||
|
const wb = XLSX.utils.book_new();
|
||||||
|
XLSX.utils.book_append_sheet(wb, ws, 'Ads Performance');
|
||||||
|
XLSX.writeFile(wb, `Ads_Performance_Export_${new Date().toISOString().slice(0, 10)}.xlsx`);
|
||||||
|
}, [filteredAndSorted]);
|
||||||
|
|
||||||
const formatCurrency = (val: number) =>
|
const formatCurrency = (val: number) =>
|
||||||
`$${Math.round(val).toLocaleString('de-DE')}`;
|
`$${Math.round(val).toLocaleString('de-DE')}`;
|
||||||
|
|
||||||
@@ -306,11 +346,19 @@ const AdsPerformance: React.FC<AdsPerformanceProps> = ({ data, filters, top50Ran
|
|||||||
)}
|
)}
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search ASIN, SKU or Title..."
|
placeholder="Search..."
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
className="bg-[#0B0E14] border border-white/10 rounded-lg px-3 py-1.5 text-xs text-white focus:outline-none focus:ring-1 focus:ring-indigo-500 w-64"
|
className="bg-[#0B0E14] border border-white/10 rounded-lg px-3 py-1.5 text-xs text-white focus:outline-none focus:ring-1 focus:ring-indigo-500 w-48"
|
||||||
/>
|
/>
|
||||||
|
<button
|
||||||
|
onClick={handleExportExcel}
|
||||||
|
className="flex items-center gap-2 px-3 py-1.5 bg-slate-800 hover:bg-slate-700 text-slate-200 rounded-lg text-xs font-bold border border-white/10 transition-all shadow-lg active:scale-95"
|
||||||
|
title="Export to Excel"
|
||||||
|
>
|
||||||
|
<DownloadIcon />
|
||||||
|
<span>Export</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
import React, { useMemo, useState, useEffect, useCallback } from 'react';
|
||||||
import React, { useMemo, useState, useEffect } from 'react';
|
import * as XLSX from 'xlsx';
|
||||||
import { ProductForecastData, FilterState } from '../types';
|
import { ProductForecastData, FilterState } from '../types';
|
||||||
|
import { DownloadIcon } from './Icons';
|
||||||
import {
|
import {
|
||||||
BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer,
|
BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer,
|
||||||
LineChart, Line, Legend, ComposedChart, Area
|
LineChart, Line, Legend, ComposedChart, Area
|
||||||
@@ -168,6 +169,47 @@ const ForecastView: React.FC<ForecastViewProps> = ({ data, filters, top50Ranking
|
|||||||
return result;
|
return result;
|
||||||
}, [filteredProducts, showOnlyTop50, top50Mode, top50Ranking]);
|
}, [filteredProducts, showOnlyTop50, top50Mode, top50Ranking]);
|
||||||
|
|
||||||
|
const handleExportExcel = useCallback(() => {
|
||||||
|
const exportData = sortedProducts.map(p => {
|
||||||
|
const rowData: any = {
|
||||||
|
ASIN: p.asin,
|
||||||
|
SKU: p.sku,
|
||||||
|
Title: p.title,
|
||||||
|
'Product Line': p.line,
|
||||||
|
'Annual Forecast': Math.round(p.annualForecast),
|
||||||
|
'Annual Actual': Math.round(p.actualUnits),
|
||||||
|
'Annual Fulfillment (%)': p.annualForecast > 0 ? Number(((p.actualUnits / p.annualForecast) * 100).toFixed(1)) : 0,
|
||||||
|
'Period Forecast': 0,
|
||||||
|
'Period Actual': 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
p.monthlyData.forEach(md => {
|
||||||
|
if (activeMonths.includes(md.month)) {
|
||||||
|
rowData['Period Forecast'] += md.forecastUnits;
|
||||||
|
rowData['Period Actual'] += md.actualUnits;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
rowData['Period Fulfillment (%)'] = rowData['Period Forecast'] > 0
|
||||||
|
? Number(((rowData['Period Actual'] / rowData['Period Forecast']) * 100).toFixed(1))
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
// Add monthly details
|
||||||
|
MONTH_ORDER.forEach(m => {
|
||||||
|
const md = p.monthlyData.find(d => d.month === m);
|
||||||
|
rowData[`${m} FC`] = md ? Math.round(md.forecastUnits) : 0;
|
||||||
|
rowData[`${m} ACT`] = md ? Math.round(md.actualUnits) : 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
return rowData;
|
||||||
|
});
|
||||||
|
|
||||||
|
const ws = XLSX.utils.json_to_sheet(exportData);
|
||||||
|
const wb = XLSX.utils.book_new();
|
||||||
|
XLSX.utils.book_append_sheet(wb, ws, 'Forecast Overview');
|
||||||
|
XLSX.writeFile(wb, `Forecast_Export_${new Date().toISOString().slice(0, 10)}.xlsx`);
|
||||||
|
}, [sortedProducts, activeMonths]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-6 space-y-6 max-w-7xl mx-auto animate-fade-in pb-24">
|
<div className="p-6 space-y-6 max-w-7xl mx-auto animate-fade-in pb-24">
|
||||||
|
|
||||||
@@ -299,6 +341,14 @@ const ForecastView: React.FC<ForecastViewProps> = ({ data, filters, top50Ranking
|
|||||||
/>
|
/>
|
||||||
<svg className="absolute left-3 top-2.5 w-4 h-4 text-slate-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /></svg>
|
<svg className="absolute left-3 top-2.5 w-4 h-4 text-slate-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /></svg>
|
||||||
</div>
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={handleExportExcel}
|
||||||
|
className="flex items-center gap-2 px-3 py-1.5 bg-slate-800 hover:bg-slate-700 text-slate-200 rounded-lg text-xs font-bold border border-white/10 transition-all shadow-lg active:scale-95"
|
||||||
|
title="Export to Excel"
|
||||||
|
>
|
||||||
|
<DownloadIcon />
|
||||||
|
<span>Export</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto">
|
||||||
|
|||||||
Reference in New Issue
Block a user