import { useSignal } from '@preact/signals'; import { useEffect } from 'preact/hooks'; import { Budget, Expense } from '/lib/types.ts'; import { RequestBody as SuggestionsRequestBody, ResponseBody as SuggestionsResponse, } from '/routes/api/expenses/auto-complete.tsx'; interface ExpenseModalProps { isOpen: boolean; expense: Expense | null; budgets: Budget[]; onClickSave: ( newExpenseCost: number, newExpenseDescription: string, newExpenseBudget: string, newExpenseDate: string, newExpenseIsRecurring: boolean, ) => Promise; onClickDelete: () => Promise; onClose: () => void; shouldResetForm: boolean; } export default function ExpenseModal( { isOpen, expense, budgets, onClickSave, onClickDelete, onClose, shouldResetForm }: ExpenseModalProps, ) { const newExpenseCost = useSignal(expense?.cost ?? ''); const newExpenseDescription = useSignal(expense?.description ?? ''); const newExpenseBudget = useSignal(expense?.budget ?? 'Misc'); const newExpenseDate = useSignal(expense?.date ?? ''); const newExpenseIsRecurring = useSignal(expense?.is_recurring ?? false); const suggestions = useSignal([]); const showSuggestions = useSignal(false); const resetForm = () => { newExpenseCost.value = ''; newExpenseDescription.value = ''; newExpenseBudget.value = 'Misc'; newExpenseDate.value = ''; newExpenseIsRecurring.value = false; }; useEffect(() => { if (expense) { newExpenseCost.value = expense.cost; newExpenseDescription.value = expense.description; newExpenseBudget.value = expense.budget; newExpenseDate.value = expense.date; newExpenseIsRecurring.value = expense.is_recurring; showSuggestions.value = false; } if (shouldResetForm) { resetForm(); } }, [expense, shouldResetForm]); const sortedBudgetNames = budgets.map((budget) => budget.name).sort(); if (!sortedBudgetNames.includes('Misc')) { sortedBudgetNames.push('Misc'); sortedBudgetNames.sort(); } const fetchSuggestions = async (name: string) => { if (name.length < 2) { suggestions.value = []; showSuggestions.value = false; return; } try { const requestBody: SuggestionsRequestBody = { name, }; const response = await fetch(`/api/expenses/auto-complete`, { method: 'POST', body: JSON.stringify(requestBody), }); if (response.ok) { const result = await response.json() as SuggestionsResponse; suggestions.value = result.suggestions; showSuggestions.value = true; } } catch (error) { console.error('Failed to fetch suggestions:', error); suggestions.value = []; showSuggestions.value = false; } }; return ( <>

{expense ? 'Edit Expense' : 'Create New Expense'}

{ newExpenseCost.value = Number(event.currentTarget.value); }} placeholder='10.99' />
{ newExpenseDescription.value = event.currentTarget.value; fetchSuggestions(event.currentTarget.value); }} onFocus={() => { if (suggestions.value.length > 0) { showSuggestions.value = true; } }} onBlur={() => { setTimeout(() => { showSuggestions.value = false; }, 200); }} placeholder='Lunch' /> {showSuggestions.value && suggestions.value.length > 0 && (
    {suggestions.value.map((suggestion) => (
  • { newExpenseDescription.value = suggestion; showSuggestions.value = false; suggestions.value = []; }} > {suggestion}
  • ))}
)}
{ newExpenseDate.value = event.currentTarget.value; }} placeholder='2025-01-01' />
{expense ? (
{ newExpenseIsRecurring.value = event.currentTarget.checked; }} />
) : null}
); }