diff --git a/package-lock.json b/package-lock.json index 5df1e12..9fa4859 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2567,10 +2567,13 @@ "license": "MIT" }, "node_modules/fdir": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", - "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, "peerDependencies": { "picomatch": "^3 || ^4" }, @@ -4071,13 +4074,13 @@ "license": "MIT" }, "node_modules/tinyglobby": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", - "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "license": "MIT", "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" }, "engines": { "node": ">=12.0.0" @@ -4179,17 +4182,17 @@ "license": "MIT" }, "node_modules/vite": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.0.6.tgz", - "integrity": "sha512-MHFiOENNBd+Bd9uvc8GEsIzdkn1JxMmEeYX35tI3fv0sJBUTfW5tQsoaOwuY4KhBI09A3dUJ/DXf2yxPVPUceg==", + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.7.tgz", + "integrity": "sha512-VbA8ScMvAISJNJVbRDTJdCwqQoAareR/wutevKanhR2/1EkoXVZVkkORaYm/tNVCjP/UDTKtcw3bAkwOUdedmA==", "license": "MIT", "dependencies": { "esbuild": "^0.25.0", - "fdir": "^6.4.6", + "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", - "rollup": "^4.40.0", - "tinyglobby": "^0.2.14" + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" }, "bin": { "vite": "bin/vite.js" diff --git a/package.json b/package.json index 87671d1..ef6a8c4 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "dev": "vite", "build": "vite build", "lint": "eslint .", - "preview": "vite preview" + "preview": "vite preview", + "start":"node index.js" }, "dependencies": { "@react-pdf/renderer": "^4.3.0", diff --git a/src/components/AddTransactionModal.jsx b/src/components/AddTransactionModal.jsx index b277ecf..eb4c85a 100644 --- a/src/components/AddTransactionModal.jsx +++ b/src/components/AddTransactionModal.jsx @@ -1,3 +1,8 @@ +import { useState, useEffect } from 'react'; +import { useCurrency } from "./CurrencyContext"; +import { useTransactions } from './TransactionContext'; +import toast from 'react-hot-toast'; + const formatDate = (date) => { const d = new Date(date); const day = String(d.getDate()).padStart(2, '0'); @@ -6,104 +11,97 @@ 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, + transactionToEdit = null, + setTransactionToEdit = null // optional, parent can pass to clear edit state on close +}) { + // transaction functions from context (some apps expose different names) + const { + addTransaction, + editTransaction, // optional API name + updateTransaction, // optional API name + setTransactions, // optional fallback + transactions // optional fallback data + } = useTransactions(); -export default function AddTransactionModal({ showModal = true, setShowModal = () => {}, darkMode = false }) { - const { addTransaction } = useTransactions(); - const [form, setForm] = useState({ + const initialForm = { amount: "", category: "", type: "Expense", date: formatDate(new Date()), - note: " " - }); + note: "" + }; + + const [form, setForm] = useState(initialForm); const [isVisible, setIsVisible] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); - const { currency, locale, setCurrency, setLocale } = useCurrency(); + const { currency, locale } = useCurrency(); const [errors, setErrors] = useState({}); const suggestedCategories = ['Food', 'Transport', 'Groceries', 'Entertainment', 'Bills', 'Shopping', 'Rent', 'Utilities', 'EMI', 'Others']; const [categorySuggestions, setCategorySuggestions] = useState([]); const [showSuggestions, setShowSuggestions] = useState(false); + // currency symbol helper const getCurrencySymbol = (currency, locale) => { - return (0).toLocaleString(locale, { - style: "currency", - currency, - minimumFractionDigits: 0, - maximumFractionDigits: 0, - }).replace(/\d/g, "").trim(); + try { + return (0).toLocaleString(locale, { + style: "currency", + currency, + minimumFractionDigits: 0, + maximumFractionDigits: 0, + }).replace(/\d/g, "").trim(); + } catch { + return ""; + } }; + // show/hide animation state useEffect(() => { - if (showModal) { - setIsVisible(true); - } + if (showModal) setIsVisible(true); + else setIsVisible(false); }, [showModal]); + // prefill when editing (or reset when switching to add) + useEffect(() => { + if (transactionToEdit) { + setForm({ + amount: transactionToEdit.amount != null ? String(transactionToEdit.amount) : "", + category: transactionToEdit.category || "", + type: transactionToEdit.type || "Expense", + date: transactionToEdit.date || formatDate(new Date()), + note: transactionToEdit.note || "" + }); + } else if (showModal) { + setForm(initialForm); + } + // clear validation when transaction changes + setErrors({}); + }, [transactionToEdit, showModal]); + const validateForm = () => { const newErrors = {}; - if (!form.amount || parseFloat(form.amount) <= 0) { - newErrors.amount = "Please enter a valid amount"; + if (!form.amount || isNaN(parseFloat(form.amount)) || parseFloat(form.amount) <= 0) { + newErrors.amount = "Please enter a valid amount"; } + + if (!form.category || !form.category.trim()) { + if (form.type==="Expense" && !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; - - 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); + if (field === "date") { + // value from is YYYY-MM-DD, convert to DD/MM/YYYY + newValue = formatDate(new Date(value)); } if (field === "category") { @@ -115,42 +113,90 @@ const handleSubmit = async (e) => { setShowSuggestions(filtered.length > 0); } - setForm({ ...form, [field]: newValue }); - if (errors[field]) { - setErrors({ ...errors, [field]: " " }); + setForm(prev => ({ ...prev, [field]: newValue })); + if (errors[field]) setErrors(prev => ({ ...prev, [field]: undefined })); + }; + + const handleClose = () => { + setIsVisible(false); + // small delay to let animation run (matches your CSS timing) + setTimeout(() => { + setShowModal(false); + if (typeof setTransactionToEdit === "function") setTransactionToEdit(null); + }, 250); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + if (!validateForm()) return; + + setIsSubmitting(true); + + try { + const payload = { + ...form, + amount: parseFloat(form.amount), + date: form.date || formatDate(new Date()) + }; + + if (transactionToEdit && transactionToEdit.id != null) { + // EDIT flow + const updated = { ...payload, id: transactionToEdit.id }; + + if (typeof editTransaction === "function") { + // if your context exposes a named edit function + editTransaction(updated); + } else if (typeof updateTransaction === "function") { + // alternative API: updateTransaction(id, data) + updateTransaction(updated.id, updated); + } else if (typeof setTransactions === "function" && Array.isArray(transactions)) { + // fallback: mutate list directly in context + setTransactions(prev => prev.map(t => t.id === updated.id ? updated : t)); + } else { + // last fallback: call addTransaction (creates duplicate if no update available) + addTransaction(updated); + } + + toast.success("Transaction updated"); + } else { + // ADD flow + const newTransaction = { ...payload, id: Date.now() }; + addTransaction(newTransaction); + toast.success("Transaction added"); + } + + // reset & close + setForm(initialForm); + handleClose(); + } catch (err) { + console.error("Failed to save transaction:", err); + toast.error("Could not save transaction. Try again."); + } finally { + setIsSubmitting(false); + setErrors({}); } }; if (!showModal) return null; + const isEditMode = !!(transactionToEdit && transactionToEdit.id != null); + return ( -