import { useState, useEffect } from "react" import { toast } from "sonner" import { useLanguage } from "@/contexts/LanguageContext" import { Button } from "@/components/ui/button" import { ResponsiveDialog, ResponsiveDialogContent, ResponsiveDialogDescription, ResponsiveDialogFooter, ResponsiveDialogHeader, ResponsiveDialogTitle, } from "@/components/ui/responsive-dialog" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { MultipleSelector, type Option } from "@/components/ui/multiselector" import { DatePicker } from "@/components/ui/date-picker" import axios from "axios" interface Wallet { id: string name: string kind: "money" | "asset" currency?: string | null unit?: string | null deletedAt?: string | null } interface Transaction { id: string walletId: string date: string amount: number direction: "in" | "out" category?: string | null memo?: string | null } interface TransactionDialogProps { open: boolean onOpenChange: (open: boolean) => void transaction?: Transaction | null walletId?: string | null onSuccess: () => void } const API = "/api" export function TransactionDialog({ open, onOpenChange, transaction, walletId: initialWalletId, onSuccess }: TransactionDialogProps) { const { t } = useLanguage() const [wallets, setWallets] = useState([]) const [categoryOptions, setCategoryOptions] = useState([]) const [loading, setLoading] = useState(false) const [amount, setAmount] = useState(transaction?.amount?.toString() || "") const [walletId, setWalletId] = useState(transaction?.walletId || initialWalletId || "") const [direction, setDirection] = useState<"in" | "out">(transaction?.direction || "out") const [categories, setCategories] = useState( transaction?.category ? transaction.category.split(',').map(c => c.trim()) : [] ) const [memo, setMemo] = useState(transaction?.memo || "") const [selectedDate, setSelectedDate] = useState( transaction?.date ? new Date(transaction.date) : new Date() ) const [error, setError] = useState(null) const isEditing = !!transaction useEffect(() => { if (open) { loadWallets() loadCategories() } }, [open]) const loadWallets = async () => { try { const response = await axios.get(`${API}/wallets`) setWallets(response.data.filter((w: Wallet) => !w.deletedAt)) } catch (error) { console.error('Failed to load wallets:', error) } } const loadCategories = async () => { try { // Get categories from the dedicated Category table const response = await axios.get(`${API}/categories`) const categories = response.data const options: Option[] = categories.map((cat: { name: string }) => ({ label: cat.name, value: cat.name })) setCategoryOptions(options) } catch (error) { console.error('Failed to load categories:', error) // Fallback: extract from existing transactions if categories endpoint fails try { const txResponse = await axios.get(`${API}/wallets/transactions`) const allTransactions = txResponse.data const uniqueCategories = new Set() allTransactions.forEach((tx: Transaction) => { if (tx.category) { tx.category.split(',').forEach(cat => { const trimmed = cat.trim() if (trimmed) uniqueCategories.add(trimmed) }) } }) const fallbackOptions: Option[] = Array.from(uniqueCategories).map(cat => ({ label: cat, value: cat })) setCategoryOptions(fallbackOptions) } catch (fallbackError) { console.error('Failed to load categories from transactions:', fallbackError) } } } const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() const amountNum = parseFloat(amount) if (!amountNum || amountNum <= 0) { setError("Amount must be a positive number") return } if (!walletId) { setError("Please select a wallet") return } setLoading(true) setError(null) try { // First, create any new categories if (categories.length > 0) { for (const category of categories) { try { await axios.post(`${API}/categories`, { name: category }) } catch (error) { // Ignore if category already exists (409 conflict) const err = error as { response?: { status?: number } } if (err.response?.status !== 409) { console.error('Failed to create category:', error) } } } } const data = { amount: amountNum, direction, category: categories.join(', ').trim() || undefined, memo: memo.trim() || undefined, date: selectedDate.toISOString() } if (isEditing) { await axios.put(`${API}/wallets/${walletId}/transactions/${transaction.id}`, data) toast.success(t.transactionDialog.editSuccess) } else { await axios.post(`${API}/wallets/${walletId}/transactions`, data) toast.success(t.transactionDialog.addSuccess) } onSuccess() onOpenChange(false) } catch (error) { console.error("Failed to save transaction:", error) toast.error(t.transactionDialog.saveError) } finally { setLoading(false) } } const resetForm = () => { setAmount("") setWalletId("") setDirection("out") setCategories([]) setMemo("") setSelectedDate(new Date()) setError(null) } // Reset form when dialog opens/closes const handleOpenChange = (newOpen: boolean) => { onOpenChange(newOpen) if (!newOpen) { setError(null) } } // Update form when transaction prop changes useEffect(() => { if (open) { if (transaction) { setAmount(transaction.amount.toString()) setWalletId(transaction.walletId) setDirection(transaction.direction) setCategories(transaction.category ? transaction.category.split(',').map(c => c.trim()) : []) setMemo(transaction.memo || "") setSelectedDate(new Date(transaction.date)) } else { resetForm() } } }, [open, transaction]) return ( {isEditing ? t.transactionDialog.editTitle : t.transactionDialog.addTitle} {t.transactionDialog.description}
setAmount(e.target.value)} placeholder="0" className="h-11 md:h-9 text-base md:text-sm" />
date && setSelectedDate(date)} placeholder={t.transactionDialog.selectDate} className="h-11 md:h-9 text-base md:text-sm w-full" />
setMemo(e.target.value)} placeholder={t.transactionDialog.addMemo} className="h-11 md:h-9 text-base md:text-sm" />
{error && (
{error}
)}
) }