From caace928ac81c284629ee50942d72179d4da9784 Mon Sep 17 00:00:00 2001
From: Biswa Kalyan Bhuyan <biswa@surgot.in>
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<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
-- 
cgit v1.2.3-59-g8ed1b

