import React, { useState, useEffect } from "react"; import { useStore } from "@nanostores/react"; import type { Transaction } from "../types"; // Import store atoms and actions import { currentAccountId as currentAccountIdStore, transactionToEdit as transactionToEditStore, cancelEditingTransaction, transactionSaved, } from "../stores/transactionStore"; // Remove props that now come from the store interface AddTransactionFormProps {} export default function AddTransactionForm({}: AddTransactionFormProps) { // --- 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 (!isNaN(dateObj.getTime())) { // Adjust for timezone offset to prevent date shifting const timezoneOffset = dateObj.getTimezoneOffset() * 60000; //offset in milliseconds const adjustedDate = new Date(dateObj.getTime() - timezoneOffset); setDate(adjustedDate.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 = parseFloat(amount); if (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 (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: 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 && ( )}
); }