mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 13:05:24 +02:00
Refactor: Switched all exports to .xlsx and enhanced Weekly Grid export
This commit is contained in:
@@ -3,7 +3,7 @@ import {
|
||||
LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer
|
||||
} from 'recharts';
|
||||
import { SalesRecord, PivotRow, AdsRecord, CombinedKPIs } from '../types';
|
||||
import { pivotSalesData, generateCSV, aggregateForTimeSeries, aggregateForComparisonTimeSeries, applyPanEUGrouping } from '../services/dataProcessor';
|
||||
import { pivotSalesData, generateXLSX, aggregateForTimeSeries, aggregateForComparisonTimeSeries, applyPanEUGrouping } from '../services/dataProcessor';
|
||||
import { DownloadIcon, FunnelIcon, CloseIcon, ChartIcon, TrendingIcon } from './Icons';
|
||||
|
||||
interface DataGridProps {
|
||||
@@ -551,7 +551,7 @@ const DataGrid: React.FC<DataGridProps> = ({ data, hasCustomerFilter, adsData =
|
||||
};
|
||||
|
||||
const handleExport = () => {
|
||||
generateCSV(processedRows, effectiveDimensions, years);
|
||||
generateXLSX(processedRows, effectiveDimensions, years);
|
||||
};
|
||||
|
||||
const addFilter = () => {
|
||||
|
||||
+14
-34
@@ -1,6 +1,6 @@
|
||||
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import { SalesRecord } from './types';
|
||||
import React, { useState, useMemo, useCallback } from 'react';
|
||||
import * as XLSX from 'xlsx';
|
||||
import { SalesRecord } from '../types';
|
||||
import { DownloadIcon } from './Icons';
|
||||
|
||||
interface TopMoversProps {
|
||||
@@ -34,45 +34,25 @@ const MoversTable: React.FC<{
|
||||
return val.toLocaleString('de-DE');
|
||||
};
|
||||
|
||||
const handleExport = () => {
|
||||
const handleExport = useCallback(() => {
|
||||
if (!data || data.length === 0) return;
|
||||
|
||||
// Helper to force Comma as thousands separator (US Locale)
|
||||
const formatForCSV = (val: number) => {
|
||||
return val.toLocaleString('de-DE', {
|
||||
useGrouping: true,
|
||||
minimumFractionDigits: metric === 'sellOut' ? 2 : 0,
|
||||
maximumFractionDigits: metric === 'sellOut' ? 2 : 0,
|
||||
});
|
||||
};
|
||||
|
||||
// Prepare data for CSV
|
||||
const csvData = data.map((item, index) => ({
|
||||
const exportData = data.map((item, index) => ({
|
||||
Rank: index + 1,
|
||||
Title: item.title,
|
||||
SKU: item.sku,
|
||||
'Product Line': item.line,
|
||||
[`${previousYear} ${metric === 'sellOut' ? 'Sell Out' : 'Units'}`]: formatForCSV(item.previousValue),
|
||||
[`${currentYear} ${metric === 'sellOut' ? 'Sell Out' : 'Units'}`]: formatForCSV(item.currentValue),
|
||||
'Difference': formatForCSV(item.diff),
|
||||
'% Change': `${item.pct.toFixed(2)}%`
|
||||
[`${previousYear} ${metric === 'sellOut' ? 'Sell Out' : 'Units'}`]: item.previousValue,
|
||||
[`${currentYear} ${metric === 'sellOut' ? 'Sell Out' : 'Units'}`]: item.currentValue,
|
||||
'Difference': item.diff,
|
||||
'% Change': Number(item.pct.toFixed(2))
|
||||
}));
|
||||
|
||||
// Generate CSV string
|
||||
// @ts-ignore - Papa is loaded globally via CDN
|
||||
const csv = Papa.unparse(csvData);
|
||||
|
||||
// Create download link
|
||||
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
const filename = `${title.replace(/\s+/g, '_')}_${currentYear}_vs_${previousYear}.csv`;
|
||||
link.setAttribute('download', filename);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
};
|
||||
const ws = XLSX.utils.json_to_sheet(exportData);
|
||||
const wb = XLSX.utils.book_new();
|
||||
XLSX.utils.book_append_sheet(wb, ws, 'Top Movers');
|
||||
XLSX.writeFile(wb, `${title.replace(/\s+/g, '_')}_${currentYear}_vs_${previousYear}.xlsx`);
|
||||
}, [data, title, currentYear, previousYear, metric]);
|
||||
|
||||
const colorClass = type === 'growth' ? 'text-emerald-400' : 'text-rose-400';
|
||||
const bgClass = type === 'growth' ? 'bg-emerald-500/10 text-emerald-400' : 'bg-rose-500/10 text-rose-400';
|
||||
|
||||
@@ -246,15 +246,30 @@ const WeeklyGrid: React.FC<WeeklyGridProps> = ({ data, top50Ranking, onDrillDown
|
||||
allWeeks.forEach(week => {
|
||||
rowData[`${week} Units`] = row.unitsByWeek[week] || 0;
|
||||
rowData[`${week} Spend`] = row.spendByWeek[week] || 0;
|
||||
rowData[`${week} GV`] = row.gvByWeek[week] || 0;
|
||||
});
|
||||
return rowData;
|
||||
});
|
||||
|
||||
// Add Totals row
|
||||
const totalsRow: any = {
|
||||
SKU: 'TOTALS',
|
||||
ASIN: '',
|
||||
Title: 'ALL FILTERED PRODUCTS',
|
||||
Line: '',
|
||||
};
|
||||
allWeeks.forEach(week => {
|
||||
totalsRow[`${week} Units`] = weekTotals[week]?.units || 0;
|
||||
totalsRow[`${week} Spend`] = weekTotals[week]?.spend || 0;
|
||||
totalsRow[`${week} GV`] = weekTotals[week]?.gv || 0;
|
||||
});
|
||||
exportData.push(totalsRow);
|
||||
|
||||
const ws = XLSX.utils.json_to_sheet(exportData);
|
||||
const wb = XLSX.utils.book_new();
|
||||
XLSX.utils.book_append_sheet(wb, ws, 'Weekly Sales');
|
||||
XLSX.writeFile(wb, `Weekly_Sales_Export_${new Date().toISOString().slice(0, 10)}.xlsx`);
|
||||
}, [sortedRows, allWeeks]);
|
||||
}, [sortedRows, allWeeks, weekTotals]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 animate-fade-in">
|
||||
|
||||
+21
-36
@@ -1270,18 +1270,19 @@ export const pivotSalesData = (data: any[], dimensions: string[] = ['title', 'cu
|
||||
};
|
||||
};
|
||||
|
||||
export const generateCSV = (rows: PivotRow[], dimensions: string[], years: string[]) => {
|
||||
// Flatten PivotRows into CSV-friendly objects
|
||||
export const generateXLSX = (rows: PivotRow[], dimensions: string[], years: string[]) => {
|
||||
// Flatten PivotRows into Excel-friendly objects
|
||||
const flatData = rows.map(row => {
|
||||
const flatRow: any = {};
|
||||
|
||||
// Add Dimension Columns
|
||||
dimensions.forEach(dim => {
|
||||
// Map internal key to nicer Header if needed
|
||||
let header = dim;
|
||||
if (dim === 'line') header = 'Product Line';
|
||||
if (dim === 'title') header = 'Title';
|
||||
if (dim === 'customer') header = 'Customer';
|
||||
if (dim === 'sku' || dim === 'SKU') header = 'SKU';
|
||||
if (dim === 'asin' || dim === 'ASIN') header = 'ASIN';
|
||||
|
||||
flatRow[header] = row[dim as keyof PivotRow];
|
||||
});
|
||||
@@ -1306,22 +1307,13 @@ export const generateCSV = (rows: PivotRow[], dimensions: string[], years: strin
|
||||
return flatRow;
|
||||
});
|
||||
|
||||
// Generate CSV string
|
||||
// @ts-ignore
|
||||
const csv = Papa.unparse(flatData);
|
||||
|
||||
// Trigger Download
|
||||
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.setAttribute('download', `sales_export_${new Date().toISOString().split('T')[0]}.csv`);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
const ws = XLSX.utils.json_to_sheet(flatData);
|
||||
const wb = XLSX.utils.book_new();
|
||||
XLSX.utils.book_append_sheet(wb, ws, 'Business Data');
|
||||
XLSX.writeFile(wb, `Business_Data_Export_${new Date().toISOString().slice(0, 10)}.xlsx`);
|
||||
};
|
||||
|
||||
export const generateItemMoversCSV = (
|
||||
export const generateItemMoversXLSX = (
|
||||
data: ItemGrowthMetric[],
|
||||
periods: { current: string; previous: string },
|
||||
type: 'Gainers' | 'Losers'
|
||||
@@ -1331,27 +1323,20 @@ export const generateItemMoversCSV = (
|
||||
ASIN: item.asin || '-',
|
||||
'Product Title': item.title || '-',
|
||||
'Product Line': item.line || '-',
|
||||
[`Sell Out ${periods.previous}`]: item.previousYearSellOut.toLocaleString('de-DE', { minimumFractionDigits: 0, maximumFractionDigits: 0 }),
|
||||
[`Sell Out ${periods.current}`]: item.currentYearSellOut.toLocaleString('de-DE', { minimumFractionDigits: 0, maximumFractionDigits: 0 }),
|
||||
'SO Diff': item.sellOutGrowthValue.toLocaleString('de-DE', { minimumFractionDigits: 0, maximumFractionDigits: 0 }),
|
||||
'SO Growth %': item.sellOutGrowthPercentage.toLocaleString('de-DE', { minimumFractionDigits: 1, maximumFractionDigits: 1 }) + '%',
|
||||
[`Units ${periods.previous}`]: item.previousYearUnits.toLocaleString('de-DE', { minimumFractionDigits: 0, maximumFractionDigits: 0 }),
|
||||
[`Units ${periods.current}`]: item.currentYearUnits.toLocaleString('de-DE', { minimumFractionDigits: 0, maximumFractionDigits: 0 }),
|
||||
'Units Diff': item.unitsGrowthValue.toLocaleString('de-DE', { minimumFractionDigits: 0, maximumFractionDigits: 0 }),
|
||||
'Units Growth %': item.unitsGrowthPercentage.toLocaleString('de-DE', { minimumFractionDigits: 1, maximumFractionDigits: 1 }) + '%',
|
||||
[`Sell Out ${periods.previous}`]: item.previousYearSellOut,
|
||||
[`Sell Out ${periods.current}`]: item.currentYearSellOut,
|
||||
'SO Diff': item.sellOutGrowthValue,
|
||||
'SO Growth %': Number(item.sellOutGrowthPercentage.toFixed(2)),
|
||||
[`Units ${periods.previous}`]: item.previousYearUnits,
|
||||
[`Units ${periods.current}`]: item.currentYearUnits,
|
||||
'Units Diff': item.unitsGrowthValue,
|
||||
'Units Growth %': Number(item.unitsGrowthPercentage.toFixed(2)),
|
||||
}));
|
||||
|
||||
// @ts-ignore
|
||||
const csv = Papa.unparse(flatData);
|
||||
|
||||
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.setAttribute('download', `${type}_${periods.current}_vs_${periods.previous}_${new Date().toISOString().split('T')[0]}.csv`);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
const ws = XLSX.utils.json_to_sheet(flatData);
|
||||
const wb = XLSX.utils.book_new();
|
||||
XLSX.utils.book_append_sheet(wb, ws, type);
|
||||
XLSX.writeFile(wb, `${type}_${periods.current}_vs_${periods.previous}_${new Date().toISOString().split('T')[0]}.xlsx`);
|
||||
};
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user