diff --git a/app/api/docs/SwaggerUIWrapper.tsx b/app/api/docs/SwaggerUIWrapper.tsx index e01affa..8b5c289 100644 --- a/app/api/docs/SwaggerUIWrapper.tsx +++ b/app/api/docs/SwaggerUIWrapper.tsx @@ -113,7 +113,7 @@ export default function SwaggerUIWrapper({ specUrl }: SwaggerUIWrapperProps) { RemitWise API Documentation

- Complete reference for integrating with RemitWise's remittance and financial planning services. + Complete reference for integrating with RemitWise's remittance and financial planning services. Build secure, scalable applications with our comprehensive API.

diff --git a/app/bills/page.tsx b/app/bills/page.tsx index 755e577..583ae67 100644 --- a/app/bills/page.tsx +++ b/app/bills/page.tsx @@ -129,6 +129,57 @@ export default function Bills() { } }, [toast]); + const [bills, setBills] = useState([]); + const [stats, setStats] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + + const fetchBillsData = async () => { + setIsLoading(true); + setError(null); + try { + const [billsRes, statsRes] = await Promise.all([ + apiClient.get('/api/bills'), + apiClient.get('/api/bills/total-unpaid') + ]); + + if (!billsRes || !statsRes) throw new Error("Session expired"); + if (!billsRes.ok || !statsRes.ok) throw new Error("Failed to load bills data"); + + const billsJson = await billsRes.json(); + const statsJson = await statsRes.json(); + + const fetchedBills: Bill[] = billsJson.data?.bills || []; + const fetchedStats = statsJson.data; + + setBills(fetchedBills); + + const paidBills = fetchedBills.filter((b: Bill) => b.status === 'paid'); + const paidAmount = paidBills.reduce((acc: number, b: Bill) => acc + b.amount, 0); + const overdueCount = fetchedBills.filter((b: Bill) => b.status === 'overdue' || b.status === 'urgent').length; + + setStats({ + totalUnpaid: { + amount: fetchedStats?.totalUnpaid?.toLocaleString() || '0', + pendingCount: fetchedStats?.count || 0 + }, + overdueCount, + paidThisMonth: { + amount: paidAmount.toLocaleString(), + paymentCount: paidBills.length + } + }); + } catch (err) { + setError(err instanceof Error ? err : new Error("Unknown error")); + } finally { + setIsLoading(false); + } + }; + + useEffect(() => { + fetchBillsData(); + }, []); + function handleAddBill() { formSectionRef.current?.scrollIntoView({ behavior: "smooth", @@ -147,17 +198,34 @@ export default function Bills() { />
-
- -
+ {error ? ( +
+ +
+ ) : isLoading ? ( +
+ + +
+ ) : ( + <> +
+ +
-
- -
+
+ +
-
- -
+
+ +
+ + )}
= { overdue: "Overdue", urgent: "Due Soon", @@ -79,7 +79,7 @@ function StatusBadge({ status }: { status: Bill["status"] }) { return (
-
- {mockPaidBills.map((bill) => ( - - ))} -
+ {paidBills.length > 0 ? ( +
+ {paidBills.map((bill) => ( + + ))} +
+ ) : ( + + )} ); } diff --git a/components/Bills/UnpaidBillsSection.tsx b/components/Bills/UnpaidBillsSection.tsx index c8a3575..4ba7e45 100644 --- a/components/Bills/UnpaidBillsSection.tsx +++ b/components/Bills/UnpaidBillsSection.tsx @@ -1,13 +1,14 @@ import React from 'react'; import { BillCards } from './BillsCard'; -import { mockBills } from '@/lib/mockdata/bills'; import { useDensity } from '@/lib/context/DensityContext'; +import { Bill } from '@/lib/contracts/bill-payments'; +import { WidgetEmptyState } from '@/components/ui/WidgetStates'; export function UnpaidBillsSection() { const { density } = useDensity(); const unpaidStatuses: Bill['status'][] = ['overdue', 'urgent', 'upcoming']; - const unpaidBills = mockBills.filter((bill) => + const unpaidBills = bills.filter((bill) => unpaidStatuses.includes(bill.status) ); const recurringUnpaidCount = unpaidBills.filter((bill) => bill.isRecurring).length; diff --git a/components/ui/WidgetStates.tsx b/components/ui/WidgetStates.tsx new file mode 100644 index 0000000..50c1d54 --- /dev/null +++ b/components/ui/WidgetStates.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { AlertCircle, AlertTriangle, RefreshCcw } from 'lucide-react'; + +export function WidgetEmptyState({ title, message }: { title: string; message?: string }) { + return ( +
+ +

{title}

+ {message &&

{message}

} +
+ ); +} + +export function WidgetErrorState({ title, message, onRetry }: { title: string; message?: string; onRetry?: () => void }) { + return ( +
+ +

{title}

+ {message &&

{message}

} + {onRetry && ( + + )} +
+ ); +}