import { useStore } from '@nanostores/react'; import type React from 'react'; import { useEffect, useState } from 'react'; // Import store atoms and actions import { cancelEditingTransaction, currentAccountId as currentAccountIdStore, transactionSaved, transactionToEdit as transactionToEditStore, } from '../stores/transactionStore'; import type { Transaction } from '../types'; export default function AddTransactionForm() { // --- Read state from store --- const currentAccountId = useStore(currentAccountIdStore); const transactionToEdit = useStore(transactionToEditStore); // --- State Variables --- const [date, setDate] = useState(''); const [description, setDescription] = useState(''); const [amount, setAmount] = useState(''); const [editingId, setEditingId] = useState(null); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const isEditMode = !!editingId; // --- Effects --- // Effect to set default date on mount useEffect(() => { // Only set default date if not editing if (!transactionToEdit) { setDate(new Date().toISOString().split('T')[0]); } }, [transactionToEdit]); // Rerun if edit mode changes // Effect to populate form when editing useEffect(() => { if (transactionToEdit) { setEditingId(transactionToEdit.id); // Format date correctly for input type="date" try { const dateObj = new Date(transactionToEdit.date); // Check if date is valid before formatting if (!Number.isNaN(dateObj.getTime())) { // Directly format the date object (usually interpreted as UTC midnight) // into the YYYY-MM-DD format required by the input. // No timezone adjustment needed here. setDate(dateObj.toISOString().split('T')[0]); } else { console.warn('Invalid date received for editing:', transactionToEdit.date); setDate(''); // Set to empty if invalid } } catch (e) { console.error('Error parsing date for editing:', e); setDate(''); // Set to empty on error } setDescription(transactionToEdit.description); setAmount(transactionToEdit.amount.toString()); setError(null); // Clear errors when starting edit } else { // Reset form if transactionToEdit becomes null (e.g., after saving or cancelling) // but only if not already resetting via handleCancel or handleSubmit if (!isLoading) { resetForm(); } } }, [transactionToEdit, isLoading]); // Add isLoading dependency // --- Helper Functions --- const resetForm = () => { setEditingId(null); setDate(new Date().toISOString().split('T')[0]); setDescription(''); setAmount(''); setError(null); // Don't reset isLoading here, it's handled in submit/cancel }; const validateForm = (): string[] => { const errors: string[] = []; if (!description || description.trim().length < 2) { errors.push('Description must be at least 2 characters long'); } if (!amount) { errors.push('Amount is required'); } else { const amountNum = Number.parseFloat(amount); if (Number.isNaN(amountNum)) { errors.push('Amount must be a valid number'); } else if (amountNum === 0) { errors.push('Amount cannot be zero'); } } if (!date) { errors.push('Date is required'); } else { try { const dateObj = new Date(`${date}T00:00:00`); // Treat input as local date if (Number.isNaN(dateObj.getTime())) { errors.push('Invalid date format'); } } catch (e) { errors.push('Invalid date format'); } } return errors; }; // --- Event Handlers --- const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setError(null); if (isLoading || !currentAccountId) { if (!currentAccountId) setError('No account selected.'); return; } const validationErrors = validateForm(); if (validationErrors.length > 0) { setError(validationErrors.join('. ')); return; } setIsLoading(true); try { // Ensure date is sent in a consistent format (e.g., YYYY-MM-DD) // The API should handle parsing this. const transactionData = { accountId: currentAccountId, date: date, // Send as YYYY-MM-DD string description: description.trim(), amount: Number.parseFloat(amount), }; const method = editingId ? 'PUT' : 'POST'; const url = editingId ? `/api/transactions/${editingId}` : '/api/transactions'; const response = await fetch(url, { method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(transactionData), }); if (!response.ok) { let errorMsg = `Failed to ${isEditMode ? 'update' : 'create'} transaction`; try { const errorData = await response.json(); errorMsg = errorData.error || errorMsg; } catch (jsonError) { // Ignore if response is not JSON errorMsg = `${response.status}: ${response.statusText}`; } throw new Error(errorMsg); } const savedTransaction: Transaction = await response.json(); transactionSaved(savedTransaction); // Call store action instead of prop callback resetForm(); // Reset form on success } catch (err) { setError(err instanceof Error ? err.message : 'An unexpected error occurred'); } finally { setIsLoading(false); } }; const handleCancel = () => { resetForm(); cancelEditingTransaction(); // Call store action instead of prop callback }; // --- JSX --- return (

{isEditMode ? 'Edit Transaction' : 'New Transaction'}

{error &&
{error}
}
setDate(e.target.value)} required disabled={isLoading} />
setDescription(e.target.value)} required minLength={2} maxLength={100} placeholder="e.g. Groceries" disabled={isLoading} />
setAmount(e.target.value)} step="0.01" required placeholder="e.g. -25.50 or 1200.00" disabled={isLoading} /> Use negative numbers for expenses (e.g., -50.00)
{isEditMode && ( )}
); }