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/BudgetDashboard/BudgetDashboard.css b/src/components/BudgetDashboard/BudgetDashboard.css index db02190..8ddf177 100644 --- a/src/components/BudgetDashboard/BudgetDashboard.css +++ b/src/components/BudgetDashboard/BudgetDashboard.css @@ -58,25 +58,29 @@ padding: 2rem; } - .back-button { - position: absolute; - top: -40px; - right: 0; - background-color: #66AA11; - color: white; - border: none; - padding: 8px 16px; - border-radius: 4px; - cursor: pointer; - font-size: 14px; - transition: background-color 0.2s; - } - - .back-button:hover { - background-color: #558800; - } - - .pie-chart-container { - position: relative; - /* ... existing styles ... */ - } \ No newline at end of file +.dashboard-header { + display: flex; + align-items: center; + margin-bottom: 1.5rem; +} + +.back-button { + background-color: #66AA11; + color: white; + border: none; + border-radius: 50%; + width: 32px; + height: 32px; + font-size: 18px; + cursor: pointer; + margin-right: 1rem; + display: flex; + align-items: center; + justify-content: center; + transition: background-color 0.2s; + transform: translateY(-10px); +} + +.back-button:hover { + background-color: #558800; +} diff --git a/src/components/BudgetDashboard/BudgetDashboard.js b/src/components/BudgetDashboard/BudgetDashboard.js index f5cdde2..1cac78b 100644 --- a/src/components/BudgetDashboard/BudgetDashboard.js +++ b/src/components/BudgetDashboard/BudgetDashboard.js @@ -6,7 +6,6 @@ import { Tooltip, Legend } from 'chart.js'; - import './BudgetDashboard.css'; ChartJS.register( @@ -21,6 +20,31 @@ const BudgetDashboard = ({ catData, catLabels, income, transactions }) => { const totalCategoryAmount = catData.reduce((a, b) => a + b, 0); const remainingAmount = Math.max(0, income - totalCategoryAmount); + // Helper function to get transactions from last 30 days + const getRecentTransactions = (category) => { + const thirtyDaysAgo = new Date(); + thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30); + + return transactions.filter(t => { + const transactionDate = new Date(t.date); + return t.category === category && transactionDate >= thirtyDaysAgo; + }); + }; + + // Calculate category-specific data + const getCategoryData = (category) => { + const categoryBudget = catData[catLabels.indexOf(category)]; + const recentTransactions = getRecentTransactions(category); + const spentAmount = recentTransactions.reduce((sum, t) => sum + t.amount, 0); + const remainingAmount = Math.max(0, categoryBudget - spentAmount); + + return { + budget: categoryBudget, + spent: spentAmount, + remaining: remainingAmount, + transactionCount: recentTransactions.length + }; + }; // Define bright colors for categories const categoryColors = [ @@ -36,50 +60,40 @@ const BudgetDashboard = ({ catData, catLabels, income, transactions }) => { '#45B7D1', // Sky blue ]; - const getMainPieData = () => ({ + + const pieData = selectedCategory ? { + labels: ['Spent', 'Remaining'], + datasets: [{ + data: [ + getCategoryData(selectedCategory).spent, + getCategoryData(selectedCategory).remaining + ], + backgroundColor: [categoryColors[catLabels.indexOf(selectedCategory)], '#E0E0E0'], + hoverBackgroundColor: [ + categoryColors[catLabels.indexOf(selectedCategory)].replace('FF', 'DD'), + '#CCCCCC' + ] + }] + } : { labels: [...catLabels, 'Remaining'], datasets: [{ data: [...catData, remainingAmount], backgroundColor: [ ...catLabels.map((_, index) => categoryColors[index % categoryColors.length]), - '#E0E0E0' // Light gray for remaining amount + + '#E0E0E0' + ], hoverBackgroundColor: [ ...catLabels.map((_, index) => { const color = categoryColors[index % categoryColors.length]; return color.replace('FF', 'DD'); }), - '#CCCCCC' // Slightly darker gray for remaining amount hover + + '#CCCCCC' ], - }], - }); - - const getTransactionPieData = () => { - const categoryTransactions = transactions.filter(t => t.category === selectedCategory); - const categoryIndex = catLabels.indexOf(selectedCategory); - const categoryBudget = catData[categoryIndex]; - const totalSpent = categoryTransactions.reduce((sum, t) => sum + t.amount, 0); - const remainingInCategory = Math.max(0, categoryBudget - totalSpent); - - return { - labels: [...categoryTransactions.map(t => t.memo || 'No memo'), 'Remaining'], - datasets: [{ - data: [...categoryTransactions.map(t => t.amount), remainingInCategory], - backgroundColor: [ - ...categoryTransactions.map((_, index) => - categoryColors[index % categoryColors.length] - ), - '#E0E0E0' // Same light gray for remaining amount - ], - hoverBackgroundColor: [ - ...categoryTransactions.map((_, index) => { - const color = categoryColors[index % categoryColors.length]; - return color.replace('FF', 'DD'); - }), - '#CCCCCC' // Same hover gray as main chart - ], - }], - }; + }] + }; const mainOptions = { @@ -89,9 +103,7 @@ const BudgetDashboard = ({ catData, catLabels, income, transactions }) => { labels: { color: 'white', padding: 20, - font: { - size: 14 - } + font: { size: 14 } } }, tooltip: { @@ -104,61 +116,71 @@ const BudgetDashboard = ({ catData, catLabels, income, transactions }) => { } } }, - maintainAspectRatio: true, - responsive: true, onClick: (event, elements) => { - if (elements.length > 0) { + if (elements.length > 0 && !selectedCategory) { const index = elements[0].index; if (index < catLabels.length) { // Don't select "Remaining" slice setSelectedCategory(catLabels[index]); } } - } - }; + }, + maintainAspectRatio: true, + responsive: true, - const transactionOptions = { - ...mainOptions, - onClick: undefined, // Remove click handler for transaction view - plugins: { - ...mainOptions.plugins, - tooltip: { - callbacks: { - label: function(context) { - const label = context.label || ''; - const value = context.raw || 0; - if (label === 'Remaining') { - return `Remaining: $${value.toFixed(2)}`; - } - const date = transactions.find(t => t.memo === label)?.date; - return [ - `${label}`, - `Amount: $${value.toFixed(2)}`, - date ? `Date: ${new Date(date).toLocaleDateString()}` : '', - ].filter(Boolean); - } - } - } - } - }; + elements: { + arc: { + cursor: (ctx) => { + const index = ctx.dataIndex; + return (!selectedCategory && index < catLabels.length) ? 'pointer' : 'default'; return (
-

Budget Overview

+
+ {selectedCategory && ( + + )} +

{selectedCategory ? `${selectedCategory} Overview` : 'Budget Overview'}

+
+
${income.toFixed(2)}
-
- - ${totalCategoryAmount.toFixed(2)} -
-
- - ${remainingAmount.toFixed(2)} -
+ {selectedCategory ? ( + <> +
+ + ${getCategoryData(selectedCategory).budget.toFixed(2)} +
+
+ + ${getCategoryData(selectedCategory).remaining.toFixed(2)} +
+
+ + {getCategoryData(selectedCategory).transactionCount} +
+ + ) : ( + <> +
+ + ${totalCategoryAmount.toFixed(2)} +
+
+ + ${remainingAmount.toFixed(2)} +
+ + )}
- +
{selectedCategory && (