import { useState } from 'react'; import { toast } from 'sonner'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from '@/components/ui/dialog'; import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem, } from '@/components/ui/select'; import { api } from '@/api'; import { cn } from '@/lib/utils'; // Radix Select crashes on empty string value const CAT_NONE = 'none'; export default function BillModal({ bill, categories, onClose, onSave }) { const isNew = !bill; const [name, setName] = useState(bill?.name || ''); const [categoryId, setCategoryId] = useState(bill?.category_id ? String(bill.category_id) : CAT_NONE); const [dueDay, setDueDay] = useState(String(bill?.due_day || '')); const [expectedAmount, setExpected] = useState(String(bill?.expected_amount || '')); const [interestRate, setInterestRate] = useState(bill?.interest_rate == null ? '' : String(bill.interest_rate)); const [billingCycle, setCycle] = useState(bill?.billing_cycle || 'monthly'); const [autopay, setAutopay] = useState(!!bill?.autopay_enabled); const [has2fa, setHas2fa] = useState(!!bill?.has_2fa); const [website, setWebsite] = useState(bill?.website || ''); const [username, setUsername] = useState(bill?.username || ''); const [accountInfo, setAccountInfo] = useState(bill?.account_info || ''); const [notes, setNotes] = useState(bill?.notes || ''); const [busy, setBusy] = useState(false); async function handleSubmit(e) { e.preventDefault(); const parsedDueDay = Number(dueDay); if (!Number.isInteger(parsedDueDay) || parsedDueDay < 1 || parsedDueDay > 31) { toast.error('Due day must be a whole number from 1 to 31.'); return; } const trimmedInterestRate = interestRate.trim(); const parsedInterestRate = trimmedInterestRate === '' ? null : Number(trimmedInterestRate); if (parsedInterestRate !== null && (!Number.isFinite(parsedInterestRate) || parsedInterestRate < 0 || parsedInterestRate > 100)) { toast.error('Interest rate must be blank or a number from 0 to 100.'); return; } const data = { name: name.trim(), category_id: categoryId === CAT_NONE ? null : parseInt(categoryId, 10), due_day: parsedDueDay, expected_amount: parseFloat(expectedAmount) || 0, interest_rate: parsedInterestRate, billing_cycle: billingCycle, autopay_enabled: autopay, has_2fa: has2fa, website: website || null, username: username || null, account_info: accountInfo || null, notes: notes || null, }; setBusy(true); try { if (isNew) { await api.createBill(data); toast.success('Bill added'); } else { await api.updateBill(bill.id, data); toast.success('Bill updated'); } onSave(); onClose(); } catch (err) { toast.error(err.message); } finally { setBusy(false); } } const inp = 'bg-background/50 border-border/60 h-9 text-sm'; return ( { if (!v) onClose(); }}> {isNew ? 'Add Bill' : 'Edit Bill'}
{/* Name */}
setName(e.target.value)} required />
{/* Category */}
{/* Due Day */}
setDueDay(e.target.value)} />

Enter the day of the month this bill is due.

{/* Expected Amount */}
setExpected(e.target.value)} />
{/* Interest Rate */}
setInterestRate(e.target.value)} />

Optional, useful for credit cards. Enter 29.99 for 29.99%.

{/* Billing Cycle */}
{/* Website */}
setWebsite(e.target.value)} />
{/* Username */}
setUsername(e.target.value)} />
{/* Account Info */}
setAccountInfo(e.target.value)} />
{/* Checkboxes */}
{/* Notes */}