"use client"; import { useState, useEffect, useCallback } from "react"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; import { Progress } from "@/components/ui/progress"; import { Badge } from "@/components/ui/badge"; import { Edit, Trash2, BarChart, AlertCircle } from "lucide-react"; import Link from "next/link"; import { useToast } from "@/components/ui/use-toast"; import { formatCurrency } from "@/lib/utils"; import { api } from "@/lib/api"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; // Type definitions export interface Goal { id: number; name: string; targetAmount: number; currentAmount: number; status: string; targetDate: string; } export interface GoalProgress { goal: Goal; percentComplete: number; amountRemaining: number; daysRemaining: number; requiredPerDay: number; requiredPerMonth: number; onTrack: boolean; } // Backend API response type interface ApiGoal { ID: number; Name: string; TargetAmount: number; CurrentAmount: number; Status: string; TargetDate: string; // Other fields might exist but we don't need them } interface ApiGoalProgress { goal: ApiGoal; percentComplete: number; amountRemaining: number; daysRemaining: number; requiredPerDay: number; requiredPerMonth: number; onTrack: boolean; } export function GoalsList() { const [goals, setGoals] = useState([]); const [loading, setLoading] = useState(true); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [goalToDelete, setGoalToDelete] = useState<{id: number, name: string} | null>(null); const { toast } = useToast(); const fetchGoals = useCallback(async () => { try { setLoading(true); // Add timestamp parameter to prevent caching const response = await api.get(`/goals/progress/all?cache=${new Date().getTime()}`); if (!response.data || !Array.isArray(response.data)) { setGoals([]); return; } // Validate and sanitize the data before setting state const validatedGoals = response.data.map((goalProgress: ApiGoalProgress) => { // Map API field names (uppercase) to our component field names (lowercase) const mappedGoal = { id: goalProgress.goal.ID, name: goalProgress.goal.Name, targetAmount: Number(goalProgress.goal.TargetAmount) || 0, currentAmount: Number(goalProgress.goal.CurrentAmount) || 0, status: goalProgress.goal.Status, targetDate: goalProgress.goal.TargetDate }; return { goal: mappedGoal, percentComplete: Number(goalProgress.percentComplete) || 0, amountRemaining: Number(goalProgress.amountRemaining) || 0, daysRemaining: Number(goalProgress.daysRemaining) || 0, requiredPerDay: Number(goalProgress.requiredPerDay) || 0, requiredPerMonth: Number(goalProgress.requiredPerMonth) || 0, onTrack: Boolean(goalProgress.onTrack) }; }); setGoals(validatedGoals); } catch (error) { console.error("Error fetching goals:", error); toast({ title: "Error", description: "Failed to fetch goals. Please try again later.", variant: "destructive", }); } finally { setLoading(false); } }, [toast]); // Fetch goals when component mounts or if URL contains a refresh parameter useEffect(() => { fetchGoals(); // Add event listener to refresh when the window gains focus (user comes back to the tab) window.addEventListener("focus", fetchGoals); return () => { window.removeEventListener("focus", fetchGoals); }; }, [fetchGoals]); const confirmDelete = (id: number, name: string) => { setGoalToDelete({ id, name }); setDeleteDialogOpen(true); }; const handleDeleteConfirm = async () => { if (!goalToDelete) return; try { const goalId = Number(goalToDelete.id); await api.delete(`/goals/${goalId}`); toast({ title: "Goal deleted", description: "The goal has been successfully deleted.", }); fetchGoals(); } catch (error) { console.error("Error deleting goal:", error); toast({ title: "Error", description: "Failed to delete the goal. Please try again.", variant: "destructive", }); } finally { setDeleteDialogOpen(false); setGoalToDelete(null); } }; const handleDeleteCancel = () => { setDeleteDialogOpen(false); setGoalToDelete(null); }; if (loading) { return
Loading goals...
; } if (!goals || goals.length === 0) { return (

You haven't created any goals yet.

); } return ( <>
{goals.map((goalProgress, index) => { const { goal, percentComplete, amountRemaining, daysRemaining, onTrack } = goalProgress; const isCompleted = goal.status === "Achieved"; return (
{goal.name} {isCompleted ? "Achieved" : onTrack ? "On Track" : "Behind"}

Saving for: {goal.name}

Progress {Math.round(percentComplete)}%
Target {formatCurrency(goal.targetAmount)}
Current {formatCurrency(goal.currentAmount)}
Remaining {formatCurrency(amountRemaining)}
{daysRemaining > 0 && (
Days Left {daysRemaining}
)} {goal.targetDate && (
Target Date {new Date(goal.targetDate).toLocaleDateString()}
)}
); })}
Confirm Deletion Are you sure you want to delete the goal “{goalToDelete?.name}”? This action cannot be undone. ); }