From ceecbc354d5fd4869f5724b811e8c42e05473226 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Faleiros?= Date: Wed, 20 May 2026 10:27:04 -0300 Subject: [PATCH] feat: add CSV export functionality to Clients and Products pages --- src/dataService.ts | 33 +++++++++++++++++++++++++++++++++ src/pages/Clients.tsx | 13 +++++++++++-- src/pages/Products.tsx | 13 +++++++++++-- 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/src/dataService.ts b/src/dataService.ts index 5f442d8..4d0aeee 100644 --- a/src/dataService.ts +++ b/src/dataService.ts @@ -69,3 +69,36 @@ export const parseOrderDate = (dateStr: string): Date => { const fallback = new Date(dateStr); 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); + } +}; diff --git a/src/pages/Clients.tsx b/src/pages/Clients.tsx index f7d2f44..660c97e 100644 --- a/src/pages/Clients.tsx +++ b/src/pages/Clients.tsx @@ -1,8 +1,8 @@ import { useMemo, useState, useEffect } from 'react'; 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 { parseOrderDate } from '../dataService'; +import { parseOrderDate, exportToCSV } from '../dataService'; import DateRangePicker from '../components/DateRangePicker'; type SortOption = 'recent' | 'spent_desc' | 'spent_asc' | 'items_desc' | 'items_asc'; @@ -124,6 +124,15 @@ 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" /> + + diff --git a/src/pages/Products.tsx b/src/pages/Products.tsx index 608e0c8..e3af625 100644 --- a/src/pages/Products.tsx +++ b/src/pages/Products.tsx @@ -1,9 +1,9 @@ import { useMemo, useState, useEffect } from 'react'; 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 type { OrderData, DateRange } from '../types'; -import { parseOrderDate } from '../dataService'; +import { parseOrderDate, exportToCSV } from '../dataService'; const Products = () => { const { dateRange, setDateRange, ordersData } = useOutletContext<{ @@ -89,6 +89,15 @@ 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" /> + +