mirror of
https://github.com/gabehf/BudgetBuddy.git
synced 2026-03-07 21:48:14 -08:00
Functionality of Widgets
This commit is contained in:
parent
377ac40bd6
commit
5448ba665a
9 changed files with 281 additions and 100 deletions
70
package-lock.json
generated
70
package-lock.json
generated
|
|
@ -21,6 +21,7 @@
|
|||
"axios": "^1.3.5",
|
||||
"bootstrap": "^5.2.3",
|
||||
"chart.js": "^4.2.1",
|
||||
"ng2-charts": "^4.1.1",
|
||||
"react": "^18.0.0",
|
||||
"react-bootstrap": "^2.7.4",
|
||||
"react-bootstrap-validation": "^0.1.11",
|
||||
|
|
@ -4436,41 +4437,6 @@
|
|||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@testing-library/dom": {
|
||||
"version": "9.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.2.0.tgz",
|
||||
"integrity": "sha512-xTEnpUKiV/bMyEsE5bT4oYA0x0Z/colMtxzUY8bKyPXBNLn/e0V4ZjBZkEhms0xE4pv9QsPfSRu9AWS4y5wGvA==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.10.4",
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@types/aria-query": "^5.0.1",
|
||||
"aria-query": "^5.0.0",
|
||||
"chalk": "^4.1.0",
|
||||
"dom-accessibility-api": "^0.5.9",
|
||||
"lz-string": "^1.5.0",
|
||||
"pretty-format": "^27.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@testing-library/dom/node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@testing-library/jest-dom": {
|
||||
"version": "5.16.5",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz",
|
||||
|
|
@ -12915,6 +12881,11 @@
|
|||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"node_modules/lodash-es": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
|
||||
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
|
||||
},
|
||||
"node_modules/lodash.debounce": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||
|
|
@ -13260,6 +13231,22 @@
|
|||
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
|
||||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
|
||||
},
|
||||
"node_modules/ng2-charts": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ng2-charts/-/ng2-charts-4.1.1.tgz",
|
||||
"integrity": "sha512-iHwXDbmX86lfeH8VRcsaW2tJATsuAZo4kvvC/Yk2l35zOHjevja1qBvO6BAibiDazi9r9aS6ZRJOqWPsz1pP2w==",
|
||||
"dependencies": {
|
||||
"lodash-es": "^4.17.15",
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/cdk": ">=14.0.0",
|
||||
"@angular/common": ">=14.0.0",
|
||||
"@angular/core": ">=14.0.0",
|
||||
"chart.js": "^3.4.0 || ^4.0.0",
|
||||
"rxjs": "^6.5.3 || ^7.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/no-case": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz",
|
||||
|
|
@ -17328,19 +17315,6 @@
|
|||
"is-typedarray": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "4.9.5",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
|
||||
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/unbox-primitive": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
"axios": "^1.3.5",
|
||||
"bootstrap": "^5.2.3",
|
||||
"chart.js": "^4.2.1",
|
||||
"ng2-charts": "^4.1.1",
|
||||
"react": "^18.0.0",
|
||||
"react-bootstrap": "^2.7.4",
|
||||
"react-bootstrap-validation": "^0.1.11",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
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 { calculateValue, getSessionKey } from '../utils/utils.js'
|
||||
|
|
|
|||
|
|
@ -2,21 +2,103 @@ import React, { useContext, useState, useEffect } from 'react';
|
|||
import ExpenseItem from './ExpenseItem';
|
||||
import { AppContext } from '../context/AppContext';
|
||||
|
||||
|
||||
|
||||
const ExpenseList = () => {
|
||||
const { expenses } = useContext(AppContext);
|
||||
const [whole, setWhole] = useState('');
|
||||
const [decimal, setDecimal] = useState('');
|
||||
const [transaction, setTransactions] = useState('');
|
||||
const [category, setCategories] = useState('');
|
||||
|
||||
const [filteredExpenses, setfilteredExpenses] = useState(expenses || []);
|
||||
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
|
||||
if (cookie.startsWith('session=')) {
|
||||
return cookie.substring('session='.length, cookie.length); // Extract the value of the cookie
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function getTransactionsBalance() {
|
||||
try {
|
||||
const response = await fetch('https://api.bb.gabefarrell.com/w/transactions/recent', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'x-session-key': getSessionKey(),
|
||||
},
|
||||
});
|
||||
const data = await response.json();
|
||||
let whole = data.transactions.map((item)=>item.amount.whole);
|
||||
let decimal = data.transactions.map((item)=>item.amount.decimal);
|
||||
let balance = [];
|
||||
for (let i = 0; i < whole.length; i++) {
|
||||
let expense_balance = whole[i] + '.' + decimal[i];
|
||||
balance.push(expense_balance);
|
||||
}
|
||||
|
||||
//console.log(whole + decimal + "<- total recent transacations");
|
||||
//console.log(whole, decimal, balance);
|
||||
//const transactions_balance = whole + '.' + decimal;
|
||||
//console.log(whole, decimal, category);
|
||||
return balance;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function getTransactionsCategory() {
|
||||
try {
|
||||
const response = await fetch('https://api.bb.gabefarrell.com/w/transactions/recent', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'x-session-key': getSessionKey(),
|
||||
},
|
||||
});
|
||||
const data = await response.json();
|
||||
const category = data.transactions.map((item)=>item.category);
|
||||
|
||||
//console.log(category);
|
||||
return category;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function buildTable(data1, data2){
|
||||
var table = document.getElementById('myTable')
|
||||
|
||||
for (var i = 0; i < data1.length; i++){
|
||||
var row = `<tr>
|
||||
<td>${data1[i]}</td>
|
||||
<td>${data2[i]}</td>
|
||||
</tr>`
|
||||
table.innerHTML += row
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
buildTable(category, transaction);
|
||||
|
||||
async function fetchTransactions() {
|
||||
const categories = await getTransactionsCategory();
|
||||
const transaction_balance = await getTransactionsBalance();
|
||||
|
||||
|
||||
|
||||
setCategories(categories);
|
||||
setTransactions(transaction_balance);
|
||||
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setfilteredExpenses(expenses);
|
||||
}, [expenses]);
|
||||
fetchTransactions(); //b36efa01-7824-4f61-a274-63131b58d8fe
|
||||
}, [])
|
||||
|
||||
const handleChange = (event) => {
|
||||
const searchResults = expenses.filter((filteredExpense) =>
|
||||
filteredExpense.name.toLowerCase().includes(event.target.value)
|
||||
);
|
||||
setfilteredExpenses(searchResults);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='widget'>
|
||||
|
|
@ -26,16 +108,14 @@ const ExpenseList = () => {
|
|||
placeholder='Type to search...'
|
||||
onChange={handleChange}
|
||||
/> */}
|
||||
<h4>Expenses</h4>
|
||||
<ul className='list-group mt-3 mb-3'>
|
||||
{filteredExpenses.map((expense) => (
|
||||
<ExpenseItem
|
||||
id={expense.id}
|
||||
name={expense.name}
|
||||
cost={expense.cost}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<h4>Recent Transactions</h4>
|
||||
{/* <h4>{category}</h4>
|
||||
<h4>{transaction}</h4> */}
|
||||
<table><tbody id="myTable">
|
||||
|
||||
</tbody></table>
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,23 +1,124 @@
|
|||
import React, { useContext } from 'react';
|
||||
import { AppContext } from '../context/AppContext';
|
||||
import logo from './widget_logos/budget_logo.png';
|
||||
import './css/Remaining.css'
|
||||
import React, {useState, useEffect} from 'react';
|
||||
import './css/ViewBudget.css'
|
||||
import logo from './widget_logos/current_balance_logo.png';
|
||||
|
||||
const RemainingBudget = () => {
|
||||
const { expenses, budget } = useContext(AppContext);
|
||||
export default function Remaining() {
|
||||
const [budget, setBudget] = useState('');
|
||||
const [whole, setWhole] = useState('');
|
||||
const [decimal, setDecimal] = useState('');
|
||||
|
||||
const totalExpenses = expenses.reduce((total, item) => {
|
||||
return (total += item.cost);
|
||||
}, 0);
|
||||
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
|
||||
if (cookie.startsWith('session=')) {
|
||||
return cookie.substring('session='.length, cookie.length); // Extract the value of the cookie
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const alertType = totalExpenses > budget ? 'alert-danger' : 'alert-success';
|
||||
async function getBudget() {
|
||||
try {
|
||||
const response = await fetch('https://api.bb.gabefarrell.com/w/budget', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'x-session-key': getSessionKey(),
|
||||
},
|
||||
});
|
||||
const data = await response.json();
|
||||
const whole = data.budget.whole;
|
||||
const decimal = data.budget.decimal;
|
||||
const budget_balance = whole + '.' + decimal;
|
||||
console.log(budget_balance);
|
||||
return budget_balance;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function getMonthlyExpenses() {
|
||||
try {
|
||||
const response = await fetch('https://api.bb.gabefarrell.com/w/expenses/month', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'x-session-key': getSessionKey(),
|
||||
},
|
||||
});
|
||||
const data = await response.json();
|
||||
const whole = data.whole;
|
||||
const decimal = data.decimal;
|
||||
const total_expenses = whole + '.' + decimal;
|
||||
console.log(total_expenses + "<- Total Expenses");
|
||||
return total_expenses;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
async function fetchBudget() {
|
||||
const name = await getBudget();
|
||||
const expenses = await getMonthlyExpenses();
|
||||
const budget_remaining = (name - expenses).toFixed(2);
|
||||
setBudget(budget_remaining);
|
||||
}
|
||||
useEffect(() => {
|
||||
|
||||
fetchBudget();
|
||||
}, [])
|
||||
|
||||
function handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
const budget = whole.split('.')
|
||||
if (budget.length < 2)
|
||||
budget.push(0);
|
||||
else if (budget[1].length < 1)
|
||||
budget.push(0);
|
||||
|
||||
var details = {
|
||||
// 'currency' : currency,
|
||||
'whole' : budget[0],
|
||||
'decimal' : budget[1]
|
||||
};
|
||||
var formBody = []
|
||||
for (var property in details) {
|
||||
var encodedKey = encodeURIComponent(property);
|
||||
var encodedValue = encodeURIComponent(details[property]);
|
||||
formBody.push(encodedKey + "=" + encodedValue);
|
||||
}
|
||||
formBody = formBody.join("&");
|
||||
|
||||
fetch('https://api.bb.gabefarrell.com/w/budget', {
|
||||
method: 'POST',
|
||||
body: formBody,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
|
||||
'x-session-key': getSessionKey()
|
||||
}
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
console.log(data)
|
||||
if (data.status == 200)
|
||||
fetchBudget();
|
||||
});
|
||||
}
|
||||
return (
|
||||
<div className={`alert p-4 ${alertType} widget`}>
|
||||
<img src={logo}></img>
|
||||
<span>Budget Remaining: ${budget - totalExpenses}</span>
|
||||
</div>
|
||||
<>
|
||||
{/* <img src={logo}></img>
|
||||
<span>Current Balance: ${data}</span> */}
|
||||
{/* <button onClick={apiGet()}> TEST</button> */}
|
||||
<h1>{budget}</h1>
|
||||
<form id="form" onSubmit={handleSubmit}>
|
||||
<input
|
||||
type = "text"
|
||||
className="form-control"
|
||||
placeholder="Enter Budget"
|
||||
value={whole}
|
||||
onChange={(event) => setWhole(event.target.value)}
|
||||
required />
|
||||
<button type="submit">Submit</button>
|
||||
</form>
|
||||
</>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
export default RemainingBudget;
|
||||
};
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
import React, {useState, useEffect} from 'react';
|
||||
import React, {useState, useEffect, useRef} from 'react';
|
||||
import './css/ViewBudget.css'
|
||||
import logo from './widget_logos/current_balance_logo.png';
|
||||
|
||||
export default function FetchAPI() {
|
||||
const [whole, setWhole] = useState('');
|
||||
const [decimal, setDecimal] = useState('')
|
||||
const [decimal, setDecimal] = useState('');
|
||||
const [balance, setBalance] = useState('');
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
function getSessionKey() {
|
||||
var cookies = document.cookie.split(';');
|
||||
|
|
@ -41,19 +42,36 @@ export default function FetchAPI() {
|
|||
setBalance(name);
|
||||
}
|
||||
useEffect(() => {
|
||||
|
||||
fetchBalance();
|
||||
}, [])
|
||||
|
||||
|
||||
const handleBlur = (event) => {
|
||||
|
||||
const handleBlur = (event) => {
|
||||
if (event.target.validity.patternMismatch) {
|
||||
setError(true);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
function handleSubmit(event) {
|
||||
// const form = document.getElementById('form');
|
||||
event.preventDefault();
|
||||
// const payload = new FormData(form);
|
||||
const balance = whole.split('.')
|
||||
if (balance.length < 2)
|
||||
balance.push(0);
|
||||
else if (balance[1].length < 1)
|
||||
balance.push(0);
|
||||
console.log(whole);
|
||||
const balance = whole.split('.')
|
||||
console.log(balance)
|
||||
if (balance.length < 2) {
|
||||
balance.push('0');
|
||||
}
|
||||
else if (balance[1].length > 2) {
|
||||
console.log("TEST SUCCESS")
|
||||
}
|
||||
else if (balance[1].length < 1) {
|
||||
balance.push('0');
|
||||
}
|
||||
|
||||
|
||||
// console.log([...payload])
|
||||
// const formData = new FormData();
|
||||
// formData.append('whole', whole);
|
||||
|
|
@ -86,6 +104,7 @@ export default function FetchAPI() {
|
|||
fetchBalance();
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* <img src={logo}></img>
|
||||
|
|
@ -98,8 +117,13 @@ export default function FetchAPI() {
|
|||
className="form-control"
|
||||
placeholder="Enter Balance"
|
||||
value={whole}
|
||||
maxLength="10"
|
||||
pattern="^[+]?[0-9]{1,3}(?:,?[0-9]{3})*(?:\.[0-9]{2})?"
|
||||
title="Please enter the balance with two decimal places for cents. Please enter a balance 999999999.99"
|
||||
onChange={(event) => setWhole(event.target.value)}
|
||||
required />
|
||||
|
||||
|
||||
<button type="submit">Submit</button>
|
||||
</form>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -49,4 +49,4 @@ export const AppProvider = (props) => {
|
|||
{props.children}
|
||||
</AppContext.Provider>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
|
@ -9,13 +9,13 @@ import Main from './Main.js'
|
|||
|
||||
function App() {
|
||||
return (
|
||||
<React.StrictMode>
|
||||
// <React.StrictMode> COMMENTED OUT BY CHRIS TO FIX MULTIPLE TABLE DISPLAYED IN RECENT TRANSACTION
|
||||
<div className="app">
|
||||
<ThemeProvider theme={theme}>
|
||||
<Main/>
|
||||
</ThemeProvider>
|
||||
</div>
|
||||
</React.StrictMode>
|
||||
// </React.StrictMode> praying I didn't break the project tbh
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { useState } from 'react'
|
|||
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||
|
||||
import { AppProvider } from '../context/AppContext';
|
||||
import FetchAPI from '../components/Budget';
|
||||
import FetchAPI from '../components/budget';
|
||||
import ExpenseTotal from '../components/ExpenseTotal';
|
||||
import ExpenseList from '../components/ExpenseList';
|
||||
import AddExpenseForm from '../components/AddExpenseForm';
|
||||
|
|
@ -24,11 +24,11 @@ export default function Dashboard() {
|
|||
<div className='col-sm'>
|
||||
<FetchAPI />
|
||||
</div>
|
||||
<div className='col-sm'>
|
||||
<div className='col-sm widget d-flex align-items-center justify-content-between'>
|
||||
<RemainingBudget />
|
||||
</div>
|
||||
<div className='col-sm'>
|
||||
<ExpenseTotal />
|
||||
{/* <ExpenseTotal /> */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue