/** Sparziele mit Fortschritt und nötiger Monatsrate. */ import { type FormEvent, useState } from "react"; import { AlertTriangle, CheckCircle2, Pencil, PiggyBank, Plus, Trash2 } from "lucide-react"; import { AccountSelect } from "@/components/EntitySelects"; import { PageHeader } from "@/components/layout/AppLayout"; import { Button } from "@/components/ui/Button"; import { Badge, ConfirmDialog, EmptyState, Skeleton } from "@/components/ui/Feedback"; import { Field, Input } from "@/components/ui/Field"; import { Modal } from "@/components/ui/Modal"; import { MoneyInput } from "@/components/ui/MoneyInput"; import { useDeleteGoal, useSaveGoal, useSavingsGoals } from "@/hooks/useBudgets"; import { useGoalProgress } from "@/hooks/useReports"; import { formatDate, formatMoney, relativeDays } from "@/lib/format"; import type { SavingsGoal, SavingsGoalProgress } from "@/types/api"; export function GoalsPage() { const [bearbeiten, setBearbeiten] = useState(undefined); const [loeschen, setLoeschen] = useState(null); const { data: ziele = [], isLoading } = useSavingsGoals(); const { data: fortschritt = [] } = useGoalProgress(); const entfernen = useDeleteGoal(); const nachId = new Map(fortschritt.map((eintrag) => [eintrag.goal_id, eintrag])); return ( <> setBearbeiten(null)}> Sparziel } /> {isLoading ? (
{Array.from({ length: 2 }, (_, index) => ( ))}
) : ziele.length === 0 ? ( setBearbeiten(null)}> Erstes Sparziel anlegen } /> ) : (
    {ziele.map((ziel) => ( setBearbeiten(ziel)} onDelete={() => setLoeschen(ziel)} /> ))}
)} setBearbeiten(undefined)} /> setLoeschen(null)} onConfirm={() => { if (loeschen) entfernen.mutate(loeschen.id, { onSuccess: () => setLoeschen(null) }); }} /> ); } function GoalCard({ goal, progress, onEdit, onDelete, }: { goal: SavingsGoal; progress: SavingsGoalProgress | undefined; onEdit: () => void; onDelete: () => void; }) { const anteil = Math.min(progress?.ratio ?? 0, 1); const erreicht = anteil >= 1; return (
  • {goal.name}

    {goal.target_date ? `Ziel bis ${formatDate(goal.target_date)} · ${relativeDays(goal.target_date)}` : "Ohne Zieldatum"}

    {formatMoney(goal.current_amount)} / {formatMoney(goal.target_amount)}

    {Math.round(anteil * 100)} % erreicht {erreicht ? ( Ziel erreicht ) : ( progress?.required_monthly && ( {formatMoney(progress.required_monthly)} pro Monat nötig {progress.months_left !== null && ` (${progress.months_left} Monate)`} ) )}
    {progress?.is_on_track === false && (

    Die geplante Rate von {formatMoney(goal.monthly_contribution)} reicht nicht bis zum Zieldatum.

    )}
  • ); } function GoalDialog({ goal, open, onClose, }: { goal: SavingsGoal | null | undefined; open: boolean; onClose: () => void; }) { const speichern = useSaveGoal(); const [name, setName] = useState(""); const [ziel, setZiel] = useState(""); const [stand, setStand] = useState("0.00"); const [zieldatum, setZieldatum] = useState(""); const [rate, setRate] = useState(""); const [konto, setKonto] = useState(null); const [farbe, setFarbe] = useState("#10b981"); const [initialisiert, setInitialisiert] = useState(undefined); if (open && initialisiert !== (goal?.id ?? null)) { setName(goal?.name ?? ""); setZiel(goal?.target_amount ?? ""); setStand(goal?.current_amount ?? "0.00"); setZieldatum(goal?.target_date ?? ""); setRate(goal?.monthly_contribution ?? ""); setKonto(goal?.account_id ?? null); setFarbe(goal?.color ?? "#10b981"); setInitialisiert(goal?.id ?? null); } function absenden(ereignis: FormEvent) { ereignis.preventDefault(); if (!name.trim() || !ziel) return; speichern.mutate( { id: goal?.id, daten: { name: name.trim(), target_amount: ziel, current_amount: stand || "0.00", target_date: zieldatum || null, monthly_contribution: rate || null, account_id: konto, color: farbe, }, }, { onSuccess: () => { setInitialisiert(undefined); onClose(); }, }, ); } return ( } >
    {(id) => ( setName(ereignis.target.value)} /> )} {(id) => } {(id) => } {(id) => ( setZieldatum(ereignis.target.value)} /> )} {(id) => } {(id) => } {(id) => ( setFarbe(ereignis.target.value)} /> )}
    ); }