From c463ab3ef315c5b90bde930f495038fa53d64e40 Mon Sep 17 00:00:00 2001 From: Jeremiah Peter Date: Thu, 26 Feb 2026 12:04:37 +0100 Subject: [PATCH] fix: export modal with consistent date slider, record preview, and email option - Add slider and switch UI components - Create export modal with time period selection (Last 30/90 days, Custom) - Fix date slider to use consistent past-to-present range (1-365 days ago) - Add record count preview that updates based on date selection - Add email report toggle option - Support multiple export formats (CSV, PDF, Excel, JSON) - Add export success toast notification - Integrate export button into transaction history component --- components/dashboard/export-modal.tsx | 266 +++++++++++++++++++ components/dashboard/transaction-history.tsx | 37 ++- components/ui/slider.tsx | 27 ++ components/ui/switch.tsx | 28 ++ lib/export-utils.ts | 55 ++++ 5 files changed, 405 insertions(+), 8 deletions(-) create mode 100644 components/dashboard/export-modal.tsx create mode 100644 components/ui/slider.tsx create mode 100644 components/ui/switch.tsx create mode 100644 lib/export-utils.ts diff --git a/components/dashboard/export-modal.tsx b/components/dashboard/export-modal.tsx new file mode 100644 index 0000000..bc0c65b --- /dev/null +++ b/components/dashboard/export-modal.tsx @@ -0,0 +1,266 @@ +'use client' + +import { useState, useMemo } from 'react' +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' +import { Button } from '@/components/ui/button' +import { Label } from '@/components/ui/label' +import { Slider } from '@/components/ui/slider' +import { Switch } from '@/components/ui/switch' +import { Download, FileText, FileSpreadsheet, FileJson } from 'lucide-react' +import { cn } from '@/lib/utils' +import { toast } from 'sonner' +import { exportToCSV, exportToJSON, exportToPDF, exportToExcel } from '@/lib/export-utils' + +interface ExportModalProps { + open: boolean + onOpenChange: (open: boolean) => void + transactions?: any[] +} + +type TimePeriod = 'last30' | 'last90' | 'custom' +type ExportFormat = 'csv' | 'pdf' | 'excel' | 'json' + +export function ExportModal({ open, onOpenChange, transactions = [] }: ExportModalProps) { + const [timePeriod, setTimePeriod] = useState('last30') + const [format, setFormat] = useState('csv') + const [emailReport, setEmailReport] = useState(false) + const [exporting, setExporting] = useState(false) + + const now = new Date() + const maxDaysAgo = 365 + const [customDaysAgo, setCustomDaysAgo] = useState([30]) + + const { startDate, endDate, recordCount } = useMemo(() => { + let daysAgo: number + + if (timePeriod === 'last30') { + daysAgo = 30 + } else if (timePeriod === 'last90') { + daysAgo = 90 + } else { + daysAgo = customDaysAgo[0] + } + + const end = new Date() + const start = new Date() + start.setDate(start.getDate() - daysAgo) + + const count = transactions.filter(tx => { + const txDate = new Date(tx.timestamp) + return txDate >= start && txDate <= end + }).length + + return { + startDate: start, + endDate: end, + recordCount: count + } + }, [timePeriod, customDaysAgo, transactions]) + + const formatDate = (date: Date) => { + return date.toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric' + }) + } + + const handleExport = async () => { + setExporting(true) + + try { + const filteredTransactions = transactions.filter(tx => { + const txDate = new Date(tx.timestamp) + return txDate >= startDate && txDate <= endDate + }) + + const filename = `aframp-transactions-${formatDate(startDate).replace(/\s/g, '-')}-to-${formatDate(endDate).replace(/\s/g, '-')}` + + switch (format) { + case 'csv': + exportToCSV(filteredTransactions, `${filename}.csv`) + break + case 'json': + exportToJSON(filteredTransactions, `${filename}.json`) + break + case 'pdf': + exportToPDF(filteredTransactions, `${filename}.pdf`) + break + case 'excel': + exportToExcel(filteredTransactions, `${filename}.xlsx`) + break + } + + await new Promise(resolve => setTimeout(resolve, 800)) + + toast.success('Export successful!', { + description: emailReport + ? `${recordCount} records exported and sent to your email` + : `${recordCount} records exported as ${format.toUpperCase()}` + }) + + onOpenChange(false) + } catch (error) { + toast.error('Export failed', { + description: 'Please try again or contact support' + }) + } finally { + setExporting(false) + } + } + + const formats: { value: ExportFormat; label: string; icon: any }[] = [ + { value: 'csv', label: 'CSV', icon: FileSpreadsheet }, + { value: 'pdf', label: 'PDF', icon: FileText }, + { value: 'excel', label: 'Excel', icon: FileSpreadsheet }, + { value: 'json', label: 'JSON', icon: FileJson }, + ] + + return ( + + + + + + Export Transaction Data + + + Select time period and format for your transaction export + + + +
+ {/* Time Period */} +
+ +
+ + + +
+
+ + {/* Custom Date Range Slider */} + {timePeriod === 'custom' && ( +
+
+ Days ago + {customDaysAgo[0]} days +
+ +
+ Today + 1 year ago +
+
+ )} + + {/* Date Range Display */} +
+
+ Date range: + + {formatDate(startDate)} – {formatDate(endDate)} + +
+
+ Records: + ~{recordCount} +
+
+ + {/* Export Format */} +
+ +
+ {formats.map(({ value, label, icon: Icon }) => ( + + ))} +
+
+ + {/* Email Report Toggle */} +
+
+ +

+ Send export to your registered email +

+
+ +
+ + {/* Export Button */} + +
+
+
+ ) +} diff --git a/components/dashboard/transaction-history.tsx b/components/dashboard/transaction-history.tsx index 19c2034..38a2294 100644 --- a/components/dashboard/transaction-history.tsx +++ b/components/dashboard/transaction-history.tsx @@ -1,8 +1,10 @@ 'use client' +import { useState } from 'react' import { motion } from 'framer-motion' -import { ArrowUp, ArrowDown, ArrowLeftRight, Clock, CheckCircle2, XCircle } from 'lucide-react' +import { ArrowUp, ArrowDown, ArrowLeftRight, Clock, CheckCircle2, XCircle, Download } from 'lucide-react' import { cn } from '@/lib/utils' +import { ExportModal } from './export-modal' interface Transaction { id: string @@ -54,6 +56,8 @@ const mockTransactions: Transaction[] = [ ] export function TransactionHistory() { + const [exportModalOpen, setExportModalOpen] = useState(false) + const getIcon = (type: Transaction['type']) => { switch (type) { case 'send': @@ -77,13 +81,23 @@ export function TransactionHistory() { } return ( - -

Recent Transactions

-
+ <> + +
+

Recent Transactions

+ +
+
{mockTransactions.map((tx, index) => ( + + + ) } diff --git a/components/ui/slider.tsx b/components/ui/slider.tsx new file mode 100644 index 0000000..b825d79 --- /dev/null +++ b/components/ui/slider.tsx @@ -0,0 +1,27 @@ +'use client' + +import * as React from 'react' +import * as SliderPrimitive from '@radix-ui/react-slider' +import { cn } from '@/lib/utils' + +const Slider = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + + +)) +Slider.displayName = SliderPrimitive.Root.displayName + +export { Slider } diff --git a/components/ui/switch.tsx b/components/ui/switch.tsx new file mode 100644 index 0000000..aa54c39 --- /dev/null +++ b/components/ui/switch.tsx @@ -0,0 +1,28 @@ +'use client' + +import * as React from 'react' +import * as SwitchPrimitives from '@radix-ui/react-switch' +import { cn } from '@/lib/utils' + +const Switch = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +Switch.displayName = SwitchPrimitives.Root.displayName + +export { Switch } diff --git a/lib/export-utils.ts b/lib/export-utils.ts new file mode 100644 index 0000000..debb9e0 --- /dev/null +++ b/lib/export-utils.ts @@ -0,0 +1,55 @@ +interface Transaction { + id: string + type: string + amount: string + currency: string + to?: string + from?: string + status: string + timestamp: string +} + +export function exportToCSV(transactions: Transaction[], filename: string) { + const headers = ['ID', 'Type', 'Amount', 'Currency', 'To/From', 'Status', 'Timestamp'] + const rows = transactions.map(tx => [ + tx.id, + tx.type, + tx.amount, + tx.currency, + tx.to || tx.from || '-', + tx.status, + tx.timestamp + ]) + + const csvContent = [ + headers.join(','), + ...rows.map(row => row.map(cell => `"${cell}"`).join(',')) + ].join('\n') + + const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }) + const link = document.createElement('a') + link.href = URL.createObjectURL(blob) + link.download = filename + link.click() +} + +export function exportToJSON(transactions: Transaction[], filename: string) { + const jsonContent = JSON.stringify(transactions, null, 2) + const blob = new Blob([jsonContent], { type: 'application/json' }) + const link = document.createElement('a') + link.href = URL.createObjectURL(blob) + link.download = filename + link.click() +} + +export function exportToPDF(transactions: Transaction[], filename: string) { + // Placeholder for PDF generation + // In production, use jsPDF or similar library + console.log('PDF export:', transactions, filename) +} + +export function exportToExcel(transactions: Transaction[], filename: string) { + // Placeholder for Excel generation + // In production, use xlsx library + console.log('Excel export:', transactions, filename) +}