aboutsummaryrefslogtreecommitdiffstats
path: root/frontend/src/app/(main)/loans/page.tsx
diff options
context:
space:
mode:
authorLibravatarLibravatar Biswa Kalyan Bhuyan <biswa@surgot.in> 2025-04-24 09:13:07 +0530
committerLibravatarLibravatar Biswa Kalyan Bhuyan <biswa@surgot.in> 2025-04-24 09:13:07 +0530
commitcaace928ac81c284629ee50942d72179d4da9784 (patch)
treeb2f4e87b7a53e30ac5ac9af94cdc70c2da5bbfb9 /frontend/src/app/(main)/loans/page.tsx
parent50d5e6534f5e593297a09323e683c7c8b850117b (diff)
downloadfinance-caace928ac81c284629ee50942d72179d4da9784.tar.gz
finance-caace928ac81c284629ee50942d72179d4da9784.tar.bz2
finance-caace928ac81c284629ee50942d72179d4da9784.zip
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.
Diffstat (limited to 'frontend/src/app/(main)/loans/page.tsx')
-rw-r--r--frontend/src/app/(main)/loans/page.tsx279
1 files changed, 279 insertions, 0 deletions
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<Loan | null>(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 <div>Loading loans...</div>;
+ }
+
+ if (error) {
+ return <div>Error loading loans: {error.toString()}</div>;
+ }
+
+ return (
+ <div className="space-y-6">
+ <div className="flex items-center justify-between">
+ <h1 className="text-3xl font-bold">Loans</h1>
+ <Dialog open={isAddDialogOpen} onOpenChange={setIsAddDialogOpen}>
+ <DialogTrigger asChild>
+ <Button onClick={resetForm}>Add New Loan</Button>
+ </DialogTrigger>
+ <DialogContent>
+ <DialogHeader>
+ <DialogTitle>Add New Loan</DialogTitle>
+ </DialogHeader>
+ <form onSubmit={handleCreateLoan} className="space-y-4">
+ <div className="space-y-2">
+ <Label htmlFor="name">Loan Name</Label>
+ <Input
+ id="name"
+ placeholder="e.g., Car Loan"
+ value={loanName}
+ onChange={(e) => setLoanName(e.target.value)}
+ required
+ />
+ </div>
+
+ <div className="space-y-2">
+ <Label htmlFor="originalAmount">Original Amount ($)</Label>
+ <Input
+ id="originalAmount"
+ type="number"
+ step="0.01"
+ placeholder="10000.00"
+ value={originalAmount}
+ onChange={(e) => setOriginalAmount(e.target.value)}
+ required
+ />
+ </div>
+
+ <div className="space-y-2">
+ <Label htmlFor="currentBalance">Current Balance ($)</Label>
+ <Input
+ id="currentBalance"
+ type="number"
+ step="0.01"
+ placeholder="9500.00"
+ value={currentBalance}
+ onChange={(e) => setCurrentBalance(e.target.value)}
+ required
+ />
+ </div>
+
+ <div className="space-y-2">
+ <Label htmlFor="interestRate">Interest Rate (%)</Label>
+ <Input
+ id="interestRate"
+ type="number"
+ step="0.01"
+ placeholder="5.25"
+ value={interestRate}
+ onChange={(e) => setInterestRate(e.target.value)}
+ />
+ </div>
+
+ <div className="space-y-2">
+ <Label htmlFor="startDate">Start Date</Label>
+ <Input
+ id="startDate"
+ type="date"
+ value={startDate}
+ onChange={(e) => setStartDate(e.target.value)}
+ required
+ />
+ </div>
+
+ <div className="space-y-2">
+ <Label htmlFor="endDate">End Date</Label>
+ <Input
+ id="endDate"
+ type="date"
+ value={endDate}
+ onChange={(e) => setEndDate(e.target.value)}
+ required
+ />
+ </div>
+
+ {formError && (
+ <div className="text-sm text-red-500">
+ {formError}
+ </div>
+ )}
+
+ <div className="flex justify-end space-x-2">
+ <Button type="button" variant="outline" onClick={() => setIsAddDialogOpen(false)}>
+ Cancel
+ </Button>
+ <Button type="submit" disabled={createLoanMutation.isPending}>
+ {createLoanMutation.isPending ? 'Saving...' : 'Save Loan'}
+ </Button>
+ </div>
+ </form>
+ </DialogContent>
+ </Dialog>
+ </div>
+
+ {data && data.length > 0 ? (
+ <Card>
+ <CardHeader>
+ <CardTitle>Your Loans</CardTitle>
+ </CardHeader>
+ <CardContent>
+ <Table>
+ <TableHeader>
+ <TableRow>
+ <TableHead>Name</TableHead>
+ <TableHead>Original Amount</TableHead>
+ <TableHead>Current Balance</TableHead>
+ <TableHead>Interest Rate</TableHead>
+ <TableHead>Start Date</TableHead>
+ <TableHead>End Date</TableHead>
+ <TableHead>Actions</TableHead>
+ </TableRow>
+ </TableHeader>
+ <TableBody>
+ {data.map((loan) => (
+ <TableRow key={loan.ID}>
+ <TableCell className="font-medium">{loan.Name}</TableCell>
+ <TableCell>{formatCurrency(loan.OriginalAmount)}</TableCell>
+ <TableCell>{formatCurrency(loan.CurrentBalance)}</TableCell>
+ <TableCell>{loan.InterestRate}%</TableCell>
+ <TableCell>{formatDate(loan.StartDate)}</TableCell>
+ <TableCell>{formatDate(loan.EndDate)}</TableCell>
+ <TableCell>
+ <div className="flex items-center space-x-2">
+ <Button
+ variant="destructive"
+ size="sm"
+ onClick={() => handleDeleteLoan(loan.ID)}
+ disabled={deleteLoanMutation.isPending}
+ >
+ Delete
+ </Button>
+ </div>
+ </TableCell>
+ </TableRow>
+ ))}
+ </TableBody>
+ </Table>
+ </CardContent>
+ </Card>
+ ) : (
+ <Card>
+ <CardContent className="flex flex-col items-center justify-center p-10">
+ <p className="text-muted-foreground mb-4">You don&apos;t have any loans yet.</p>
+ <Button onClick={() => setIsAddDialogOpen(true)}>Add Your First Loan</Button>
+ </CardContent>
+ </Card>
+ )}
+ </div>
+ );
+} \ No newline at end of file