mirror of
https://github.com/gabehf/BudgetBuddy.git
synced 2026-03-09 15:38:17 -07:00
Merge branch 'main' of https://github.com/jacobmveber-01839764/BudgetBuddy
This commit is contained in:
commit
719cf5d1b8
20 changed files with 458 additions and 302 deletions
|
|
@ -71,7 +71,7 @@ export default function Main() {
|
|||
<ProSidebarProvider>
|
||||
<div className="main-body">
|
||||
<SideNav />
|
||||
<div className="page-display elevated">
|
||||
<div className="page-display">
|
||||
<Layout />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,82 @@
|
|||
import React, { useContext, useState } from 'react';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { AppContext } from '../context/AppContext';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import './AddExpenseForm.css'
|
||||
import { getSessionKey } from '../utils/utils.js'
|
||||
|
||||
const AddExpenseForm = (props) => {
|
||||
const { dispatch } = useContext(AppContext);
|
||||
|
||||
const [name, setName] = useState('');
|
||||
const [cost, setCost] = useState('');
|
||||
const [category, setCategory] = useState('');
|
||||
const [categoryList, setCategoryList] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
fetch('https://api.bb.gabefarrell.com/w/budget', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'x-session-key' : getSessionKey(),
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.status != 200) {
|
||||
console.log(data.error);
|
||||
} else {
|
||||
setCategoryList(data.categories);
|
||||
}
|
||||
})
|
||||
} catch(error) {
|
||||
console.error(error);
|
||||
}
|
||||
}, [])
|
||||
|
||||
const onSubmit = (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
const formData = new FormData();
|
||||
|
||||
let currency = "USD"
|
||||
let whole = 0;
|
||||
let decimal = 0;
|
||||
let type = 'expenses';
|
||||
|
||||
|
||||
if (cost.includes(".")) {
|
||||
whole = parseInt(cost.split(".")[0]);
|
||||
decimal = parseInt(cost.split(".")[1]);
|
||||
} else {
|
||||
whole = parseInt(cost);
|
||||
}
|
||||
formData.append('category', category)
|
||||
formData.append('currency', currency);
|
||||
formData.append('whole', whole);
|
||||
formData.append('decimal', decimal);
|
||||
formData.append('type', type)
|
||||
|
||||
try {
|
||||
fetch(`https://api.bb.gabefarrell.com/w/transactions?whole=${whole}&decimal=${decimal}¤cy=${currency}&category=${category}&type=${type}`, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: {
|
||||
'x-session-key' : getSessionKey(),
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.status != 200) {
|
||||
console.log(data.error);
|
||||
} else {
|
||||
|
||||
}
|
||||
})
|
||||
} catch(error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
/*
|
||||
const expense = {
|
||||
id: uuidv4(),
|
||||
name,
|
||||
|
|
@ -21,60 +87,82 @@ const AddExpenseForm = (props) => {
|
|||
type: 'ADD_EXPENSE',
|
||||
payload: expense,
|
||||
});
|
||||
*/
|
||||
|
||||
setName('');
|
||||
setCost('');
|
||||
};
|
||||
|
||||
const handleAddCategory = () => {
|
||||
const newCategory = prompt('Enter the new category name:');
|
||||
if (newCategory) {
|
||||
if (categoryList.indexOf(newCategory) == -1) {
|
||||
const newCategories = [...categoryList, newCategory];
|
||||
setCategoryList(newCategories);
|
||||
}
|
||||
setCategory(newCategory);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={onSubmit}>
|
||||
<div className='row alert alert-secondary'>
|
||||
{/* <div className='col-sm col-lg-4'>
|
||||
<form htmlFor="name">
|
||||
<label >Choose a category:</label>
|
||||
<select>
|
||||
<option required='required'
|
||||
type='text'
|
||||
className='form-control'
|
||||
id='name'
|
||||
value={name}
|
||||
onChange={(event) => setName(event.target.value)}>Rent</option>
|
||||
</select>
|
||||
</form>
|
||||
</div> */}
|
||||
<h3>Add Expense</h3>
|
||||
<div className='col-sm col-lg-4'>
|
||||
<label htmlFor='name'>Name</label>
|
||||
<input
|
||||
required='required'
|
||||
type='text'
|
||||
className='form-control'
|
||||
id='name'
|
||||
value={name}
|
||||
onChange={(event) => setName(event.target.value)}
|
||||
></input>
|
||||
|
||||
</div>
|
||||
<div className='col-sm col-lg-4'>
|
||||
<label htmlFor='cost'>Cost</label>
|
||||
<input
|
||||
required='required'
|
||||
type='number'
|
||||
className='form-control'
|
||||
id='cost'
|
||||
value={cost}
|
||||
onChange={(event) => setCost(event.target.value)}
|
||||
/>
|
||||
<div className='widget'>
|
||||
<h4>Add Expense</h4>
|
||||
<form onSubmit={onSubmit}>
|
||||
<div className='row'>
|
||||
{/*
|
||||
<div className='col-md col-lg-4'>
|
||||
<label htmlFor='name'>Name</label>
|
||||
<input
|
||||
required='required'
|
||||
type='text'
|
||||
className='form-control'
|
||||
id='name'
|
||||
value={name}
|
||||
onChange={(event) => setName(event.target.value)}
|
||||
></input>
|
||||
</div>
|
||||
*/}
|
||||
</div>
|
||||
<div className='row mt-3'>
|
||||
<div className='col-sm'>
|
||||
<button type='submit' className='btn btn-primary'>
|
||||
Save
|
||||
</button>
|
||||
<div className='row'>
|
||||
<div className='col-md col-lg-4'>
|
||||
<label htmlFor='cost'>Cost</label>
|
||||
<input
|
||||
required='required'
|
||||
type='number'
|
||||
className='form-control'
|
||||
id='cost'
|
||||
min="0.00"
|
||||
step=".01"
|
||||
value={cost}
|
||||
onChange={(event) => setCost(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className='col-md col-lg-4'>
|
||||
<label htmlFor='category-select'>Category</label>
|
||||
<select className="form-select" id='category-select'
|
||||
value={category}
|
||||
onChange={(event) => setCategory(event.target.value)}>
|
||||
|
||||
{categoryList.map((category) => (
|
||||
<option key={category} value={category}>
|
||||
{category}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div></div>
|
||||
|
||||
</form>
|
||||
<div className='row mt-3'>
|
||||
<div className='col-sm'>
|
||||
<button type='submit' className='btn btn-primary'>
|
||||
Add Expense
|
||||
</button>
|
||||
<button className='btn btn-primary mx-3' onClick={handleAddCategory}>
|
||||
Add New Category
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
84
src/components/CategorizedBudget.js
Normal file
84
src/components/CategorizedBudget.js
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
import { Doughnut } from "react-chartjs-2";
|
||||
import { Chart as ChartJS, ArcElement, Tooltip, Legend, defaults } from 'chart.js';
|
||||
|
||||
import { AppProvider } from "../context/AppContext";
|
||||
import { useState, useEffect } from 'react'
|
||||
import { getSessionKey } from '../utils/utils.js'
|
||||
|
||||
ChartJS.register(ArcElement, Tooltip, Legend);
|
||||
|
||||
export default function CategorizedBudget() {
|
||||
const [chartData, setChartData] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
async function getChartData() {
|
||||
try {
|
||||
fetch('https://api.bb.gabefarrell.com/w/budget', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'x-session-key' : 'b36efa01-7824-4f61-a274-63131b58d8fe',
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.status != 200) {
|
||||
console.log(data.error);
|
||||
} else {
|
||||
const chartData = {
|
||||
labels: [data.categories.length > 0 ? data.categories : "no expenses"],
|
||||
datasets: [
|
||||
{
|
||||
data: data.expenses_by_category.length > 0
|
||||
? data.expenses_by_category.map(category => {
|
||||
return category['whole']
|
||||
})
|
||||
: [1],
|
||||
backgroundColor: [
|
||||
'rgba(255, 99, 132, 0.2)',
|
||||
'rgba(54, 162, 235, 0.2)',
|
||||
'rgba(255, 206, 86, 0.2)',
|
||||
'rgba(75, 192, 192, 0.2)',
|
||||
'rgba(153, 102, 255, 0.2)',
|
||||
'rgba(255, 159, 64, 0.2)',
|
||||
],
|
||||
borderColor: [
|
||||
"black"
|
||||
],
|
||||
borderWidth: 1,
|
||||
},
|
||||
],
|
||||
}
|
||||
setChartData(chartData);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error);
|
||||
});
|
||||
} catch(error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
getChartData();
|
||||
}, []);
|
||||
|
||||
if (!chartData) {
|
||||
return <p>Loading...</p>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="widget">
|
||||
<h4>Expenses by Category</h4>
|
||||
<Doughnut className="w-100 h-auto"
|
||||
data={chartData}
|
||||
options={{
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'right'
|
||||
}
|
||||
},
|
||||
responsive: true
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -19,13 +19,14 @@ const ExpenseList = () => {
|
|||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='widget'>
|
||||
{/* <input
|
||||
type='text'
|
||||
className='form-control mb-2 mr-sm-2'
|
||||
placeholder='Type to search...'
|
||||
onChange={handleChange}
|
||||
/> */}
|
||||
<h4>Expenses</h4>
|
||||
<ul className='list-group mt-3 mb-3'>
|
||||
{filteredExpenses.map((expense) => (
|
||||
<ExpenseItem
|
||||
|
|
@ -35,7 +36,7 @@ const ExpenseList = () => {
|
|||
/>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ const ExpenseTotal = () => {
|
|||
}, 0);
|
||||
|
||||
return (
|
||||
<div className='alert alert-secondary p-4'>
|
||||
<div className='widget p-4'>
|
||||
<img src={logo}></img>
|
||||
<span>This Month's Expenses: ${total}</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
.bar {
|
||||
padding: 10px;
|
||||
margin-bottom: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { async } from "q";
|
|||
|
||||
export default function NavBar() {
|
||||
const [name, setName] = useState(null);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchName() {
|
||||
|
|
@ -21,11 +22,11 @@ export default function NavBar() {
|
|||
}, []);
|
||||
|
||||
return (
|
||||
<Toolbar position="sticky" className="bar">
|
||||
<Toolbar position="sticky" className="navbar bar py-3 px-4">
|
||||
<Link href="/" className="nav-brand" underline="none">
|
||||
<img src={logo} className="logo w-100"/>
|
||||
<Typography
|
||||
variant="h5"
|
||||
variant="h4"
|
||||
className="nav-header">
|
||||
BudgetBuddy
|
||||
</Typography>
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ const RemainingBudget = () => {
|
|||
const alertType = totalExpenses > budget ? 'alert-danger' : 'alert-success';
|
||||
|
||||
return (
|
||||
<div className={`alert p-4 ${alertType}`}>
|
||||
<div className={`alert p-4 ${alertType} widget`}>
|
||||
<img src={logo}></img>
|
||||
<span>Budget Remaining: ${budget - totalExpenses}</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ export default function SideNav() {
|
|||
<Avatar />
|
||||
</div>
|
||||
<div className='sidebar-text'>
|
||||
<Typography className='sidebar-welcome' variant='h6'>Welcome, {firstName}</Typography>
|
||||
<Typography className='sidebar-welcome' variant='h6'>Welcome, {firstName}!</Typography>
|
||||
<Typography variant='subtitle2'> Your Budget Overiew</Typography>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ const Budget = () => {
|
|||
};
|
||||
|
||||
return (
|
||||
<div className='alert alert-secondary d-flex align-items-center justify-content-between'>
|
||||
<div className='widget d-flex align-items-center justify-content-between'>
|
||||
{isEditing ? (
|
||||
<EditBudget handleSaveClick={handleSaveClick} budget={budget} />
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -8,13 +8,14 @@ import ExpenseList from '../components/ExpenseList';
|
|||
import AddExpenseForm from '../components/AddExpenseForm';
|
||||
import RemainingBudget from '../components/Remaining';
|
||||
import AddIncome from '../components/AddIncome'
|
||||
import CategorizedBudget from '../components/CategorizedBudget';
|
||||
import { Typography } from '@mui/material';
|
||||
|
||||
export default function Dashboard() {
|
||||
return (
|
||||
<AppProvider>
|
||||
<div className='container-fluid'>
|
||||
<h1 className='mt-3'>My Budget Planner</h1>
|
||||
<div className='row mt-3'>
|
||||
<div className='row align-items-stretch'>
|
||||
<div className='col-sm'>
|
||||
<FetchAPI />
|
||||
</div>
|
||||
|
|
@ -27,19 +28,24 @@ export default function Dashboard() {
|
|||
</div>
|
||||
|
||||
<div className='row mt-3'>
|
||||
<div className='col-sm alert alert-secondary'>
|
||||
<h3 className='mt-3'>Expenses</h3><ExpenseList />
|
||||
<div className='col'>
|
||||
<ExpenseList />
|
||||
</div>
|
||||
</div>
|
||||
<div className='row mt-3'>
|
||||
<div className='col-sm'>
|
||||
<div className='col'>
|
||||
<AddExpenseForm />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className='row mt-3'>
|
||||
<div className='col-sm'>
|
||||
{/*<h3 className='mt-3'>Add Income</h3><AddIncome /> */}
|
||||
<div className='col-12 col-lg-6'>
|
||||
<CategorizedBudget/>
|
||||
</div>
|
||||
<div className='col-12 col-lg-6'>
|
||||
<div className='widget'>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export default function Login() {
|
|||
formData.append('password', password);
|
||||
|
||||
// Send the form data via HTTP using Fetch API
|
||||
fetch(`http://127.0.0.1:3030/auth/login?email=${email}&password=${password}`, {
|
||||
fetch(`https://api.bb.gabefarrell.com/auth/login?email=${email}&password=${password}`, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
})
|
||||
|
|
@ -29,7 +29,6 @@ export default function Login() {
|
|||
document.cookie = `session=${session}; path=/;`
|
||||
window.location.href = '/dashboard';
|
||||
}
|
||||
|
||||
console.log(data); // Log the response from the server
|
||||
})
|
||||
.catch((error) => {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export default function SignUp() {
|
|||
formData.append('password', password);
|
||||
|
||||
// Send the form data via HTTP using Fetch API
|
||||
fetch(`http://127.0.0.1:3030/auth/createaccount?name=${name}&email=${email}&password=${password}`, {
|
||||
fetch(`https://api.bb.gabefarrell.com/auth/createaccount?name=${name}&email=${email}&password=${password}`, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -13,39 +13,39 @@ code {
|
|||
}
|
||||
|
||||
.app {
|
||||
background-color: #F7F7F2;
|
||||
color: #444444;
|
||||
background-color: #EFEFE6;
|
||||
color: #191910;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.main-body {
|
||||
display: flex;
|
||||
width: 100vw;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.page-display {
|
||||
margin: 0 40px;
|
||||
padding: 0 10px;
|
||||
margin-bottom: 30px;
|
||||
border-radius: 20px;
|
||||
padding: 20px 30px;
|
||||
width: 100vw;
|
||||
background-color: #FFFFFF;
|
||||
/*display: grid; COMMENTED OUT BY CHRIS FOR PUSH ON WIDGETS, not sure if need for welcome page or not, do not need anymore for widgets
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
grid-template-rows: 125px 250px 100px 100px;
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
box-sizing: border-box; */
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.elevated {
|
||||
filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.25));
|
||||
}
|
||||
|
||||
.page-display div {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.page-display > * {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.widget {
|
||||
border-radius: 10px;
|
||||
padding: 15px 20px;
|
||||
height: 100%;
|
||||
background-color: #F7F7F3;
|
||||
filter: drop-shadow(0 4px 2px rgba(0, 0, 0, 0.10));
|
||||
}
|
||||
|
||||
.btn-primary, .btn-primary:hover, .btn-primary:active, .btn-primary:visited {
|
||||
background-color: #5672C7 !important;
|
||||
border-color: #4766C2 !important;
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ export function checkLogin() {
|
|||
return false;
|
||||
}
|
||||
|
||||
function getSessionKey() {
|
||||
export function getSessionKey() {
|
||||
var cookies = document.cookie.split(';');
|
||||
for (let i = 0; i < cookies.length; i++) {
|
||||
const cookie = cookies[i].trim(); // Remove any leading or trailing whitespace
|
||||
|
|
@ -24,16 +24,15 @@ function getSessionKey() {
|
|||
|
||||
export async function getName() {
|
||||
try {
|
||||
const response = await fetch('http://127.0.0.1:3030/userinfo', {
|
||||
const response = await fetch('https://api.bb.gabefarrell.com/userinfo', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'x-session-key': getSessionKey(),
|
||||
},
|
||||
});
|
||||
const data = await response.json();
|
||||
const firstName = data.name;
|
||||
console.log(firstName); // Logs the first name correctly
|
||||
return firstName;
|
||||
const name = data.name;
|
||||
return name;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue