From caace928ac81c284629ee50942d72179d4da9784 Mon Sep 17 00:00:00 2001 From: Biswa Kalyan Bhuyan Date: Thu, 24 Apr 2025 09:13:07 +0530 Subject: feat: Fix loan API type assertion and complete core loan features - Resolve interface conversion panic in loan handlers by correcting user type assertions from *models.User to models.User - Finalize loan management API integration with frontend components - Implement remaining loan calculation logic and CRUD operations - Connect loan display components to backend APIs as per Phase 3 - Update project status in README.md to reflect completed loan features - Add CORS middleware configuration for frontend-backend communication This commit completes core loan management functionality and fixes critical type safety issues in the API handlers, enabling proper user context handling. --- frontend/src/app/(main)/loans/page.tsx | 279 +++++++++++++++++++++++++++++++++ 1 file changed, 279 insertions(+) create mode 100644 frontend/src/app/(main)/loans/page.tsx (limited to 'frontend/src/app/(main)/loans/page.tsx') diff --git a/frontend/src/app/(main)/loans/page.tsx b/frontend/src/app/(main)/loans/page.tsx new file mode 100644 index 0000000..07c1428 --- /dev/null +++ b/frontend/src/app/(main)/loans/page.tsx @@ -0,0 +1,279 @@ +'use client'; + +import { useState } from 'react'; +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from '@/components/ui/table'; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { loanApi, Loan, LoanInput } from '@/lib/api'; + +export default function LoansPage() { + const queryClient = useQueryClient(); + const [isAddDialogOpen, setIsAddDialogOpen] = useState(false); + const [selectedLoan, setSelectedLoan] = useState(null); + + // Form state + const [loanName, setLoanName] = useState(''); + const [originalAmount, setOriginalAmount] = useState(''); + const [currentBalance, setCurrentBalance] = useState(''); + const [interestRate, setInterestRate] = useState(''); + const [startDate, setStartDate] = useState(''); + const [endDate, setEndDate] = useState(''); + const [formError, setFormError] = useState(''); + + // Query loans + const { data, isLoading, error } = useQuery({ + queryKey: ['loans'], + queryFn: async () => { + const response = await loanApi.getLoans(); + return response.loans as Loan[]; + } + }); + + // Create loan mutation + const createLoanMutation = useMutation({ + mutationFn: (loanData: LoanInput) => loanApi.createLoan(loanData), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['loans'] }); + resetForm(); + setIsAddDialogOpen(false); + }, + onError: (error: Error) => { + setFormError(error.message); + } + }); + + // Delete loan mutation + const deleteLoanMutation = useMutation({ + mutationFn: (id: number) => loanApi.deleteLoan(id), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['loans'] }); + } + }); + + const resetForm = () => { + setLoanName(''); + setOriginalAmount(''); + setCurrentBalance(''); + setInterestRate(''); + setStartDate(''); + setEndDate(''); + setFormError(''); + setSelectedLoan(null); + }; + + const handleCreateLoan = (e: React.FormEvent) => { + e.preventDefault(); + setFormError(''); + + try { + // Validate inputs + if (!loanName || !originalAmount || !currentBalance || !startDate || !endDate) { + setFormError('Please fill in all required fields'); + return; + } + + const loanData: LoanInput = { + name: loanName, + originalAmount: Number(originalAmount) * 100, // Convert to cents + currentBalance: Number(currentBalance) * 100, // Convert to cents + interestRate: Number(interestRate) || 0, + startDate, + endDate, + }; + + createLoanMutation.mutate(loanData); + } catch (err: any) { + setFormError(err.message || 'Error creating loan'); + } + }; + + const handleDeleteLoan = (id: number) => { + if (window.confirm('Are you sure you want to delete this loan?')) { + deleteLoanMutation.mutate(id); + } + }; + + // Format currency (convert cents to dollars) + const formatCurrency = (amount: number) => { + return new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD', + }).format(amount / 100); + }; + + // Format date + const formatDate = (dateString: string) => { + return new Date(dateString).toLocaleDateString(); + }; + + if (isLoading) { + return
Loading loans...
; + } + + if (error) { + return
Error loading loans: {error.toString()}
; + } + + return ( +
+
+

Loans

+ + + + + + + Add New Loan + +
+
+ + setLoanName(e.target.value)} + required + /> +
+ +
+ + setOriginalAmount(e.target.value)} + required + /> +
+ +
+ + setCurrentBalance(e.target.value)} + required + /> +
+ +
+ + setInterestRate(e.target.value)} + /> +
+ +
+ + setStartDate(e.target.value)} + required + /> +
+ +
+ + setEndDate(e.target.value)} + required + /> +
+ + {formError && ( +
+ {formError} +
+ )} + +
+ + +
+
+
+
+
+ + {data && data.length > 0 ? ( + + + Your Loans + + + + + + Name + Original Amount + Current Balance + Interest Rate + Start Date + End Date + Actions + + + + {data.map((loan) => ( + + {loan.Name} + {formatCurrency(loan.OriginalAmount)} + {formatCurrency(loan.CurrentBalance)} + {loan.InterestRate}% + {formatDate(loan.StartDate)} + {formatDate(loan.EndDate)} + +
+ +
+
+
+ ))} +
+
+
+
+ ) : ( + + +

You don't have any loans yet.

+ +
+
+ )} +
+ ); +} \ No newline at end of file -- cgit v1.2.3-59-g8ed1b