mirror of
https://github.com/gabehf/BudgetBuddy.git
synced 2026-03-07 21:48:14 -08:00
Expenses by Category Chart & Add transaction widget
This commit is contained in:
parent
78e2b99ef2
commit
1b5e9a2cef
6 changed files with 134 additions and 29 deletions
|
|
@ -7,11 +7,10 @@ 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([]);
|
||||
const [isExpense, setIsExpense] = useState(true);
|
||||
const [transactionType, setTransactionType] = useState('expenses');
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
|
|
@ -26,18 +25,23 @@ const AddExpenseForm = (props) => {
|
|||
if (data.status != 200) {
|
||||
console.log(data.error);
|
||||
} else {
|
||||
isExpense
|
||||
? setCategoryList(Object.keys(data.expenses))
|
||||
: setCategoryList(Object.keys(data.income));
|
||||
getCategoryList();
|
||||
}
|
||||
})
|
||||
} catch(error) {
|
||||
console.error(error);
|
||||
}
|
||||
}, [])
|
||||
}, [transactionType])
|
||||
|
||||
const toggleTransactionType = () => {
|
||||
setIsExpense(!isExpense);
|
||||
if (transactionType == "expenses") {
|
||||
setTransactionType("income");
|
||||
} else {
|
||||
setTransactionType("expenses");
|
||||
}
|
||||
}
|
||||
|
||||
const getCategoryList = () => {
|
||||
fetch('https://api.bb.gabefarrell.com/w/budget', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
|
|
@ -49,9 +53,19 @@ const AddExpenseForm = (props) => {
|
|||
if (data.status != 200) {
|
||||
console.log(data.error);
|
||||
} else {
|
||||
isExpense
|
||||
? setCategoryList(Object.keys(data.expenses))
|
||||
: setCategoryList(Object.keys(data.income));
|
||||
let categories = [];
|
||||
|
||||
Object.values(data.expenses).map((transactions) => {
|
||||
transactions.forEach(transaction => {
|
||||
if (transaction.type == transactionType && !categories.includes(transaction.category)) {
|
||||
categories.push(transaction.category)
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (categories.length == 0) categories = ["Uncategorized"];
|
||||
setCategoryList(categories);
|
||||
setCategory(categories[0])
|
||||
}
|
||||
})
|
||||
};
|
||||
|
|
@ -64,7 +78,6 @@ const AddExpenseForm = (props) => {
|
|||
let currency = "USD"
|
||||
let whole = 0;
|
||||
let decimal = 0;
|
||||
let type = isExpense ? 'expenses' : 'income';
|
||||
|
||||
|
||||
if (cost.includes(".")) {
|
||||
|
|
@ -77,10 +90,12 @@ const AddExpenseForm = (props) => {
|
|||
formData.append('currency', currency);
|
||||
formData.append('whole', whole);
|
||||
formData.append('decimal', decimal);
|
||||
formData.append('type', type)
|
||||
formData.append('type', transactionType)
|
||||
|
||||
console.log(transactionType);
|
||||
|
||||
try {
|
||||
fetch(`https://api.bb.gabefarrell.com/w/transactions?whole=${whole}&decimal=${decimal}¤cy=${currency}&category=${category}&type=${type}`, {
|
||||
fetch(`https://api.bb.gabefarrell.com/w/transactions?whole=${whole}&decimal=${decimal}¤cy=${currency}&category=${category}&type=${transactionType}`, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: {
|
||||
|
|
@ -99,7 +114,6 @@ const AddExpenseForm = (props) => {
|
|||
console.error(error);
|
||||
}
|
||||
|
||||
setName('');
|
||||
setCost('');
|
||||
};
|
||||
|
||||
|
|
@ -148,13 +162,13 @@ const AddExpenseForm = (props) => {
|
|||
<div className='row mt-3'>
|
||||
<div className='col-sm'>
|
||||
<button type='submit' onClick={onSubmit} className='btn btn-primary' style={{marginRight:"12px"}}>
|
||||
Add {isExpense ? 'Expense' : 'Income'}
|
||||
Add {transactionType.substring(0, 1).toUpperCase() + transactionType.substring(1)}
|
||||
</button>
|
||||
<button className='btn btn-primary' onClick={handleAddCategory} style={{marginRight:"12px"}}>
|
||||
Add New Category
|
||||
</button>
|
||||
<button onClick={toggleTransactionType}>
|
||||
{isExpense ? 'EXPENSE' : 'INCOME'}
|
||||
{transactionType.toUpperCase()}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
93
src/components/CategorizedExpenses.js
Normal file
93
src/components/CategorizedExpenses.js
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
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'
|
||||
import { Minimize } from "@material-ui/icons";
|
||||
|
||||
ChartJS.register(ArcElement, Tooltip, Legend);
|
||||
|
||||
export default function CategorizedExpenses() {
|
||||
const [chartData, setChartData] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
async function getChartData() {
|
||||
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 {
|
||||
let categories = [];
|
||||
let values = [];
|
||||
|
||||
Object.values(data.expenses).map((transactions) => {
|
||||
let cost = 0;
|
||||
categories.push(transactions[0].category)
|
||||
transactions.forEach(transaction => {
|
||||
cost += calculateValue(transaction.amount);
|
||||
});
|
||||
|
||||
values.push(cost);
|
||||
});
|
||||
if (categories.length == 0) categories = [`No expenses`];
|
||||
|
||||
const chartData = {
|
||||
labels: categories,
|
||||
datasets: [{
|
||||
data: values,
|
||||
label: "Category",
|
||||
backgroundColor: [
|
||||
'#FFC857',
|
||||
'#ED8146',
|
||||
'#DB3A34',
|
||||
'#5672C7',
|
||||
],
|
||||
borderColor: [
|
||||
"white"
|
||||
],
|
||||
borderWidth: 2,
|
||||
}],
|
||||
}
|
||||
|
||||
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>
|
||||
)
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ import { Minimize } from "@material-ui/icons";
|
|||
|
||||
ChartJS.register(ArcElement, Tooltip, Legend);
|
||||
|
||||
export default function CategorizedBudget() {
|
||||
export default function CategorizedIncome() {
|
||||
const [chartData, setChartData] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -26,12 +26,11 @@ export default function CategorizedBudget() {
|
|||
console.log(data.error);
|
||||
} else {
|
||||
|
||||
console.log(typeof data.expenses_by_category);
|
||||
const chartData = {
|
||||
labels: Object.keys(data.expenses).length > 0 ? Object.keys(data.expenses) : [ "no expenses"],
|
||||
labels: Object.keys(data.income).length > 0 ? Object.keys(data.income) : [ "no income"],
|
||||
datasets: [
|
||||
{
|
||||
data: Object.values(data.expenses_by_category).map(category => {
|
||||
data: Object.values(data.income_by_category).map(category => {
|
||||
return calculateValue(category);
|
||||
}),
|
||||
backgroundColor: [
|
||||
|
|
@ -66,7 +65,7 @@ export default function CategorizedBudget() {
|
|||
|
||||
return (
|
||||
<div className="widget">
|
||||
<h4>Expenses by Category</h4>
|
||||
<h4>Income by Category</h4>
|
||||
<Doughnut className="w-100 h-auto"
|
||||
data={chartData}
|
||||
options={{
|
||||
0
src/components/Income
Normal file
0
src/components/Income
Normal file
|
|
@ -8,7 +8,8 @@ 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 CategorizedExpenses from '../components/CategorizedExpenses';
|
||||
import CategorizedIncome from '../components/CategorizedIncome';
|
||||
import { Typography } from '@mui/material';
|
||||
|
||||
export default function Dashboard() {
|
||||
|
|
@ -40,12 +41,10 @@ export default function Dashboard() {
|
|||
|
||||
<div className='row mt-3'>
|
||||
<div className='col-12 col-lg-6'>
|
||||
<CategorizedBudget/>
|
||||
<CategorizedExpenses type={"expenses"}/>
|
||||
</div>
|
||||
<div className='col-12 col-lg-6'>
|
||||
<div className='widget'>
|
||||
|
||||
</div>
|
||||
<CategorizedExpenses type={"income"}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -43,6 +43,6 @@ export function handleLogout() {
|
|||
window.location.href='/welcome';
|
||||
}
|
||||
|
||||
export function calculateValue(category) {
|
||||
return category.whole + category.decimal * 0.01;
|
||||
export function calculateValue(amount) {
|
||||
return amount.whole + amount.decimal * 0.01;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue