Compare commits

..

2 Commits

Author SHA1 Message Date
Cauê Faleiros
c77da0a9d0 feat: translate CSV export headers to pt-BR and format currency values
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m3s
2026-05-20 10:33:03 -03:00
Cauê Faleiros
ceecbc354d feat: add CSV export functionality to Clients and Products pages 2026-05-20 10:27:04 -03:00
3 changed files with 73 additions and 4 deletions

View File

@@ -69,3 +69,36 @@ export const parseOrderDate = (dateStr: string): Date => {
const fallback = new Date(dateStr); const fallback = new Date(dateStr);
return isNaN(fallback.getTime()) ? new Date(0) : fallback; return isNaN(fallback.getTime()) ? new Date(0) : fallback;
}; };
export const exportToCSV = (data: any[], filename: string) => {
if (!data || !data.length) return;
const headers = Object.keys(data[0]);
const csvRows = [];
// Add headers
csvRows.push(headers.join(','));
// Add rows
for (const row of data) {
const values = headers.map(header => {
const val = row[header];
const escaped = ('' + val).replace(/"/g, '\\"');
return `"${escaped}"`;
});
csvRows.push(values.join(','));
}
const csvString = csvRows.join('\n');
const blob = new Blob([csvString], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
if (link.download !== undefined) {
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', filename);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
};

View File

@@ -1,8 +1,8 @@
import { useMemo, useState, useEffect } from 'react'; import { useMemo, useState, useEffect } from 'react';
import { Link, useOutletContext } from 'react-router-dom'; import { Link, useOutletContext } from 'react-router-dom';
import { Search, ChevronRight, Filter, ChevronLeft } from 'lucide-react'; import { Search, ChevronRight, Filter, ChevronLeft, Download } from 'lucide-react';
import type { OrderData, DateRange } from '../types'; import type { OrderData, DateRange } from '../types';
import { parseOrderDate } from '../dataService'; import { parseOrderDate, exportToCSV } from '../dataService';
import DateRangePicker from '../components/DateRangePicker'; import DateRangePicker from '../components/DateRangePicker';
type SortOption = 'recent' | 'spent_desc' | 'spent_asc' | 'items_desc' | 'items_asc'; type SortOption = 'recent' | 'spent_desc' | 'spent_asc' | 'items_desc' | 'items_asc';
@@ -124,6 +124,24 @@ const Clients = () => {
className="w-full sm:w-64 bg-dark-card border border-dark-border text-dark-text rounded-xl pl-10 pr-4 py-2.5 focus:outline-none focus:border-brand-primary hover:border-brand-primary transition-colors shadow-sm" className="w-full sm:w-64 bg-dark-card border border-dark-border text-dark-text rounded-xl pl-10 pr-4 py-2.5 focus:outline-none focus:border-brand-primary hover:border-brand-primary transition-colors shadow-sm"
/> />
</div> </div>
<button
onClick={() => {
const exportData = clientsData.map(client => ({
'Nome do Cliente': client.name,
'Total Gasto (R$)': client.totalSpent.toFixed(2).replace('.', ','),
'Produtos Comprados': client.totalItems,
'Total de Pedidos': client.orderCount,
'Última Compra': new Date(client.lastPurchase).toLocaleDateString('pt-BR')
}));
exportToCSV(exportData, `clientes_${new Date().toISOString().split('T')[0]}.csv`);
}}
className="flex items-center justify-center gap-2 bg-dark-card border border-dark-border px-4 py-2.5 rounded-xl shadow-sm hover:border-brand-primary transition-colors text-sm font-medium text-dark-text cursor-pointer"
title="Exportar para CSV"
>
<Download size={16} className="text-brand-primary" />
<span className="hidden sm:inline">Exportar</span>
</button>
</div> </div>
</div> </div>

View File

@@ -1,9 +1,9 @@
import { useMemo, useState, useEffect } from 'react'; import { useMemo, useState, useEffect } from 'react';
import { Link, useOutletContext } from 'react-router-dom'; import { Link, useOutletContext } from 'react-router-dom';
import { Search, Package, TrendingUp, ChevronLeft, ChevronRight } from 'lucide-react'; import { Search, Package, TrendingUp, ChevronLeft, ChevronRight, Download } from 'lucide-react';
import DateRangePicker from '../components/DateRangePicker'; import DateRangePicker from '../components/DateRangePicker';
import type { OrderData, DateRange } from '../types'; import type { OrderData, DateRange } from '../types';
import { parseOrderDate } from '../dataService'; import { parseOrderDate, exportToCSV } from '../dataService';
const Products = () => { const Products = () => {
const { dateRange, setDateRange, ordersData } = useOutletContext<{ const { dateRange, setDateRange, ordersData } = useOutletContext<{
@@ -89,6 +89,24 @@ const Products = () => {
className="w-full md:w-64 bg-dark-card border border-dark-border text-dark-text rounded-xl pl-10 pr-4 py-2.5 focus:outline-none focus:border-brand-primary hover:border-brand-primary transition-colors shadow-sm" className="w-full md:w-64 bg-dark-card border border-dark-border text-dark-text rounded-xl pl-10 pr-4 py-2.5 focus:outline-none focus:border-brand-primary hover:border-brand-primary transition-colors shadow-sm"
/> />
</div> </div>
<button
onClick={() => {
const exportData = productsData.map(product => ({
'ID Produto': product.id,
'Descrição': product.name,
'Preço Atual (R$)': product.lastPrice.toFixed(2).replace('.', ','),
'Total Vendido (un.)': product.totalSold,
'Receita Gerada (R$)': product.revenue.toFixed(2).replace('.', ',')
}));
exportToCSV(exportData, `produtos_${new Date().toISOString().split('T')[0]}.csv`);
}}
className="flex items-center justify-center gap-2 bg-dark-card border border-dark-border px-4 py-2.5 rounded-xl shadow-sm hover:border-brand-primary transition-colors text-sm font-medium text-dark-text cursor-pointer"
title="Exportar para CSV"
>
<Download size={16} className="text-brand-primary" />
<span className="hidden sm:inline">Exportar</span>
</button>
</div> </div>
</div> </div>