diff --git a/src/components/AddTransactionModal.jsx b/src/components/AddTransactionModal.jsx index 93b3da0..8a05661 100644 --- a/src/components/AddTransactionModal.jsx +++ b/src/components/AddTransactionModal.jsx @@ -1,3 +1,20 @@ +import { useState, useEffect, useRef } from 'react'; +import { useTransactions, useCurrency } from './TransactionContext'; +import { Calendar, ChevronDown } from 'lucide-react'; +import toast from 'react-hot-toast'; + +const defaultCategories = [ + "Food", + "Entertainment", + "Utilities", + "Transport", + "Shopping", + "Health", + "Education", + "Salary", + "Gift", + "Investment" +]; const formatDate = (date) => { const d = new Date(date); const day = String(d.getDate()).padStart(2, '0'); @@ -6,35 +23,43 @@ const formatDate = (date) => { return `${day}/${month}/${year}`; }; -import { useState, useEffect } from 'react'; -import { useCurrency } from "./CurrencyContext"; -import { useTransactions } from './TransactionContext'; -import toast from 'react-hot-toast'; - export default function AddTransactionModal({ showModal = true, setShowModal = () => {}, darkMode = false }) { - const { addTransaction } = useTransactions(); + const { addTransaction, transactions } = useTransactions(); + const { currency, locale } = useCurrency(); const [form, setForm] = useState({ - amount: "", - category: "", - type: "Expense", + amount: '', + category: '', + type: 'Expense', date: formatDate(new Date()), - note: " " + note: '' + }); const [isVisible, setIsVisible] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); - const { currency, locale, setCurrency, setLocale } = useCurrency(); const [errors, setErrors] = useState({}); - const suggestedCategories = ['Food', 'Transport', 'Groceries', 'Entertainment', 'Bills', 'Shopping', 'Rent', 'Utilities', 'Salary', 'Others']; - const [categorySuggestions, setCategorySuggestions] = useState([]); - const [showSuggestions, setShowSuggestions] = useState(false); +const [showCategoryDropdown, setShowCategoryDropdown] = useState(false); +const [showCustomCategoryInput, setShowCustomCategoryInput] = useState(false); +const [customCategory, setCustomCategory] = useState(''); +const [showDatePicker, setShowDatePicker] = useState(false); +const [categorySuggestions, setCategorySuggestions] = useState([]); +const [showSuggestions, setShowSuggestions] = useState(false); + +const categoryRef = useRef(null); +const dateRef = useRef(null); - const getCurrencySymbol = (currency, locale) => { +const suggestedCategories = ['Food', 'Transport', 'Groceries', 'Entertainment', 'Bills', 'Shopping', 'Rent', 'Utilities', 'Salary', 'Others']; + +const existingCategories = [...new Set(transactions.map(t => t.category))]; +const allCategories = [...defaultCategories, ...existingCategories.filter(c => !defaultCategories.includes(c))]; +const categories = [...new Set(allCategories)].sort(); + + const getCurrencySymbol = () => { return (0).toLocaleString(locale, { - style: "currency", + style: 'currency', currency, minimumFractionDigits: 0, maximumFractionDigits: 0, - }).replace(/\d/g, "").trim(); + }).replace(/\d/g, '').trim(); }; @@ -44,81 +69,225 @@ export default function AddTransactionModal({ showModal = true, setShowModal = ( } }, [showModal]); + // Close dropdowns when clicking outside + useEffect(() => { + const handleClickOutside = (event) => { + if (categoryRef.current && !categoryRef.current.contains(event.target)) { + setShowCategoryDropdown(false); + setShowCustomCategoryInput(false); + } + if (dateRef.current && !dateRef.current.contains(event.target)) { + setShowDatePicker(false); + } + }; + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + }, []); + const validateForm = () => { const newErrors = {}; - if (!form.amount || parseFloat(form.amount) <= 0) { - newErrors.amount = "Please enter a valid amount"; - } - if (!form.category.trim()) { - newErrors.category = "Category is required"; - } +if (!form.amount || parseFloat(form.amount) <= 0) { + newErrors.amount = "Please enter a valid amount"; +} +if (!form.category.trim()) { + newErrors.category = "Category is required"; +} setErrors(newErrors); return Object.keys(newErrors).length === 0; }; const handleSubmit = async (e) => { - e.preventDefault(); - if (!validateForm()) return; + e.preventDefault(); + if (!validateForm()) return; + setIsSubmitting(true); + + await new Promise(resolve => setTimeout(resolve, 800)); + + try { + addTransaction({ + ...form, + id: Date.now(), + amount: parseFloat(form.amount) + }); + + toast.success('Transaction Added Successfully!'); + + setForm({ + amount: '', + category: '', + type: 'Expense', + date: formatDate(new Date()), + note: '' + }); + + setErrors({}); + handleClose(); + + } catch (error) { + console.error("Failed to add transaction:", error); + toast.error('Could not add transaction. Please try again.'); + } finally { + setIsSubmitting(false); + } +}; - setIsSubmitting(true); - await new Promise(resolve => setTimeout(resolve, 800)); - - try { - - addTransaction({ - ...form, - id: Date.now(), - amount: parseFloat(form.amount) - }); - - - toast.success('Transaction Added Successfully!'); - - - setForm({ - amount: '', - category: '', - type: 'Expense', - date: formatDate(new Date()), - note: '' - }); - handleClose(); - - } catch (error) { - - console.error("Failed to add transaction:", error); - toast.error('Could not add transaction. Please try again.'); - } finally { - - setIsSubmitting(false); - setErrors({}); - } }; const handleClose = () => { setIsVisible(false); setTimeout(() => setShowModal(false), 300); }; - const handleInputChange = (field, value) => { - let newValue = value; - if (field === "date") { - newValue = formatDate(value); - } +const handleAmountChange = (value) => { + const sanitizedValue = value + .replace(/[^0-9.]/g, '') + .replace(/^\./, '') + .replace(/(\..*)\./g, '$1'); - if (field === "category") { - const input = value.toLowerCase(); - const filtered = suggestedCategories.filter(cat => - cat.toLowerCase().includes(input) && input - ); - setCategorySuggestions(filtered); - setShowSuggestions(filtered.length > 0); - } + setForm({ ...form, amount: sanitizedValue }); + if (errors.amount) setErrors({ ...errors, amount: '' }); +}; + +const handleInputChange = (field, value) => { + let newValue = value; + + if (field === "date") { + newValue = formatDate(value); + } + + if (field === "category") { + const input = value.toLowerCase(); + const filtered = suggestedCategories.filter(cat => + cat.toLowerCase().includes(input) && input + ); + setCategorySuggestions(filtered); + setShowSuggestions(filtered.length > 0); + } + + setForm({ ...form, [field]: newValue }); + if (errors[field]) { + setErrors({ ...errors, [field]: '' }); + } +}; + +const handleCategorySelect = (category) => { + if (category === 'Other') { + setShowCustomCategoryInput(true); + setForm({ ...form, category: '' }); + setCustomCategory(''); + } else { + setForm({ ...form, category }); + setShowCategoryDropdown(false); + setShowSuggestions(false); + } + if (errors.category) setErrors({ ...errors, category: '' }); +}; - setForm({ ...form, [field]: newValue }); - if (errors[field]) { - setErrors({ ...errors, [field]: " " }); +const handleCustomCategorySave = () => { + if (customCategory.trim()) { + setForm({ ...form, category: customCategory }); + setShowCustomCategoryInput(false); + setShowCategoryDropdown(false); + } +}; + + const handleDateChange = (dateString) => { + setForm({ ...form, date: formatDate(dateString) }); + setShowDatePicker(false); + }; + + const renderDatePicker = () => { + const [dd, mm, yyyy] = form.date.split('/'); + const currentDate = new Date(`${yyyy}-${mm}-${dd}`); + const monthNames = ["January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December"]; + + const daysInMonth = new Date(yyyy, mm, 0).getDate(); + const firstDayOfMonth = new Date(yyyy, mm - 1, 1).getDay(); + + const days = []; + for (let i = 0; i < firstDayOfMonth; i++) { + days.push(
); } + + for (let i = 1; i <= daysInMonth; i++) { + const isSelected = i === parseInt(dd); + days.push( + + ); + } + + return ( +