import React, { useEffect, useState, useRef } from 'react'; import { CreditCard, Receipt, TrendingUp, RefreshCw, DollarSign, AlertCircle } from 'lucide-react'; import reportService, { ReportData } from '../../features/analytics/services/reportService'; import paymentService from '../../features/payments/services/paymentService'; import invoiceService from '../../features/payments/services/invoiceService'; import type { Payment } from '../../features/payments/services/paymentService'; import type { Invoice } from '../../features/payments/services/invoiceService'; import { toast } from 'react-toastify'; import Loading from '../../shared/components/Loading'; import EmptyState from '../../shared/components/EmptyState'; import ExportButton from '../../shared/components/ExportButton'; import { formatDate } from '../../shared/utils/format'; import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency'; import { useAsync } from '../../shared/hooks/useAsync'; import { useNavigate } from 'react-router-dom'; import { logger } from '../../shared/utils/logger'; import { getPaymentStatusColor } from '../../shared/utils/paymentUtils'; const AccountantDashboardPage: React.FC = () => { const { formatCurrency } = useFormatCurrency(); const navigate = useNavigate(); const [dateRange, setDateRange] = useState({ from: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0], to: new Date().toISOString().split('T')[0], }); const [recentPayments, setRecentPayments] = useState([]); const [recentInvoices, setRecentInvoices] = useState([]); const [loadingPayments, setLoadingPayments] = useState(false); const [loadingInvoices, setLoadingInvoices] = useState(false); const paymentsAbortRef = useRef(null); const invoicesAbortRef = useRef(null); const [financialSummary, setFinancialSummary] = useState({ totalRevenue: 0, totalPayments: 0, totalInvoices: 0, pendingPayments: 0, overdueInvoices: 0, paidInvoices: 0, }); const fetchDashboardData = async () => { const response = await reportService.getReports({ from: dateRange.from, to: dateRange.to, }); return response.data; }; const { data: stats, loading, error, execute } = useAsync( fetchDashboardData, { immediate: true, onError: (error: any) => { toast.error(error.message || 'Unable to load dashboard data'); } } ); useEffect(() => { execute(); }, [dateRange]); useEffect(() => { // Cancel previous request if exists if (paymentsAbortRef.current) { paymentsAbortRef.current.abort(); } // Create new abort controller paymentsAbortRef.current = new AbortController(); const fetchPayments = async () => { try { setLoadingPayments(true); const response = await paymentService.getPayments({ page: 1, limit: 10 }); if (response.success && response.data?.payments) { setRecentPayments(response.data.payments); // Calculate financial summary const completedPayments = response.data.payments.filter((p: Payment) => p.payment_status === 'completed' || p.payment_status === 'paid'); const pendingPayments = response.data.payments.filter((p: Payment) => p.payment_status === 'pending'); const totalRevenue = completedPayments.reduce((sum: number, p: Payment) => sum + (p.amount || 0), 0); setFinancialSummary(prev => ({ ...prev, totalRevenue, totalPayments: response.data.payments.length, pendingPayments: pendingPayments.length, })); } } catch (err: any) { // Handle AbortError silently if (err.name === 'AbortError') { return; } logger.error('Error fetching payments', err); } finally { setLoadingPayments(false); } }; fetchPayments(); // Cleanup: abort request on unmount return () => { if (paymentsAbortRef.current) { paymentsAbortRef.current.abort(); } }; }, []); useEffect(() => { // Cancel previous request if exists if (invoicesAbortRef.current) { invoicesAbortRef.current.abort(); } // Create new abort controller invoicesAbortRef.current = new AbortController(); const fetchInvoices = async () => { try { setLoadingInvoices(true); const response = await invoiceService.getInvoices({ page: 1, limit: 10 }); if (response.status === 'success' && response.data?.invoices) { setRecentInvoices(response.data.invoices); // Calculate invoice summary const paidInvoices = response.data.invoices.filter((inv: Invoice) => inv.status === 'paid'); const overdueInvoices = response.data.invoices.filter((inv: Invoice) => inv.status === 'overdue'); setFinancialSummary(prev => ({ ...prev, totalInvoices: response.data.invoices?.length || 0, paidInvoices: paidInvoices.length, overdueInvoices: overdueInvoices.length, })); } } catch (err: any) { // Handle AbortError silently if (err.name === 'AbortError') { return; } logger.error('Error fetching invoices', err); } finally { setLoadingInvoices(false); } }; fetchInvoices(); // Cleanup: abort request on unmount return () => { if (invoicesAbortRef.current) { invoicesAbortRef.current.abort(); } }; }, []); const handleRefresh = () => { execute(); }; const getInvoiceStatusColor = (status: string) => { switch (status) { case 'paid': return 'bg-gradient-to-r from-emerald-50 to-green-50 text-emerald-800 border-emerald-200'; case 'sent': return 'bg-gradient-to-r from-blue-50 to-indigo-50 text-blue-800 border-blue-200'; case 'overdue': return 'bg-gradient-to-r from-rose-50 to-red-50 text-rose-800 border-rose-200'; case 'draft': return 'bg-gradient-to-r from-slate-50 to-gray-50 text-slate-700 border-slate-200'; default: return 'bg-gradient-to-r from-slate-50 to-gray-50 text-slate-700 border-slate-200'; } }; if (loading) { return ; } if (error || !stats) { return (
); } return (
{/* Header */}

Financial Dashboard

Comprehensive financial overview and analytics

{/* Date Range & Actions */}
setDateRange({ ...dateRange, from: e.target.value })} className="flex-1 sm:flex-none px-3 sm:px-4 py-2 sm:py-2.5 bg-white border-2 border-slate-200 rounded-xl focus:border-emerald-400 focus:ring-4 focus:ring-emerald-100 transition-all duration-200 text-slate-700 font-medium shadow-sm hover:shadow-md text-sm sm:text-base" /> to setDateRange({ ...dateRange, to: e.target.value })} className="flex-1 sm:flex-none px-3 sm:px-4 py-2 sm:py-2.5 bg-white border-2 border-slate-200 rounded-xl focus:border-emerald-400 focus:ring-4 focus:ring-emerald-100 transition-all duration-200 text-slate-700 font-medium shadow-sm hover:shadow-md text-sm sm:text-base" />
({ 'Type': 'Payment', 'Transaction ID': p.transaction_id || `PAY-${p.id}`, 'Amount': formatCurrency(p.amount || 0), 'Status': p.payment_status, 'Method': p.payment_method, 'Date': p.payment_date ? formatDate(p.payment_date) : 'N/A', 'Booking': p.booking?.booking_number || 'N/A' }))), ...(recentInvoices.map(i => ({ 'Type': 'Invoice', 'Invoice Number': i.invoice_number, 'Customer': i.customer_name, 'Total Amount': formatCurrency(i.total_amount), 'Amount Due': formatCurrency(i.amount_due ?? i.balance_due), 'Status': i.status, 'Due Date': i.due_date ? formatDate(i.due_date) : 'N/A', 'Issue Date': i.issue_date ? formatDate(i.issue_date) : 'N/A' }))) ]} filename="accountant-dashboard" title="Accountant Financial Dashboard Report" />
{/* Financial Summary Cards */}
{/* Total Revenue */}

Total Revenue

{formatCurrency(financialSummary.totalRevenue || stats?.total_revenue || 0)}

Active All time revenue
{/* Total Payments */}

Total Payments

{financialSummary.totalPayments}

{financialSummary.pendingPayments} pending payments
{/* Total Invoices */}

Total Invoices

{financialSummary.totalInvoices}

{financialSummary.paidInvoices} paid • {financialSummary.overdueInvoices} overdue
{/* Recent Payments and Invoices */}
{/* Recent Payments */}

Recent Payments

{loadingPayments ? (
) : recentPayments && recentPayments.length > 0 ? (
{recentPayments.slice(0, 5).map((payment) => (
navigate(`/accountant/payments`)} >

{formatCurrency(payment.amount)}

{payment.payment_method || 'N/A'}

{payment.payment_date && ( • {formatDate(payment.payment_date, 'short')} )}
{payment.payment_status.charAt(0).toUpperCase() + payment.payment_status.slice(1)}
))}
) : ( navigate('/accountant/payments') }} /> )}
{/* Recent Invoices */}

Recent Invoices

{loadingInvoices ? (
) : recentInvoices && recentInvoices.length > 0 ? (
{recentInvoices.slice(0, 5).map((invoice) => (
navigate(`/accountant/invoices`)} >

{invoice.invoice_number}

{formatCurrency(invoice.total_amount || 0)}

{invoice.issue_date && ( • {formatDate(invoice.issue_date, 'short')} )}
{invoice.status.charAt(0).toUpperCase() + invoice.status.slice(1)}
))}
) : ( navigate('/accountant/invoices') }} /> )}
{/* Alerts Section */} {(financialSummary.overdueInvoices > 0 || financialSummary.pendingPayments > 0) && (

Financial Alerts

{financialSummary.overdueInvoices > 0 && (

⚠️ {financialSummary.overdueInvoices} overdue invoice{financialSummary.overdueInvoices !== 1 ? 's' : ''} require attention

)} {financialSummary.pendingPayments > 0 && (

⏳ {financialSummary.pendingPayments} pending payment{financialSummary.pendingPayments !== 1 ? 's' : ''} awaiting processing

)}
)}
); }; export default AccountantDashboardPage;