Compare commits
No commits in common. "master" and "6133aca0ef3967c56c88d7f3e181724a28f28dd1" have entirely different histories.
master
...
6133aca0ef
2
.gitignore
vendored
@ -1,7 +1,7 @@
|
|||||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
# dependencies
|
# dependencies
|
||||||
node_modules
|
/node_modules
|
||||||
/.pnp
|
/.pnp
|
||||||
.pnp.js
|
.pnp.js
|
||||||
|
|
||||||
|
|||||||
@ -1,57 +0,0 @@
|
|||||||
use budgetdb
|
|
||||||
|
|
||||||
db.createCollection(“users”)
|
|
||||||
db.createCollection(“accounts”)
|
|
||||||
db.createCollection("budgets")
|
|
||||||
db.createCollection("budgetPeriods")
|
|
||||||
|
|
||||||
users: [{
|
|
||||||
_id: <id>
|
|
||||||
username: "jules"
|
|
||||||
}]
|
|
||||||
|
|
||||||
accounts: [{
|
|
||||||
name: amazon,
|
|
||||||
user: <id>
|
|
||||||
}]
|
|
||||||
|
|
||||||
budgets: [{
|
|
||||||
name: "main"
|
|
||||||
}]
|
|
||||||
|
|
||||||
budgetPeriods: [{
|
|
||||||
user: ObjectId("63faac6823cd0682fe8c8ed6"),
|
|
||||||
startDate: new Date("2023-03-03"),
|
|
||||||
endDate: new Date("2023-03-10"),
|
|
||||||
leftover: 23.22,
|
|
||||||
cashflows: [
|
|
||||||
{
|
|
||||||
account: ObjectId("63fab01a23cd0682fe8c8ed7"),
|
|
||||||
dueDate: new Date("2023-03-04"),
|
|
||||||
postingDate: new Date("2023-03-04"),
|
|
||||||
amount: 22.23,
|
|
||||||
isIncome: false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
verified: true,
|
|
||||||
adhocPurchases: [
|
|
||||||
{
|
|
||||||
postingDate: new Date("2023-03-05"),
|
|
||||||
amount: 1.99,
|
|
||||||
description: "",
|
|
||||||
isIncome: false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
|
|
||||||
$push operator for adding to arrays
|
|
||||||
|
|
||||||
2023-03-07
|
|
||||||
|
|
||||||
{
|
|
||||||
user: ObjectId("63faac6823cd0682fe8c8ed6"),
|
|
||||||
start: { $lte: "2023-03-07T05:00:00.000Z" },
|
|
||||||
end: { $gt: "2023-03-07T05:00:00.000Z" }
|
|
||||||
}
|
|
||||||
|
|
||||||
http://localhost:3001/jules/period/in/2023-03-01..2023-04-01
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
import { useEffect, useContext } from 'react';
|
|
||||||
import BudgetCardView from '../views/BudgetCardView';
|
|
||||||
import { BudgetContext, BudgetState } from '../data/context/BudgetContext';
|
|
||||||
import './App.css';
|
|
||||||
|
|
||||||
const AppImpl = () => {
|
|
||||||
const context = useContext(BudgetContext);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
context.api.getCurrentPeriod();
|
|
||||||
context.api.getAccounts();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BudgetCardView />
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const App = (props) => {
|
|
||||||
return (
|
|
||||||
<BudgetState>
|
|
||||||
<AppImpl {...props}/>
|
|
||||||
</BudgetState>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default App;
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
import { useReducer, createContext } from 'react';
|
|
||||||
import axios from 'axios';
|
|
||||||
import BudgetReducer from './BudgetReducer';
|
|
||||||
|
|
||||||
const BudgetContext = createContext();
|
|
||||||
|
|
||||||
const BudgetState = (props) => {
|
|
||||||
const initialState = {
|
|
||||||
budgetPeriods: [],
|
|
||||||
accounts: []
|
|
||||||
},
|
|
||||||
[state, dispatch] = useReducer(BudgetReducer, initialState),
|
|
||||||
api = {
|
|
||||||
getCurrentPeriod: () => {
|
|
||||||
axios.get(`http://localhost:3001/jules/period/for/${(new Date()).toISOString()}`, {
|
|
||||||
headers: {
|
|
||||||
'Access-Control-Allow-Origin': '*',
|
|
||||||
'Access-Control-Allow-Methods': 'GET',
|
|
||||||
crossorigin: true
|
|
||||||
}
|
|
||||||
}).then(response => {
|
|
||||||
dispatch({ type: 'budget/populate', data: [response.data] });
|
|
||||||
});
|
|
||||||
},
|
|
||||||
getAccounts: () => {
|
|
||||||
axios.get('http://localhost:3001/jules/accounts', {
|
|
||||||
headers: {
|
|
||||||
'Access-Control-Allow-Origin': '*',
|
|
||||||
'Access-Control-Allow-Methods': 'GET',
|
|
||||||
crossorigin: true
|
|
||||||
}
|
|
||||||
}).then(response => {
|
|
||||||
dispatch({ type: 'accounts/populate', data: response.data });
|
|
||||||
});
|
|
||||||
},
|
|
||||||
updateBillPaid: (periodId, billId) => {
|
|
||||||
dispatch({ type: 'budget/period/bills/update', data: { periodId, billId }});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BudgetContext.Provider value={{...state, api}}>
|
|
||||||
{props.children}
|
|
||||||
</BudgetContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export { BudgetState, BudgetContext };
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
const BudgetReducer = (state, action) => {
|
|
||||||
if (action.type === 'budget/populate') {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
budgetPeriods: action.data
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (action.type === 'budget/period/bills/update') {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
budgetPeriods: state.budgetPeriods.map(bp => ({...bp,
|
|
||||||
bills: bp.bills.map(b => ({...b,
|
|
||||||
paid: (bp.id === action.data.periodId && b.id === action.data.billId) ? !b.paid : b.paid
|
|
||||||
}))
|
|
||||||
}))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (action.type === 'accounts/populate') {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
accounts: action.data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {...state };
|
|
||||||
};
|
|
||||||
|
|
||||||
export default BudgetReducer;
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
import _ from 'lodash';
|
|
||||||
import BudgetPeriodCard from './BudgetPeriodCard';
|
|
||||||
import { BudgetContext } from '../data/context/BudgetContext'
|
|
||||||
import { useContext } from 'react';
|
|
||||||
import './BudgetCardView.scss';
|
|
||||||
|
|
||||||
const BudgetCardView = () => {
|
|
||||||
const context = useContext(BudgetContext),
|
|
||||||
activePeriod = context?.budgetPeriods?.find?.(p => !p.locked);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="budget-card-view-tray">
|
|
||||||
{context?.budgetPeriods?.map?.(p => {
|
|
||||||
return (
|
|
||||||
<BudgetPeriodCard key={p.id} periodData={p} isActive={p === activePeriod} />
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default BudgetCardView;
|
|
||||||
@ -1,54 +0,0 @@
|
|||||||
import { useState, useEffect, useContext } from 'react';
|
|
||||||
import { Grid } from 'gridjs-react';
|
|
||||||
import { h } from 'gridjs';
|
|
||||||
import { BudgetContext } from '../data/context/BudgetContext';
|
|
||||||
import './BudgetPeriodTransTable.scss';
|
|
||||||
import '../../node_modules/gridjs/dist/theme/mermaid.css'
|
|
||||||
|
|
||||||
const BudgetPeriodTransTable = ({title, transList, isBill, actionHandler}) => {
|
|
||||||
const context = useContext(BudgetContext),
|
|
||||||
[tableData, setTableData] = useState([]),
|
|
||||||
columns = [
|
|
||||||
'Description',
|
|
||||||
'Amount',
|
|
||||||
{
|
|
||||||
name: 'Actions',
|
|
||||||
formatter: (cell, row) => {
|
|
||||||
let buttonText = 'Pay';
|
|
||||||
|
|
||||||
if (row.cells[2].data.postingDate != null) {
|
|
||||||
buttonText = row.cells[2].data.isIncome ? 'Paid' : 'Unpay';
|
|
||||||
}
|
|
||||||
|
|
||||||
return h('button', {
|
|
||||||
className: 'py-2 mb-4 px-4 border rounded-md text-white bg-blue-600',
|
|
||||||
onClick: () => actionHandler(row)
|
|
||||||
}, buttonText);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const toCurrency = (amount) => { return '$' + (isBill === true ? '-' : '') + amount; },
|
|
||||||
lookupAccountName = (accountId) => {
|
|
||||||
return context.accounts.find((a) => a._id === accountId)?.name ?? '';
|
|
||||||
};
|
|
||||||
|
|
||||||
setTableData(transList.map(t => {
|
|
||||||
return [lookupAccountName(t.account), toCurrency(t.amount), t]
|
|
||||||
}));
|
|
||||||
}, [context.accounts, transList, isBill]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='budget-period-trans-table'>
|
|
||||||
<div className="table-header">
|
|
||||||
<span className="table-title">{ title }</span>
|
|
||||||
</div>
|
|
||||||
<div className='table-container'>
|
|
||||||
<Grid data={tableData} columns={columns} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default BudgetPeriodTransTable;
|
|
||||||
69
client/package-lock.json → package-lock.json
generated
@ -11,11 +11,9 @@
|
|||||||
"@testing-library/jest-dom": "^5.16.1",
|
"@testing-library/jest-dom": "^5.16.1",
|
||||||
"@testing-library/react": "^12.1.2",
|
"@testing-library/react": "^12.1.2",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
"axios": "^1.3.4",
|
|
||||||
"bootstrap": "^5.1.3",
|
"bootstrap": "^5.1.3",
|
||||||
"gridjs": "^5.0.2",
|
"gridjs": "^5.0.2",
|
||||||
"gridjs-react": "^5.0.2",
|
"gridjs-react": "^5.0.2",
|
||||||
"lodash": "^4.17.21",
|
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-bootstrap": "^2.1.2",
|
"react-bootstrap": "^2.1.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
@ -4425,29 +4423,6 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/axios": {
|
|
||||||
"version": "1.3.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz",
|
|
||||||
"integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"follow-redirects": "^1.15.0",
|
|
||||||
"form-data": "^4.0.0",
|
|
||||||
"proxy-from-env": "^1.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/axios/node_modules/form-data": {
|
|
||||||
"version": "4.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
|
||||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
|
||||||
"dependencies": {
|
|
||||||
"asynckit": "^0.4.0",
|
|
||||||
"combined-stream": "^1.0.8",
|
|
||||||
"mime-types": "^2.1.12"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/axobject-query": {
|
"node_modules/axobject-query": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz",
|
||||||
@ -7490,9 +7465,9 @@
|
|||||||
"integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg=="
|
"integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg=="
|
||||||
},
|
},
|
||||||
"node_modules/follow-redirects": {
|
"node_modules/follow-redirects": {
|
||||||
"version": "1.15.2",
|
"version": "1.14.7",
|
||||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz",
|
||||||
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
|
"integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "individual",
|
"type": "individual",
|
||||||
@ -12962,11 +12937,6 @@
|
|||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/proxy-from-env": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
|
||||||
},
|
|
||||||
"node_modules/psl": {
|
"node_modules/psl": {
|
||||||
"version": "1.8.0",
|
"version": "1.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
|
||||||
@ -19312,28 +19282,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.4.0.tgz",
|
||||||
"integrity": "sha512-btWy2rze3NnxSSxb7LtNhPYYFrRoFBfjiGzmSc/5Hu47wApO2KNXjP/w7Nv2Uz/Fyr/pfEiwOkcXhDxu0jz5FA=="
|
"integrity": "sha512-btWy2rze3NnxSSxb7LtNhPYYFrRoFBfjiGzmSc/5Hu47wApO2KNXjP/w7Nv2Uz/Fyr/pfEiwOkcXhDxu0jz5FA=="
|
||||||
},
|
},
|
||||||
"axios": {
|
|
||||||
"version": "1.3.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz",
|
|
||||||
"integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==",
|
|
||||||
"requires": {
|
|
||||||
"follow-redirects": "^1.15.0",
|
|
||||||
"form-data": "^4.0.0",
|
|
||||||
"proxy-from-env": "^1.1.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"form-data": {
|
|
||||||
"version": "4.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
|
||||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
|
||||||
"requires": {
|
|
||||||
"asynckit": "^0.4.0",
|
|
||||||
"combined-stream": "^1.0.8",
|
|
||||||
"mime-types": "^2.1.12"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"axobject-query": {
|
"axobject-query": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz",
|
||||||
@ -21597,9 +21545,9 @@
|
|||||||
"integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg=="
|
"integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg=="
|
||||||
},
|
},
|
||||||
"follow-redirects": {
|
"follow-redirects": {
|
||||||
"version": "1.15.2",
|
"version": "1.14.7",
|
||||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz",
|
||||||
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA=="
|
"integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ=="
|
||||||
},
|
},
|
||||||
"fork-ts-checker-webpack-plugin": {
|
"fork-ts-checker-webpack-plugin": {
|
||||||
"version": "6.5.0",
|
"version": "6.5.0",
|
||||||
@ -25405,11 +25353,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"proxy-from-env": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
|
||||||
},
|
|
||||||
"psl": {
|
"psl": {
|
||||||
"version": "1.8.0",
|
"version": "1.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
|
||||||
@ -6,11 +6,9 @@
|
|||||||
"@testing-library/jest-dom": "^5.16.1",
|
"@testing-library/jest-dom": "^5.16.1",
|
||||||
"@testing-library/react": "^12.1.2",
|
"@testing-library/react": "^12.1.2",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
"axios": "^1.3.4",
|
|
||||||
"bootstrap": "^5.1.3",
|
"bootstrap": "^5.1.3",
|
||||||
"gridjs": "^5.0.2",
|
"gridjs": "^5.0.2",
|
||||||
"gridjs-react": "^5.0.2",
|
"gridjs-react": "^5.0.2",
|
||||||
"lodash": "^4.17.21",
|
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-bootstrap": "^2.1.2",
|
"react-bootstrap": "^2.1.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
1866
server/package-lock.json
generated
@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "budget-demo-server",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "",
|
|
||||||
"main": "index.js",
|
|
||||||
"scripts": {
|
|
||||||
"start": "node src/server.js",
|
|
||||||
"dev": "nodemon src/server.js",
|
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
|
||||||
},
|
|
||||||
"author": "",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"express": "^4.18.2",
|
|
||||||
"mongodb": "^5.1.0",
|
|
||||||
"nodemon": "^2.0.20"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
const accountManager = require('../managers/AccountManager'),
|
|
||||||
accountStore = require('../stores/AccountStore');
|
|
||||||
|
|
||||||
exports.load = (app) => {
|
|
||||||
app.get('/accounts', (req, res) => {
|
|
||||||
accountStore.getAccounts().then(data => res.json({ accounts: data }));
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get('/:username/accounts', (req, res) => {
|
|
||||||
const { username } = req.params;
|
|
||||||
accountManager.getUserAccounts(username).then(data => res.json(data));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
const budgetPeriodManager = require('../managers/BudgetPeriodManager');
|
|
||||||
|
|
||||||
exports.load = (app) => {
|
|
||||||
// app.get('/:username/period/current', (req, res) => {
|
|
||||||
// const { username } = req.params;
|
|
||||||
// budgetPeriodManager.getBudgetPeriodForUserByDateInPeriod(username, new Date()).then(data => res.json(data));
|
|
||||||
// });
|
|
||||||
|
|
||||||
app.get('/:username/period/for/:date', (req, res) => {
|
|
||||||
const { username, date } = req.params;
|
|
||||||
res.header("Access-Control-Allow-Origin", "*");
|
|
||||||
budgetPeriodManager.getBudgetPeriodForUserByDateInPeriod(username, new Date(date)).then(data => res.json(data));
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get('/:username/period/in/:start..:end', (req, res) => {
|
|
||||||
const { username, start, end } = req.params;
|
|
||||||
budgetPeriodManager.getBudgetPeriodsForUserWithinDateRange(username, new Date(start), new Date(end)).then(data => res.json(data));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
const userStore = require('../stores/UserStore');
|
|
||||||
|
|
||||||
exports.load = (app) => {
|
|
||||||
app.get('/users', (req, res) => {
|
|
||||||
userStore.getUsers().then(data => res.json({ users: data }));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
const userStore = require('../stores/UserStore'),
|
|
||||||
accountStore = require('../stores/AccountStore');
|
|
||||||
|
|
||||||
exports.getUserAccounts = (username) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
userStore.findUser(username).then((user) => {
|
|
||||||
accountStore.getAccountsForUser(user._id).then(accounts => {
|
|
||||||
resolve(accounts);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
const userStore = require('../stores/UserStore'),
|
|
||||||
budgetPeriodStore = require('../stores/BudgetPeriodStore');
|
|
||||||
|
|
||||||
exports.getBudgetPeriodsForUserWithinDateRange = (username, startDate, endDate) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
userStore.findUser(username).then((user) => {
|
|
||||||
budgetPeriodStore.getBudgetPeriodsForUserWithinDateRange(user._id, startDate, endDate).then(budgetPeriods => {
|
|
||||||
resolve(budgetPeriods);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.getBudgetPeriodForUserByDateInPeriod = (username, date) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
userStore.findUser(username).then((user) => {
|
|
||||||
budgetPeriodStore.getBudgetPeriodForUserByDateInPeriod(user._id, date).then(accounts => {
|
|
||||||
resolve(accounts);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
const express = require('express'),
|
|
||||||
port = process.env.PORT || 3001,
|
|
||||||
app = express();
|
|
||||||
|
|
||||||
[
|
|
||||||
require('./controllers/UserController'),
|
|
||||||
require('./controllers/AccountController'),
|
|
||||||
require('./controllers/BudgetPeriodController')
|
|
||||||
].forEach(c => c.load(app));
|
|
||||||
|
|
||||||
app.listen(port, () => {
|
|
||||||
console.log(`===> Server started: http://localhost:${port}/`);
|
|
||||||
});
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
const mongo = require('./Mongo');
|
|
||||||
|
|
||||||
exports.getAccounts = () => {
|
|
||||||
return mongo.listAll('accounts');
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.getAccountsForUser = (userId) => {
|
|
||||||
return mongo.find('accounts', { user: userId });
|
|
||||||
};
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
const mongo = require('./Mongo');
|
|
||||||
|
|
||||||
exports.getBudgetPeriods = () => {
|
|
||||||
return mongo.listAll('budgetPeriods');
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.getBudgetPeriodsForUserWithinDateRange = (userId, startDate, endDate) => {
|
|
||||||
return mongo.find('budgetPeriods', {
|
|
||||||
user: userId,
|
|
||||||
startDate: { $gte: startDate },
|
|
||||||
endDate: { $lt: endDate }
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.getBudgetPeriodForUserByDateInPeriod = (userId, date) => {
|
|
||||||
return mongo.findOne('budgetPeriods', {
|
|
||||||
user: userId,
|
|
||||||
startDate: { $lte: date },
|
|
||||||
endDate: { $gt: date }
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// db.budgetPeriods.insert({ cashflows: [ { account: ObjectId("63fab01a23cd0682fe8c8ed7"), dueDate: new Date("2023-03-04"), postingDate: new Date("2023-03-04"), amount: 22.23, isIncome: false }], verified: true, adhocPurchases: [ { postingDate: new Date("2023-03-05"), amount: 1.99, description: "", isIncome: false }] })
|
|
||||||
@ -1,44 +0,0 @@
|
|||||||
const { MongoClient } = require('mongodb'),
|
|
||||||
mongoUri = process.env.MONGOURI || 'mongodb://localhost:27017',
|
|
||||||
mongoDb = process.env.MONGODB || 'budgetdb';
|
|
||||||
|
|
||||||
const find = (collectionName, query) => {
|
|
||||||
return new Promise(async(resolve, reject) => {
|
|
||||||
const client = new MongoClient(mongoUri);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const db = client.db(mongoDb),
|
|
||||||
collection = db.collection(collectionName);
|
|
||||||
|
|
||||||
resolve(await collection.find(query).toArray());
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
reject(err);
|
|
||||||
} finally {
|
|
||||||
client.close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
exports.find = find;
|
|
||||||
|
|
||||||
exports.findOne = (collectionName, query) => {
|
|
||||||
return new Promise(async(resolve, reject) => {
|
|
||||||
const client = new MongoClient(mongoUri);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const db = client.db(mongoDb),
|
|
||||||
collection = db.collection(collectionName);
|
|
||||||
|
|
||||||
resolve(await collection.findOne(query));
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
reject(err);
|
|
||||||
} finally {
|
|
||||||
client.close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.listAll = (collectionName) => {
|
|
||||||
return find(collectionName);
|
|
||||||
};
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
const mongo = require('./Mongo');
|
|
||||||
|
|
||||||
exports.getUsers = () => {
|
|
||||||
return mongo.listAll('users');
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.findUser = (username) => {
|
|
||||||
return mongo.findOne('users', { username: username });
|
|
||||||
}
|
|
||||||
11
src/base/App.jsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import mockedData from '../mocked/MockedData';
|
||||||
|
import BudgetCardView from '../views/BudgetCardView';
|
||||||
|
import './App.css';
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return (
|
||||||
|
<BudgetCardView budgetPeriods={mockedData.ranges} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
@ -1,30 +1,26 @@
|
|||||||
const mockedData = {
|
const mockedData = {
|
||||||
budgetPeriods: [{
|
ranges: [{
|
||||||
id: 1,
|
id: 1,
|
||||||
locked: true,
|
locked: true,
|
||||||
startDate: new Date('1-7-2022 0:0:0'),
|
startDate: new Date('1-7-2022 0:0:0'),
|
||||||
endDate: new Date('1-20-2022 23:59:59'),
|
endDate: new Date('1-20-2022 23:59:59'),
|
||||||
startingAmount: 0.00,
|
startingAmount: 0.00,
|
||||||
bills: [{
|
bills: [{
|
||||||
id: 1,
|
|
||||||
description: 'Amazon purchase',
|
description: 'Amazon purchase',
|
||||||
amount: 46.00,
|
amount: 46.00,
|
||||||
paid: false
|
paid: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
|
||||||
description: 'Bill 1',
|
description: 'Bill 1',
|
||||||
amount: 40.00,
|
amount: 40.00,
|
||||||
paid: true
|
paid: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 3,
|
|
||||||
description: 'Bill 2',
|
description: 'Bill 2',
|
||||||
amount: 50.00,
|
amount: 50.00,
|
||||||
paid: true
|
paid: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 4,
|
|
||||||
description: 'Bill 3',
|
description: 'Bill 3',
|
||||||
amount: 30.00,
|
amount: 30.00,
|
||||||
paid: true
|
paid: true
|
||||||
@ -43,25 +39,21 @@ const mockedData = {
|
|||||||
endDate: new Date('2-3-2022 23:59:59'),
|
endDate: new Date('2-3-2022 23:59:59'),
|
||||||
startingAmount: 0.00,
|
startingAmount: 0.00,
|
||||||
bills: [{
|
bills: [{
|
||||||
id: 5,
|
|
||||||
description: 'Amazon purchase',
|
description: 'Amazon purchase',
|
||||||
amount: 46.00,
|
amount: 46.00,
|
||||||
paid: false
|
paid: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 6,
|
|
||||||
description: 'Bill 1',
|
description: 'Bill 1',
|
||||||
amount: 40.00,
|
amount: 40.00,
|
||||||
paid: true
|
paid: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 7,
|
|
||||||
description: 'Bill 2',
|
description: 'Bill 2',
|
||||||
amount: 50.00,
|
amount: 50.00,
|
||||||
paid: true
|
paid: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 8,
|
|
||||||
description: 'Bill 3',
|
description: 'Bill 3',
|
||||||
amount: 30.00,
|
amount: 30.00,
|
||||||
paid: true
|
paid: true
|
||||||
@ -80,25 +72,21 @@ const mockedData = {
|
|||||||
endDate: new Date('2-17-2022 23:59:59'),
|
endDate: new Date('2-17-2022 23:59:59'),
|
||||||
startingAmount: 0.00,
|
startingAmount: 0.00,
|
||||||
bills: [{
|
bills: [{
|
||||||
id: 9,
|
|
||||||
description: 'Amazon purchase',
|
description: 'Amazon purchase',
|
||||||
amount: 46.00,
|
amount: 46.00,
|
||||||
paid: false
|
paid: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 10,
|
|
||||||
description: 'Bill 1',
|
description: 'Bill 1',
|
||||||
amount: 40.00,
|
amount: 40.00,
|
||||||
paid: true
|
paid: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 11,
|
|
||||||
description: 'Bill 2',
|
description: 'Bill 2',
|
||||||
amount: 50.00,
|
amount: 50.00,
|
||||||
paid: true
|
paid: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 12,
|
|
||||||
description: 'Bill 3',
|
description: 'Bill 3',
|
||||||
amount: 30.00,
|
amount: 30.00,
|
||||||
paid: true
|
paid: true
|
||||||
@ -117,25 +105,21 @@ const mockedData = {
|
|||||||
endDate: new Date('3-3-2022 23:59:59'),
|
endDate: new Date('3-3-2022 23:59:59'),
|
||||||
startingAmount: 0.00,
|
startingAmount: 0.00,
|
||||||
bills: [{
|
bills: [{
|
||||||
id: 13,
|
|
||||||
description: 'Amazon purchase',
|
description: 'Amazon purchase',
|
||||||
amount: 46.00,
|
amount: 46.00,
|
||||||
paid: false
|
paid: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 14,
|
|
||||||
description: 'Bill 1',
|
description: 'Bill 1',
|
||||||
amount: 40.00,
|
amount: 40.00,
|
||||||
paid: true
|
paid: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 15,
|
|
||||||
description: 'Bill 2',
|
description: 'Bill 2',
|
||||||
amount: 50.00,
|
amount: 50.00,
|
||||||
paid: true
|
paid: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 16,
|
|
||||||
description: 'Bill 3',
|
description: 'Bill 3',
|
||||||
amount: 30.00,
|
amount: 30.00,
|
||||||
paid: true
|
paid: true
|
||||||
@ -154,25 +138,21 @@ const mockedData = {
|
|||||||
endDate: new Date('3-17-2022 23:59:59'),
|
endDate: new Date('3-17-2022 23:59:59'),
|
||||||
startingAmount: 0.00,
|
startingAmount: 0.00,
|
||||||
bills: [{
|
bills: [{
|
||||||
id: 17,
|
|
||||||
description: 'Amazon purchase',
|
description: 'Amazon purchase',
|
||||||
amount: 46.00,
|
amount: 46.00,
|
||||||
paid: false
|
paid: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 18,
|
|
||||||
description: 'Bill 1',
|
description: 'Bill 1',
|
||||||
amount: 40.00,
|
amount: 40.00,
|
||||||
paid: true
|
paid: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 19,
|
|
||||||
description: 'Bill 2',
|
description: 'Bill 2',
|
||||||
amount: 50.00,
|
amount: 50.00,
|
||||||
paid: true
|
paid: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 20,
|
|
||||||
description: 'Bill 3',
|
description: 'Bill 3',
|
||||||
amount: 30.00,
|
amount: 30.00,
|
||||||
paid: true
|
paid: true
|
||||||
|
Before Width: | Height: | Size: 615 B After Width: | Height: | Size: 615 B |
|
Before Width: | Height: | Size: 710 B After Width: | Height: | Size: 710 B |
19
src/views/BudgetCardView.jsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import BudgetPeriodCard from "./BudgetPeriodCard";
|
||||||
|
import './BudgetCardView.scss';
|
||||||
|
|
||||||
|
const BudgetCardView = ({budgetPeriods}) => {
|
||||||
|
const activePeriod = budgetPeriods.find(p => !p.locked);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="budget-card-view-tray">
|
||||||
|
{budgetPeriods.map(p => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BudgetPeriodCard key={p.id} periodData={p} isActive={p === activePeriod} />
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BudgetCardView;
|
||||||
@ -1,31 +1,28 @@
|
|||||||
import { useState, useEffect, useRef, useContext } from 'react';
|
import { useState, useEffect, useRef } from 'react';
|
||||||
import BudgetPeriodTransTable from './BudgetPeriodTransTable';
|
import BudgetPeriodTransTable from './BudgetPeriodTransTable';
|
||||||
import Lock from '../resources/Lock.svg';
|
import Lock from '../resources/Lock.svg';
|
||||||
import OpenLock from '../resources/OpenLock.svg';
|
import OpenLock from '../resources/OpenLock.svg';
|
||||||
import './BudgetPeriodCard.scss';
|
import './BudgetPeriodCard.scss';
|
||||||
import { BudgetContext } from '../data/context/BudgetContext';
|
|
||||||
|
|
||||||
const BudgetPeriodCard = ({periodData, isActive}) => {
|
const BudgetPeriodCard = ({periodData, isActive}) => {
|
||||||
const cardRef = useRef(),
|
const cardRef = useRef(),
|
||||||
context = useContext(BudgetContext),
|
|
||||||
[bankBalance, setBankBalance] = useState(0),
|
[bankBalance, setBankBalance] = useState(0),
|
||||||
[projectedLeftover, setProjectedLeftover] = useState(periodData),
|
[projectedLeftover, setProjectedLeftover] = useState(0),
|
||||||
paidBills = periodData.cashflows.filter((f) => !f.isIncome && f.postingDate != null),
|
[bills, setBills] = useState(periodData.bills),
|
||||||
unpaidBills = periodData.cashflows.filter((f) => !f.isIncome && f.postingDate == null),
|
[paidBills, setPaidBills] = useState([]),
|
||||||
[incomes, setIncomes] = useState(periodData.cashflows.filter((f) => f.isIncome)),
|
[unpaidBills, setUnpaidBills] = useState([]),
|
||||||
|
[incomes, setIncomes] = useState(periodData.income),
|
||||||
[paidIncomes, setPaidIncomes] = useState([]),
|
[paidIncomes, setPaidIncomes] = useState([]),
|
||||||
[unpaidIncomes, setUnpaidIncomes] = useState([]),
|
[unpaidIncomes, setUnpaidIncomes] = useState([]);
|
||||||
moveTransItemAction = (row) => {
|
|
||||||
const data = row.cells[2].data
|
|
||||||
context.api.updateBillPaid(periodData.id, data.id);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(isActive) {
|
if(isActive) {
|
||||||
cardRef.current.scrollIntoView({inline: "start"});
|
cardRef.current.scrollIntoView({inline: "start"});
|
||||||
cardRef.current.parentElement.scrollBy(-40, 0);
|
cardRef.current.parentElement.scrollBy(-40, 0);
|
||||||
}
|
}
|
||||||
}, [isActive]);
|
}, []);
|
||||||
|
useEffect(() => { setPaidBills(bills.filter((bill) => bill.paid)); }, [bills]);
|
||||||
|
useEffect(() => { setUnpaidBills(bills.filter((bill) => !bill.paid)); }, [bills]);
|
||||||
useEffect(() => { setPaidIncomes(incomes.filter((income) => income.paid)); }, [incomes]);
|
useEffect(() => { setPaidIncomes(incomes.filter((income) => income.paid)); }, [incomes]);
|
||||||
useEffect(() => { setUnpaidIncomes(incomes.filter((income) => !income.paid)); }, [incomes]);
|
useEffect(() => { setUnpaidIncomes(incomes.filter((income) => !income.paid)); }, [incomes]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -34,7 +31,7 @@ const BudgetPeriodCard = ({periodData, isActive}) => {
|
|||||||
paidBills.reduce((prev, bill) => { return prev + bill.amount; }, 0.0) +
|
paidBills.reduce((prev, bill) => { return prev + bill.amount; }, 0.0) +
|
||||||
paidIncomes.reduce((prev, income) => { return prev + income.amount; }, 0.0)
|
paidIncomes.reduce((prev, income) => { return prev + income.amount; }, 0.0)
|
||||||
);
|
);
|
||||||
}, [paidBills, paidIncomes, periodData.startingAmount]);
|
}, [paidBills, paidIncomes]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setProjectedLeftover(
|
setProjectedLeftover(
|
||||||
bankBalance -
|
bankBalance -
|
||||||
@ -52,9 +49,9 @@ const BudgetPeriodCard = ({periodData, isActive}) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className='card-body'>
|
<div className='card-body'>
|
||||||
<div className='dates'>
|
<div className='dates'>
|
||||||
<span className='dates-start'>{(new Date(periodData.startDate)).toLocaleDateString()}</span>
|
<span className='dates-start'>{periodData.startDate.toDateString().split(' ').slice(1).join(' ')}</span>
|
||||||
<span> - </span>
|
<span> - </span>
|
||||||
<span className='dates-end'>{(new Date(periodData.endDate)).toLocaleDateString()}</span>
|
<span className='dates-end'>{ periodData.endDate.toDateString().split(' ').slice(1).join(' ')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className='bank-current'>
|
<div className='bank-current'>
|
||||||
<span className='bank-current-label'>Current Balance</span>
|
<span className='bank-current-label'>Current Balance</span>
|
||||||
@ -64,9 +61,9 @@ const BudgetPeriodCard = ({periodData, isActive}) => {
|
|||||||
<span className='bank-projected-label'>Projected Leftover</span>
|
<span className='bank-projected-label'>Projected Leftover</span>
|
||||||
<span className='bank-projected-value'>{ '$' + parseFloat(projectedLeftover).toFixed(2) }</span>
|
<span className='bank-projected-value'>{ '$' + parseFloat(projectedLeftover).toFixed(2) }</span>
|
||||||
</div>
|
</div>
|
||||||
<BudgetPeriodTransTable title={'Unpaid'} transList={ unpaidBills } actionHandler={moveTransItemAction} isBill />
|
<BudgetPeriodTransTable title={'Unpaid'} transList={ unpaidBills } isBill />
|
||||||
<BudgetPeriodTransTable title={'Paid'} transList={ paidBills } actionHandler={moveTransItemAction} isBill />
|
<BudgetPeriodTransTable title={'Paid'} transList={ paidBills } isBill />
|
||||||
<BudgetPeriodTransTable title={'Income'} transList={ incomes } isPaid />
|
<BudgetPeriodTransTable title={'Income'} transList={ incomes } />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -3,7 +3,7 @@
|
|||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
margin: 1em;
|
margin: 1em;
|
||||||
width: 400px;
|
width: 300px;
|
||||||
.card-header {
|
.card-header {
|
||||||
height: 3em;
|
height: 3em;
|
||||||
background-color: #ccc;
|
background-color: #ccc;
|
||||||
22
src/views/BudgetPeriodTransTable.jsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { Grid } from 'gridjs-react';
|
||||||
|
import './BudgetPeriodTransTable.scss';
|
||||||
|
import '../../node_modules/gridjs/dist/theme/mermaid.css'
|
||||||
|
|
||||||
|
const BudgetPeriodTransTable = ({title, transList, isBill}) => {
|
||||||
|
const columns = ['Description', 'Amount'],
|
||||||
|
toCurrency = (amount) => { return '$' + (isBill === true ? '-' : '') + amount; },
|
||||||
|
tableData = transList.map(t => { return [t.description, toCurrency(t.amount)]});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='budget-period-trans-table'>
|
||||||
|
<div className="table-header">
|
||||||
|
<span className="table-title">{ title }</span>
|
||||||
|
</div>
|
||||||
|
<div className='table-container'>
|
||||||
|
<Grid data={tableData} columns={columns} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BudgetPeriodTransTable;
|
||||||