From 9fa8f8e49e6c56f1c532ea995743dbee321f3db4 Mon Sep 17 00:00:00 2001 From: "LAPTOP-AL8JKVBA\\saura" Date: Sun, 8 Dec 2024 02:42:22 -0500 Subject: [PATCH] TransactionHistoryReincorporated --- oldhomepage.txt | 477 ++++++++++++++++++ src/components/ActionButtons/ActionButtons.js | 3 + src/components/Modals/LogTransactionModal.js | 19 +- src/components/Modals/Modal.css | 44 +- .../Modals/TransactionHistoryModal.js | 39 ++ src/components/Modals/index.js | 4 +- src/hooks/useBudgetData.js | 169 ++++--- src/pages/Homepage/Homepage.js | 37 +- 8 files changed, 693 insertions(+), 99 deletions(-) create mode 100644 oldhomepage.txt create mode 100644 src/components/Modals/TransactionHistoryModal.js diff --git a/oldhomepage.txt b/oldhomepage.txt new file mode 100644 index 0000000..c4b731d --- /dev/null +++ b/oldhomepage.txt @@ -0,0 +1,477 @@ +import React, { useState, useEffect } from 'react'; +import { Pie } from 'react-chartjs-2'; +import { Chart as ChartJS, ArcElement, Tooltip, Legend } from 'chart.js'; +import { doc, getDoc, setDoc, updateDoc, deleteDoc } from 'firebase/firestore'; +import { getAuth, onAuthStateChanged, signOut, deleteUser, reauthenticateWithCredential, EmailAuthProvider } from 'firebase/auth'; +import { db } from './firebase-config'; +import './homepage.css'; +import Modal from './Modal.js'; +import { set } from 'lodash'; + +ChartJS.register(ArcElement, Tooltip, Legend); + +const Homepage = () => { + const [catData, setCatData] = useState([]); + const [catLabels, setCatLabels] = useState([]); + const [showModal, setShowModal] = useState({ log: false, update: false, new: false, income: false, deleteAccount: false }); + const [formData, setFormData] = useState({ amount: '', category: '', memo: '', oldCategory: '', newCategory: { name: '', amount: 0 }, income: '' }); + const [userId, setUserId] = useState(null); + const [user, setUser] = useState(null); + const [tutorial, setTutorial] = useState(false); + const [currentStep, setCurrentStep] = useState(0); + const [income, setIncome] = useState(0); + const [password, setPassword] = useState(''); + const [transactionHistory, setTransactionHistory] = useState([]); + + const tutorialSteps = [ + { + title: 'Step 1: Set Up Income', + content: 'To set up your income, go to the income section and enter your monthly income.', + }, + { + title: 'Step 2: Create a Category', + content: 'To create a new category, click on the "New Category" button and fill in the details.', + }, + { + title: 'Step 3: Log a Transaction', + content: 'To log a transaction, click on the "Log Transaction" button and enter the transaction details.', + }, + ]; + + useEffect(() => { + const auth = getAuth(); + + const unsubscribe = onAuthStateChanged(auth, async (user) => { + if (user) { + setUser(user); + setUserId(user.uid); + const userId = user.uid; + const userDocRef = doc(db, 'users', userId); + + try { + const userDoc = await getDoc(userDocRef); + if (!userDoc.exists()) { + await setDoc( + userDocRef, + { + email: user.email, + name: user.displayName, + createdAt: new Date(), + budgetData: [], + tutorial: false, + }, + { merge: true } + ); + console.log('User document created with initial data'); + } else { + console.log('User document already exists. Data fetched successfully.'); + } + + const userData = userDoc.data(); + const budgetData = userData.budgetData || []; + const incomeEntry = budgetData.find(entry => entry.category === 'Income'); + const incomeAmount = incomeEntry ? incomeEntry.amount : 0; + setIncome(incomeAmount); + + const categories = budgetData.filter(entry => entry.type === 'category').map((entry) => entry.category); + const amounts = budgetData.filter(entry => entry.type === 'category').map((entry) => entry.amount); + + setCatData(amounts); + setCatLabels(categories); + setTutorial(userData.tutorial || false); + + const transactions = budgetData + .filter(entry => entry.type === 'category') + .flatMap(entry => entry.transactions || []) + .map(transaction => ({ + category: transaction.category, + memo: transaction.memo, + amount: transaction.amount, + date: transaction.date, + time: transaction.time + })) + .sort((a, b) => new Date(b.date + ' ' + b.time) - new Date(a.date + ' ' + a.time)); + setTransactionHistory(transactions); + } catch (error) { + console.error('Error checking or creating user document:', error); + } + } else { + console.log('No user is currently logged in'); + } + }); + + return unsubscribe; + }, []); + + const handleOpenModal = (type) => { + setShowModal({ ...showModal, [type]: true }); + }; + + const handleCloseModal = () => { + setShowModal({ log: false, update: false, new: false, income: false, deleteAccount: false }); + setFormData({ amount: '', category: '', memo: '', oldCategory: '', newCategory: { name: '', amount: 0 }, income: '' }); + }; + + const handleInputChange = (e) => { + const { name, value } = e.target; + const keys = name.split('.'); + setFormData((prevState) => { + let newState = { ...prevState }; + let current = newState; + for (let i = 0; i < keys.length - 1; i++) { + current = current[keys[i]]; + } + current[keys[keys.length - 1]] = value; + return newState; + }); + }; + + const handleFormSubmit = async (event, type) => { + event.preventDefault(); + + if (user && userId) { + const userDocRef = doc(db, 'users', userId); + + try { + const userDoc = await getDoc(userDocRef); + let updatedData; + + if (userDoc.exists()) { + const currentData = userDoc.data().budgetData || []; + + if (type === 'log') { + const newTransaction = { + amount: parseInt(formData.amount), + memo: formData.memo, + type: 'transaction', + date: new Date().toLocaleDateString(), + time: new Date().toLocaleTimeString(), + category: formData.category + }; + updatedData = currentData.map((entry) => { + if (entry.category === formData.category && entry.type === 'category') { + return { ...entry, transactions: [newTransaction, ...(entry.transactions || [])] }; + } + return entry; + }); + + await setDoc(userDocRef, { budgetData: updatedData }, { merge: true }); + setFormData({...formData, amount: '', memo: ''}); + + setTransactionHistory(prevHistory => [{ ...newTransaction }, ...prevHistory]); + } else if (type === 'update') { + updatedData = currentData.map((entry) => + entry.category === formData.oldCategory ? { ...entry, category: formData.newCategory.name, amount: parseInt(formData.newCategory.amount), type: 'category' } : entry + ); + updatedData = updatedData.filter((entry, index, self) => + index === self.findIndex((e) => e.category === entry.category) + ); + await updateDoc(userDocRef, { budgetData: updatedData }); + } else if (type === 'new') { + const newEntry = { category: formData.newCategory.name, amount: parseInt(formData.newCategory.amount), type: 'category', transactions: [] }; + updatedData = [...currentData, newEntry]; + await updateDoc(userDocRef, { budgetData: updatedData }); + } else if (type === 'income') { + const incomeEntry = { category: 'Income', amount: parseInt(formData.income), type: 'income' }; + updatedData = currentData.filter(entry => entry.category !== 'Income'); + updatedData.push(incomeEntry); + await updateDoc(userDocRef, { budgetData: updatedData }); + setIncome(incomeEntry.amount); + } + + setCatData(updatedData.filter(entry => entry.type === 'category').map((entry) => entry.amount)); + setCatLabels(updatedData.filter(entry => entry.type === 'category').map((entry) => entry.category)); + console.log('Data updated successfully'); + } else { + const newEntry = { + category: formData.category, + memo: formData.memo, + amount: parseInt(formData.amount), + type: 'transaction', + date: new Date().toLocaleDateString(), + time: new Date().toLocaleTimeString() + }; + updatedData = [newEntry]; + + await setDoc( + userDocRef, + { + email: user.email, + name: user.displayName, + createdAt: new Date(), + budgetData: updatedData, + }, + { merge: true } + ); + setCatData(updatedData.filter(entry => entry.type === 'category').map((entry) => entry.amount)); + setCatLabels(updatedData.filter(entry => entry.type === 'category').map((entry) => entry.category)); + } + + handleCloseModal(); + } catch (error) { + console.error('Error updating Firestore:', error); + } + } else { + console.error('No user is currently authenticated'); + } + }; + + const handleNextStep = () => { + if (currentStep < tutorialSteps.length - 1) { + setCurrentStep(currentStep + 1); + } + }; + + const handlePrevStep = () => { + if (currentStep > 0) { + setCurrentStep(currentStep - 1); + } + }; + + const handleTutorialClick = async () => { + if (user && userId) { + const userDocRef = doc(db, 'users', userId); + try { + await updateDoc(userDocRef, { tutorial: true }); + setTutorial(true); + console.log('Tutorial status updated successfully'); + } catch (error) { + console.error('Error updating tutorial status:', error); + } + } else { + console.error('No user is currently authenticated'); + } + }; + + const handleExitTutorial = async () => { + if (user && userId) { + const userDocRef = doc(db, 'users', userId); + try { + await updateDoc(userDocRef, { tutorial: false }); + setTutorial(false); + console.log('Tutorial status updated successfully'); + } catch (error) { + console.error('Error updating tutorial status:', error); + } + } else { + console.error('No user is currently authenticated'); + } + }; + + const handleClearData = async () => { + if (user && userId) { + const userDocRef = doc(db, 'users', userId); + try { + await updateDoc(userDocRef, { budgetData: [] }); + setCatData([]); + setCatLabels([]); + setTransactionHistory([]); + console.log('Data cleared successfully'); + } catch (error) { + console.error('Error clearing data:', error); + } + } else { + console.error('No user is currently authenticated'); + } + }; + + const handleLogout = async () => { + const auth = getAuth(); + try { + await signOut(auth); + console.log('User logged out successfully'); + window.location.href = '/'; + } catch (error) { + console.error('Logout error:', error.message); + alert('Logout failed'); + } + }; + + const handleDeleteAccount = async () => { + if (user && userId) { + const userDocRef = doc(db, 'users', userId); + try { + await deleteDoc(userDocRef); + await deleteUser(user); + console.log('Account and data deleted successfully'); + window.location.href = '/'; + } catch (error) { + console.error('Account deletion error:', error.message); + alert('Account deletion failed'); + } + } else { + console.error('No user is currently authenticated'); + } + }; + + const totalCategoryAmount = catData.reduce((a, b) => a + b, 0); + const remainingAmount = Math.max(0, income - totalCategoryAmount); + + const pieData = { + labels: [...catLabels, 'Remaining'], + datasets: [ + { + label: 'Dollars Spent', + data: [...catData, remainingAmount], + backgroundColor: [...catLabels.map(() => '#66AA11'), '#CCCCCC'], + hoverBackgroundColor: [...catLabels.map(() => '#66AA11'), '#CCCCCC'], + }, + ], + }; + + const options = { + plugins: { + legend: { + labels: { + color: 'white', + }, + }, + }, + }; + + const tutorialBoxStyle = { + position: 'fixed', + top: '20%', + left: '50%', + transform: 'translate(-50%, -20%)', + width: '80%', + maxWidth: '600px', + padding: '20px', + backgroundColor: 'white', + boxShadow: '0 4px 8px rgba(0, 0, 0, 0.1)', + zIndex: 1000, + }; + + return ( +
+
Money Gremlin
+
+
+

Budget Dashboard

+
+ {catData.length > 0 ? ( + + ) : ( +

No data available to display

+ )} +
+
+
+
handleOpenModal('log')}> + Log Transaction +
+
handleOpenModal('update')}> + Update Category +
+
handleOpenModal('new')}> + New Category +
+
handleOpenModal('income')}> + Change Income +
+
handleOpenModal('history')}> + Transaction History +
+
+
+ + +

Log Transaction

+
handleFormSubmit(e, 'log')}> + + + + + + + +
+
+ + +

Update Category

+
handleFormSubmit(e, 'update')}> + + + + + + + +
+
+ + +

New Category

+
handleFormSubmit(e, 'new')}> + + + + + +
+
+ + +

Change Income

+
handleFormSubmit(e, 'income')}> + + + +
+
+ + +

Transaction History

+ + + + + + + + + + + + {transactionHistory.map((transaction, index) => ( + + + + + + + + ))} + +
DateTimeCategoryMemoAmount
{transaction.date}{transaction.time}{transaction.category}{transaction.memo}{transaction.amount}
+
+ + +

Delete Account

+

Are you sure you want to delete your account? This will erase all data associated with the account and is irreversible.

+ + +
+ +
+

+ Account Info | | Privacy Policy | Contact | + Disclaimer | Downtime Information | | | +

+
+
+ ); +}; + +export default Homepage; \ No newline at end of file diff --git a/src/components/ActionButtons/ActionButtons.js b/src/components/ActionButtons/ActionButtons.js index 933ae74..2c5a407 100644 --- a/src/components/ActionButtons/ActionButtons.js +++ b/src/components/ActionButtons/ActionButtons.js @@ -16,6 +16,9 @@ const ActionButtons = ({ onActionClick }) => {
onActionClick('income')}> Change Income
+
onActionClick('transactionHistory')}> + Transaction History +
); }; diff --git a/src/components/Modals/LogTransactionModal.js b/src/components/Modals/LogTransactionModal.js index 4b05d7f..1027281 100644 --- a/src/components/Modals/LogTransactionModal.js +++ b/src/components/Modals/LogTransactionModal.js @@ -2,15 +2,20 @@ import React from 'react'; import Modal from './Modal'; export const LogTransactionModal = ({ show, onClose, onSubmit, categories }) => { - const handleSubmit = (e) => { + const handleSubmit = async (e) => { e.preventDefault(); const formData = new FormData(e.target); - onSubmit({ - category: formData.get('category'), - memo: formData.get('memo'), - amount: parseFloat(formData.get('amount')) - }, 'log'); - onClose(); + try { + await onSubmit({ + category: formData.get('category'), + memo: formData.get('memo'), + amount: parseFloat(formData.get('amount')) + }, 'log'); + onClose(); + } catch (error) { + console.error('Failed to log transaction:', error); + alert('Failed to update budget data'); + } }; return ( diff --git a/src/components/Modals/Modal.css b/src/components/Modals/Modal.css index bca0f5b..eef1827 100644 --- a/src/components/Modals/Modal.css +++ b/src/components/Modals/Modal.css @@ -66,4 +66,46 @@ .modal-form button:hover { background-color: #558800; - } \ No newline at end of file + } + +/* Transaction History Table Styles */ +.transaction-table-container { + max-height: 400px; + overflow-y: auto; + margin-top: 1rem; +} + +.transaction-table { + width: 100%; + border-collapse: collapse; + background-color: #1a1d23; + color: white; +} + +.transaction-table th, +.transaction-table td { + padding: 8px; + text-align: left; + border-bottom: 1px solid #444; +} + +.transaction-table th { + background-color: #66AA11; + color: white; + position: sticky; + top: 0; +} + +.transaction-table tr:nth-child(even) { + background-color: #282c34; +} + +.transaction-table tr:hover { + background-color: #333842; +} + +.no-transactions { + text-align: center; + padding: 20px; + color: #888; +} \ No newline at end of file diff --git a/src/components/Modals/TransactionHistoryModal.js b/src/components/Modals/TransactionHistoryModal.js new file mode 100644 index 0000000..1c5b632 --- /dev/null +++ b/src/components/Modals/TransactionHistoryModal.js @@ -0,0 +1,39 @@ +import React from 'react'; +import Modal from './Modal'; +import './Modal.css'; + +export const TransactionHistoryModal = ({ show, onClose, transactions }) => { + return ( + +

Transaction History

+
+ {transactions && transactions.length > 0 ? ( + + + + + + + + + + + + {transactions.map((transaction, index) => ( + + + + + + + + ))} + +
DateTimeCategoryMemoAmount
{transaction.date}{transaction.time}{transaction.category}{transaction.memo}${transaction.amount.toFixed(2)}
+ ) : ( +
No transactions to display
+ )} +
+
+ ); +}; \ No newline at end of file diff --git a/src/components/Modals/index.js b/src/components/Modals/index.js index d4b829a..5cbe298 100644 --- a/src/components/Modals/index.js +++ b/src/components/Modals/index.js @@ -3,11 +3,13 @@ import { NewCategoryModal } from './NewCategoryModal'; import { IncomeModal } from './IncomeModal'; import { DeleteAccountModal } from './DeleteAccountModal'; import { UpdateCategoryModal } from './UpdateCategoryModal'; +import { TransactionHistoryModal } from './TransactionHistoryModal'; export { LogTransactionModal, NewCategoryModal, IncomeModal, DeleteAccountModal, - UpdateCategoryModal + UpdateCategoryModal, + TransactionHistoryModal }; \ No newline at end of file diff --git a/src/hooks/useBudgetData.js b/src/hooks/useBudgetData.js index 645ebce..8db46fa 100644 --- a/src/hooks/useBudgetData.js +++ b/src/hooks/useBudgetData.js @@ -10,24 +10,39 @@ export const useBudgetData = (userId) => { const fetchData = async () => { if (userId) { - const userDocRef = doc(db, 'users', userId); - const userDoc = await getDoc(userDocRef); - if (userDoc.exists()) { - const userData = userDoc.data(); - const budgetData = userData.budgetData || []; - const transactions = userData.transactions || []; - - // Set income - const incomeEntry = budgetData.find(entry => entry.category === 'Income'); - setIncome(incomeEntry ? incomeEntry.amount : 0); - - // Set categories and amounts - const categories = budgetData.filter(entry => entry.type === 'category'); - setCatData(categories.map(entry => entry.amount)); - setCatLabels(categories.map(entry => entry.category)); - - // Set transactions - setTransactions(transactions); + try { + const userDocRef = doc(db, 'users', userId); + const userDoc = await getDoc(userDocRef); + + if (userDoc.exists()) { + const userData = userDoc.data(); + const budgetData = userData.budgetData || []; + + // Set income + const incomeEntry = budgetData.find(entry => entry.category === 'Income'); + setIncome(incomeEntry ? incomeEntry.amount : 0); + + // Set categories and amounts + const categories = budgetData + .filter(entry => entry.type === 'category') + .map(entry => entry.category); + const amounts = budgetData + .filter(entry => entry.type === 'category') + .map(entry => entry.amount); + + setCatData(amounts); + setCatLabels(categories); + + // Set transactions + const allTransactions = budgetData + .filter(entry => entry.type === 'category') + .flatMap(entry => entry.transactions || []) + .sort((a, b) => new Date(b.date + ' ' + b.time) - new Date(a.date + ' ' + a.time)); + + setTransactions(allTransactions); + } + } catch (error) { + console.error('Error fetching budget data:', error); } } }; @@ -39,82 +54,78 @@ export const useBudgetData = (userId) => { const handleFormSubmit = async (formData, type) => { if (!userId) return; - const userDocRef = doc(db, 'users', userId); - const userDoc = await getDoc(userDocRef); - let budgetData = userDoc.exists() ? userDoc.data().budgetData || [] : []; - let transactions = userDoc.exists() ? userDoc.data().transactions || [] : []; - try { + const userDocRef = doc(db, 'users', userId); + const userDoc = await getDoc(userDocRef); + const currentData = userDoc.exists() ? userDoc.data().budgetData || [] : []; + let updatedData; + switch (type) { - case 'new': - // Add new category - budgetData.push({ + case 'log': + const newTransaction = { category: formData.category, - amount: formData.amount, - type: 'category' + memo: formData.memo, + amount: parseFloat(formData.amount), + date: new Date().toLocaleDateString(), + time: new Date().toLocaleTimeString(), + type: 'transaction' + }; + + updatedData = currentData.map(entry => { + if (entry.category === formData.category && entry.type === 'category') { + return { + ...entry, + transactions: [newTransaction, ...(entry.transactions || [])] + }; + } + return entry; }); + + await updateDoc(userDocRef, { budgetData: updatedData }); + setTransactions(prevTransactions => [newTransaction, ...prevTransactions]); break; - case 'update': - // Update existing category - const categoryIndex = budgetData.findIndex( - item => item.category === formData.oldCategory - ); - if (categoryIndex !== -1) { - budgetData[categoryIndex] = { - category: formData.newCategory || formData.oldCategory, - amount: formData.amount, - type: 'category' - }; - // Update category name in transactions if it changed - if (formData.newCategory && formData.newCategory !== formData.oldCategory) { - transactions = transactions.map(t => ({ - ...t, - category: t.category === formData.oldCategory ? formData.newCategory : t.category - })); - } - } + case 'new': + const newCategory = { + category: formData.name, + amount: formData.amount, + type: 'category', + transactions: [] + }; + updatedData = [...currentData, newCategory]; + await updateDoc(userDocRef, { budgetData: updatedData }); + await fetchData(); break; - case 'income': - // Update income - const incomeIndex = budgetData.findIndex( - item => item.category === 'Income' + case 'update': + updatedData = currentData.map(entry => + entry.category === formData.oldCategory + ? { ...entry, category: formData.name, amount: formData.amount } + : entry ); - if (incomeIndex !== -1) { - budgetData[incomeIndex].amount = formData.amount; - } else { - budgetData.push({ - category: 'Income', - amount: formData.amount, - type: 'income' - }); - } + await updateDoc(userDocRef, { budgetData: updatedData }); + await fetchData(); break; - case 'log': - // Log a transaction without changing category amount - transactions.push({ - category: formData.category, + case 'income': + const incomeEntry = { + category: 'Income', amount: formData.amount, - memo: formData.memo, - date: new Date().toISOString() - }); + type: 'income' + }; + updatedData = currentData.filter(entry => entry.category !== 'Income'); + updatedData.push(incomeEntry); + await updateDoc(userDocRef, { budgetData: updatedData }); + setIncome(formData.amount); break; - } - - // Update Firestore - await updateDoc(userDocRef, { - budgetData, - transactions - }); - // Refresh the local state - await fetchData(); + default: + return; + } } catch (error) { console.error('Error updating budget data:', error); - alert('Failed to update budget data'); + throw error; } }; @@ -123,10 +134,8 @@ export const useBudgetData = (userId) => { try { const userDocRef = doc(db, 'users', userId); await updateDoc(userDocRef, { - budgetData: [{ category: 'Income', amount: 0, type: 'income' }], - transactions: [] + budgetData: [{ category: 'Income', amount: 0, type: 'income' }] }); - // Reset local state setCatData([]); setCatLabels([]); setIncome(0); diff --git a/src/pages/Homepage/Homepage.js b/src/pages/Homepage/Homepage.js index c61a6b3..e505055 100644 --- a/src/pages/Homepage/Homepage.js +++ b/src/pages/Homepage/Homepage.js @@ -1,4 +1,4 @@ -import React, { useState} from 'react'; +import React, { useState } from 'react'; import Header from '../../components/Header'; import BudgetDashboard from '../../components/BudgetDashboard'; import ActionButtons from '../../components/ActionButtons'; @@ -9,8 +9,9 @@ import { UpdateCategoryModal, NewCategoryModal, IncomeModal, - DeleteAccountModal - } from '../../components/Modals'; + DeleteAccountModal, + TransactionHistoryModal +} from '../../components/Modals'; import { useAuth } from '../../hooks/useAuth'; import { useBudgetData } from '../../hooks/useBudgetData'; import { useTutorial } from '../../hooks/useTutorial'; @@ -19,19 +20,26 @@ import './Homepage.css'; const Homepage = () => { const [activeModal, setActiveModal] = useState(null); const { user, handleLogout, handleDeleteAccount } = useAuth(); - const { catData, catLabels, income, handleFormSubmit, handleClearData } = useBudgetData(user?.uid); - const {tutorial, currentStep, handleTutorialActions } = useTutorial(user?.uid); + const { + catData, + catLabels, + income, + transactions, + handleFormSubmit, + handleClearData + } = useBudgetData(user?.uid); + const { tutorial, currentStep, handleTutorialActions } = useTutorial(user?.uid); return (
- {
} +
{tutorial && ( - )} + )}
{
- {/* Modal components */} setActiveModal(null)} onSubmit={handleFormSubmit} categories={catLabels} /> + setActiveModal(null)} onSubmit={handleFormSubmit} categories={catLabels} /> + setActiveModal(null)} onSubmit={handleFormSubmit} /> + setActiveModal(null)} onSubmit={handleFormSubmit} /> + setActiveModal(null)} onConfirm={handleDeleteAccount} /> - {
); };