stock / src /components /transactions /TransactionForm.tsx
Zelyanoth's picture
Upload 101 files
24d40b9 verified
import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import {
Coffee, ShoppingCart, Zap, Home as HomeIcon,
Car, Utensils, Briefcase, CreditCard, Calendar, MessageSquare
} from 'lucide-react';
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { Calendar as CalendarComponent } from '@/components/ui/calendar';
import { toast } from 'sonner';
import { v4 as uuidv4 } from 'uuid';
import database from '@/services/database';
import { Textarea } from '@/components/ui/textarea';
const CATEGORIES = [
{ id: 'food', name: 'Food', icon: Utensils, color: 'bg-amber-100 text-amber-600' },
{ id: 'coffee', name: 'Coffee', icon: Coffee, color: 'bg-brown-100 text-brown-600' },
{ id: 'shopping', name: 'Shopping', icon: ShoppingCart, color: 'bg-indigo-100 text-indigo-600' },
{ id: 'utilities', name: 'Utilities', icon: Zap, color: 'bg-yellow-100 text-yellow-600' },
{ id: 'housing', name: 'Housing', icon: HomeIcon, color: 'bg-blue-100 text-blue-600' },
{ id: 'transport', name: 'Transport', icon: Car, color: 'bg-green-100 text-green-600' },
{ id: 'salary', name: 'Salary', icon: Briefcase, color: 'bg-emerald-100 text-emerald-600' },
{ id: 'other', name: 'Other', icon: CreditCard, color: 'bg-gray-100 text-gray-600' },
];
const TransactionForm = () => {
const navigate = useNavigate();
const [type, setType] = useState<'expense' | 'income'>('expense');
const [title, setTitle] = useState('');
const [amount, setAmount] = useState('');
const [category, setCategory] = useState('');
const [date, setDate] = useState<Date>(new Date());
const [note, setNote] = useState('');
const [message, setMessage] = useState('');
const [isDbInitialized, setIsDbInitialized] = useState(false);
useEffect(() => {
const initDb = async () => {
try {
await database.initialize();
setIsDbInitialized(true);
} catch (error) {
console.error('Failed to initialize database:', error);
toast.error('Failed to initialize database');
}
};
initDb();
}, []);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!title || !amount || !category) {
toast.error('Please fill in all required fields');
return;
}
if (!isDbInitialized) {
toast.error('Database not initialized yet, please try again');
return;
}
try {
const formattedDate = date.toISOString().split('T')[0];
const id = uuidv4();
database.exec(
'INSERT INTO transactions (id, title, amount, type, category, date, note, message) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
[id, title, parseFloat(amount), type, category, formattedDate, note || null, message || null]
);
toast.success('Transaction saved successfully');
navigate('/transactions');
} catch (error) {
console.error('Error saving transaction:', error);
toast.error('Failed to save transaction');
}
};
return (
<form onSubmit={handleSubmit} className="space-y-6 p-4 animate-fade-in">
<div className="flex rounded-lg border border-border overflow-hidden p-1 w-full">
<button
type="button"
onClick={() => setType('expense')}
className={cn(
"flex-1 py-2.5 text-center text-sm font-medium rounded-md transition-all",
type === 'expense'
? "bg-primary text-white"
: "bg-transparent text-muted-foreground hover:bg-secondary"
)}
>
Expense
</button>
<button
type="button"
onClick={() => setType('income')}
className={cn(
"flex-1 py-2.5 text-center text-sm font-medium rounded-md transition-all",
type === 'income'
? "bg-primary text-white"
: "bg-transparent text-muted-foreground hover:bg-secondary"
)}
>
Income
</button>
</div>
<div className="space-y-2">
<label className="text-sm font-medium">Title</label>
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="What was this for?"
className="w-full p-3 rounded-lg border border-border focus:border-primary/50 focus:ring-2 focus:ring-primary/20 transition-all bg-card"
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium">Amount</label>
<div className="relative">
<span className="absolute left-3 top-3 text-muted-foreground">$</span>
<input
type="number"
value={amount}
onChange={(e) => setAmount(e.target.value)}
placeholder="0.00"
className="w-full p-3 pl-8 rounded-lg border border-border focus:border-primary/50 focus:ring-2 focus:ring-primary/20 transition-all bg-card"
/>
</div>
</div>
<div className="space-y-3">
<label className="text-sm font-medium">Category</label>
<div className="grid grid-cols-4 gap-2">
{CATEGORIES.map((cat) => (
<button
key={cat.id}
type="button"
onClick={() => setCategory(cat.id)}
className={cn(
"flex flex-col items-center justify-center p-3 rounded-lg transition-all",
category === cat.id
? "border-2 border-primary scale-in"
: "border border-border hover:border-primary/50",
)}
>
<div className={cn(
"w-10 h-10 rounded-full flex items-center justify-center mb-1",
cat.color
)}>
<cat.icon size={18} />
</div>
<span className="text-xs font-medium">{cat.name}</span>
</button>
))}
</div>
</div>
<div className="space-y-2">
<label className="text-sm font-medium">Date</label>
<Popover>
<PopoverTrigger asChild>
<button
type="button"
className="w-full flex items-center justify-between p-3 rounded-lg border border-border focus:border-primary/50 focus:ring-2 focus:ring-primary/20 transition-all bg-card"
>
<span>
{date ? date.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric'
}) : 'Select date'}
</span>
<Calendar size={18} className="text-muted-foreground" />
</button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="center">
<CalendarComponent
mode="single"
selected={date}
onSelect={(newDate) => newDate && setDate(newDate)}
initialFocus
/>
</PopoverContent>
</Popover>
</div>
<div className="space-y-2">
<label className="text-sm font-medium">Note (Optional)</label>
<textarea
value={note}
onChange={(e) => setNote(e.target.value)}
placeholder="Add a note..."
rows={3}
className="w-full p-3 rounded-lg border border-border focus:border-primary/50 focus:ring-2 focus:ring-primary/20 transition-all bg-card resize-none"
/>
</div>
<div className="space-y-2">
<div className="flex items-center gap-2">
<MessageSquare size={16} className="text-primary" />
<label className="text-sm font-medium">Message (Optional)</label>
</div>
<Textarea
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="Add a message about this transaction..."
className="min-h-[100px] border-border focus:border-primary/50 focus:ring-2 focus:ring-primary/20 transition-all bg-card"
/>
<p className="text-xs text-muted-foreground">
Add any additional information or messages related to this transaction.
</p>
</div>
<Button
type="submit"
className="w-full py-6 text-base shadow-[0_2px_10px_rgba(0,122,255,0.3)]"
>
Save Transaction
</Button>
</form>
);
};
export default TransactionForm;