Spaces:
Running
Running
| import { useState } from 'react'; | |
| import { useTranslation } from 'react-i18next'; | |
| import { | |
| FileSpreadsheet, | |
| FolderArchive, | |
| ArrowRight, | |
| RefreshCw, | |
| X, | |
| } from 'lucide-react'; | |
| import * as XLSX from 'xlsx'; | |
| import { Button } from '@/components/ui/button'; | |
| import { Progress } from '@/components/ui/progress'; | |
| import { cn } from '@/lib/utils'; | |
| import { api } from '@/services/api'; | |
| interface DownloadJob { | |
| fileName: string; | |
| progress: number; | |
| processedFiles: number; | |
| totalFiles: number; | |
| } | |
| export default function ExportSection() { | |
| const { t } = useTranslation(); | |
| const [activeDownload, setActiveDownload] = useState<DownloadJob | null>(null); | |
| const handleCancelDownload = () => { | |
| setActiveDownload(null); | |
| }; | |
| const handleExportExcel = async () => { | |
| const dateStr = new Date().toISOString().split('T')[0]; | |
| const fileName = `ICC_Transactions_${dateStr}.xlsx`; | |
| setActiveDownload({ fileName, progress: 10, processedFiles: 0, totalFiles: 0 }); | |
| try { | |
| // Use fast export endpoint — no pagination overhead | |
| const result = await api.get<{ transactions: any[]; total: number }>('/transactions/export'); | |
| const rows = result.transactions; | |
| setActiveDownload({ fileName, progress: 50, processedFiles: rows.length, totalFiles: rows.length }); | |
| const wsData = rows.map((tx: any) => ({ | |
| [t('table.date')]: tx.date ? new Date(tx.date).toLocaleDateString('fr-CA', { timeZone: 'America/Toronto' }) : '', | |
| [t('table.sender')]: tx.sender || '', | |
| [t('table.envelopeNumber')]: tx.envelopeNumber || '', | |
| [t('table.amount')]: tx.amount, | |
| 'Currency': tx.currency || 'CAD', | |
| [t('table.reference')]: tx.reference || '', | |
| [t('table.branch')]: tx.branch || 'Montreal', | |
| [t('table.status')]: tx.reviewed ? t('status.verified') : t('status.notVerified'), | |
| 'Email': tx.recipientEmail || '', | |
| 'Message': tx.message || '', | |
| })); | |
| setActiveDownload({ fileName, progress: 80, processedFiles: rows.length, totalFiles: rows.length }); | |
| const ws = XLSX.utils.json_to_sheet(wsData); | |
| ws['!cols'] = [ | |
| { wch: 12 }, // Date | |
| { wch: 25 }, // Sender | |
| { wch: 16 }, // Envelope Number | |
| { wch: 12 }, // Amount | |
| { wch: 6 }, // Currency | |
| { wch: 20 }, // Reference | |
| { wch: 20 }, // Branch | |
| { wch: 12 }, // Status | |
| { wch: 35 }, // Email | |
| { wch: 30 }, // Message | |
| ]; | |
| const wb = XLSX.utils.book_new(); | |
| XLSX.utils.book_append_sheet(wb, ws, 'Transactions'); | |
| XLSX.writeFile(wb, fileName); | |
| setActiveDownload({ fileName, progress: 100, processedFiles: rows.length, totalFiles: rows.length }); | |
| setTimeout(() => setActiveDownload(null), 2000); | |
| } catch (err) { | |
| console.error('Export failed:', err); | |
| setActiveDownload(null); | |
| } | |
| }; | |
| const handleExportPdf = () => { | |
| // TODO: Trigger PDF batch download | |
| console.log('Export PDF batch'); | |
| }; | |
| return ( | |
| <div className="mt-4 pb-12"> | |
| <h2 className="text-xl font-bold text-foreground mb-6 flex items-center gap-2"> | |
| <span className="w-1.5 h-6 bg-primary rounded-full" /> | |
| {t('reports.export.title')} | |
| </h2> | |
| <div className="bg-card rounded-xl border border-border p-6 md:p-8 shadow-[0_1px_3px_0_rgba(0,0,0,0.1),0_1px_2px_0_rgba(0,0,0,0.06)]"> | |
| {/* Active Download Progress */} | |
| {activeDownload && ( | |
| <div className="flex flex-col md:flex-row gap-8 items-start md:items-center justify-between bg-slate-50 p-6 rounded-xl border border-slate-100 mb-8"> | |
| <div className="flex-1 w-full"> | |
| <div className="flex items-center justify-between mb-3"> | |
| <div className="flex items-center gap-2"> | |
| <div className="bg-primary/10 p-1.5 rounded-full"> | |
| <RefreshCw className="h-3.5 w-3.5 text-primary animate-spin" /> | |
| </div> | |
| <span className="text-sm font-bold text-foreground"> | |
| {t('reports.export.downloading')} | |
| </span> | |
| </div> | |
| <span className="text-sm font-bold text-primary"> | |
| {activeDownload.progress}% | |
| </span> | |
| </div> | |
| <Progress value={activeDownload.progress} className="h-2 mb-2" /> | |
| <p className="text-xs text-muted-foreground font-medium"> | |
| {t('reports.export.generating')}{' '} | |
| <span className="text-foreground font-bold"> | |
| {activeDownload.fileName} | |
| </span>{' '} | |
| ({activeDownload.processedFiles}/{activeDownload.totalFiles}{' '} | |
| {t('reports.export.filesProcessed')}) | |
| </p> | |
| </div> | |
| <div className="flex gap-2 w-full md:w-auto"> | |
| <Button | |
| variant="ghost" | |
| size="sm" | |
| className="w-full md:w-auto text-red-600 hover:bg-red-50 hover:text-red-700 font-semibold" | |
| onClick={handleCancelDownload} | |
| > | |
| <X className="h-3.5 w-3.5 mr-1" /> | |
| {t('common.cancel')} | |
| </Button> | |
| </div> | |
| </div> | |
| )} | |
| {/* Export Cards */} | |
| <div className="grid grid-cols-1 md:grid-cols-2 gap-6"> | |
| {/* Excel Export */} | |
| <div | |
| className="group relative flex flex-col p-6 rounded-xl border border-slate-200 hover:border-green-400 hover:shadow-md transition-all cursor-pointer bg-white" | |
| onClick={handleExportExcel} | |
| > | |
| <div className="flex items-start justify-between mb-4"> | |
| <div className="p-3 bg-green-50 rounded-lg text-green-600 ring-1 ring-green-100 group-hover:bg-green-100 transition-colors"> | |
| <FileSpreadsheet className="h-7 w-7" /> | |
| </div> | |
| <div className="p-2 rounded-full hover:bg-slate-50 transition-colors"> | |
| <ArrowRight className="h-5 w-5 text-slate-300 group-hover:text-green-500 transition-colors" /> | |
| </div> | |
| </div> | |
| <h3 className="text-lg font-bold text-foreground mb-2 group-hover:text-green-700 transition-colors"> | |
| {t('reports.export.excelTitle')} | |
| </h3> | |
| <p className="text-sm text-muted-foreground mb-8 leading-relaxed"> | |
| {t('reports.export.excelDescription')} | |
| </p> | |
| <Button | |
| variant="outline" | |
| className={cn( | |
| 'mt-auto w-full py-3 border-2 border-slate-100 font-bold', | |
| 'group-hover:border-green-500 group-hover:text-green-600 transition-all' | |
| )} | |
| > | |
| {t('reports.export.generateReport')} | |
| </Button> | |
| </div> | |
| {/* PDF Batch Export */} | |
| <div | |
| className="group relative flex flex-col p-6 rounded-xl border border-slate-200 hover:border-primary/50 hover:shadow-md transition-all cursor-pointer bg-white" | |
| onClick={handleExportPdf} | |
| > | |
| <div className="flex items-start justify-between mb-4"> | |
| <div className="p-3 bg-blue-50 rounded-lg text-primary ring-1 ring-blue-100 group-hover:bg-blue-100 transition-colors"> | |
| <FolderArchive className="h-7 w-7" /> | |
| </div> | |
| <div className="p-2 rounded-full hover:bg-slate-50 transition-colors"> | |
| <ArrowRight className="h-5 w-5 text-slate-300 group-hover:text-primary transition-colors" /> | |
| </div> | |
| </div> | |
| <h3 className="text-lg font-bold text-foreground mb-2 group-hover:text-primary transition-colors"> | |
| {t('reports.export.pdfTitle')} | |
| </h3> | |
| <p className="text-sm text-muted-foreground mb-8 leading-relaxed"> | |
| {t('reports.export.pdfDescription')} | |
| </p> | |
| <Button className="mt-auto w-full py-3 font-bold shadow-lg shadow-blue-500/20"> | |
| {t('reports.export.startDownload')} | |
| </Button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } | |