import { useEffect, useState, useCallback } from 'react'; import { Plus, Wallet, TrendingUp, PiggyBank } from 'lucide-react'; import { useNavigate } from 'react-router-dom'; import { useStore } from '../store'; import { getBuckets, createBucket, updateBucket, deleteBucket } from '../appwrite/db'; import { bucketMonthlyAllocation } from '../lib/calculations'; import { formatCurrency, randomBucketColor, BUCKET_COLORS } from '../lib/utils'; import { Layout } from '../components/layout/Layout'; import { Header } from '../components/layout/Header'; import { Card, SectionHeader } from '../components/ui/Card'; import { Button } from '../components/ui/Button'; import { Input, Select } from '../components/ui/Input'; import { Modal, ConfirmModal } from '../components/ui/Modal'; import { Badge, ProgressBar } from '../components/ui/Badge'; import type { Bucket, BucketType } from '../types'; const TYPE_ICONS = { regular: Wallet, savings: PiggyBank, investment: TrendingUp, }; const TYPE_LABELS: Record = { regular: 'Regular', savings: 'Savings', investment: 'Investment', }; function BucketForm({ initial, onSave, onCancel, }: { initial?: Partial; onSave: (data: Omit) => void; onCancel: () => void; }) { const [name, setName] = useState(initial?.name ?? ''); const [description, setDescription] = useState(initial?.description ?? ''); const [type, setType] = useState(initial?.type ?? 'regular'); const [color, setColor] = useState(initial?.color ?? randomBucketColor()); const [goalAmount, setGoalAmount] = useState(String(initial?.goal_amount ?? '')); const [goalType, setGoalType] = useState<'amount' | 'percent'>(initial?.goal_type ?? 'amount'); const [goalFreq, setGoalFreq] = useState<'monthly' | 'yearly'>(initial?.goal_frequency ?? 'monthly'); const [returnPct, setReturnPct] = useState(String(initial?.return_percent ?? '')); const [returnFreq, setReturnFreq] = useState<'monthly' | 'yearly'>(initial?.return_frequency ?? 'yearly'); const { user } = useStore(); function submit() { onSave({ name, description, type, color, goal_amount: parseFloat(goalAmount) || 0, goal_type: goalType, goal_frequency: goalFreq, return_percent: parseFloat(returnPct) || 0, return_frequency: returnFreq, current_balance: initial?.current_balance ?? 0, sort_order: initial?.sort_order ?? 0, user_id: user?.$id ?? '', }); } return (
setName(e.target.value)} /> setDescription(e.target.value)} /> {/* Color picker */}
{BUCKET_COLORS.map((c) => (
{/* Goal settings */}

Goal / Allocation

{(['amount', 'percent'] as const).map((t) => ( ))}
setGoalAmount(e.target.value)} prefix={goalType === 'amount' ? '$' : undefined} suffix={goalType === 'percent' ? '%' : undefined} inputMode="decimal" />
{/* Investment settings */} {type === 'investment' && (

Returns

setReturnPct(e.target.value)} suffix="%" inputMode="decimal" />
)}
); } export function Buckets() { const navigate = useNavigate(); const { user, buckets, setBuckets, addBucket, updateBucketItem, removeBucket } = useStore(); const totalMonthlyIncome = useStore((s) => s.incomes.reduce( (sum, i) => sum + (i.frequency === 'yearly' ? i.amount / 12 : i.amount), 0, ), ); const [showModal, setShowModal] = useState(false); const [editingBucket, setEditingBucket] = useState(null); const [deletingBucket, setDeletingBucket] = useState(null); const [loading, setLoading] = useState(false); const loadBuckets = useCallback(async () => { if (!user) return; setLoading(true); try { const bkts = await getBuckets(user.$id); setBuckets(bkts); } finally { setLoading(false); } }, [user, setBuckets]); useEffect(() => { loadBuckets(); }, [loadBuckets]); async function handleCreate(data: Omit) { const b = await createBucket({ ...data, sort_order: buckets.length }); addBucket(b); setShowModal(false); } async function handleUpdate(data: Omit) { if (!editingBucket) return; const b = await updateBucket(editingBucket.$id, data); updateBucketItem(editingBucket.$id, b); setEditingBucket(null); } async function handleDelete() { if (!deletingBucket) return; await deleteBucket(deletingBucket.$id); removeBucket(deletingBucket.$id); setDeletingBucket(null); } const totalAllocation = buckets.reduce( (sum, b) => sum + bucketMonthlyAllocation(b, totalMonthlyIncome), 0, ); const groupedBuckets = { regular: buckets.filter((b) => b.type === 'regular'), savings: buckets.filter((b) => b.type === 'savings'), investment: buckets.filter((b) => b.type === 'investment'), }; function BucketList({ type }: { type: BucketType }) { const list = groupedBuckets[type]; if (list.length === 0) return null; const Icon = TYPE_ICONS[type]; return (
{list.map((b) => { const allocation = bucketMonthlyAllocation(b, totalMonthlyIncome); const progress = b.goal_amount > 0 ? (b.current_balance / b.goal_amount) * 100 : 0; return ( navigate(`/buckets/${b.$id}`)}>

{b.name}

{b.description && (

{b.description}

)}

{formatCurrency(b.current_balance)}

{allocation > 0 && (

+{formatCurrency(allocation)}/mo

)}
{b.goal_amount > 0 && ( )} {type === 'investment' && b.return_percent > 0 && (

{b.return_percent}% / {b.return_frequency}

)}
); })}
); } return (
setShowModal(true)}> New } />
{totalAllocation > 0 && (
Total monthly allocation {formatCurrency(totalAllocation)}
)} {loading ? (
) : buckets.length === 0 ? (

No buckets yet.

Create buckets for savings goals, investments, or expense categories.

) : ( <> )}
setShowModal(false)} title="New Bucket" size="lg"> setShowModal(false)} /> setEditingBucket(null)} title="Edit Bucket" size="lg" > setEditingBucket(null)} /> setDeletingBucket(null)} onConfirm={handleDelete} title="Delete bucket?" message={`"${deletingBucket?.name}" and all its transactions will be deleted. This cannot be undone.`} confirmLabel="Delete" danger /> ); }