diff --git a/package-lock.json b/package-lock.json
index 650d33a..19ff457 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,6 +14,7 @@
"bootstrap": "^5.1.3",
"gridjs": "^5.0.2",
"gridjs-react": "^5.0.2",
+ "lodash": "^4.17.21",
"react": "^17.0.2",
"react-bootstrap": "^2.1.2",
"react-dom": "^17.0.2",
diff --git a/package.json b/package.json
index 104941f..ca84a1f 100644
--- a/package.json
+++ b/package.json
@@ -9,6 +9,7 @@
"bootstrap": "^5.1.3",
"gridjs": "^5.0.2",
"gridjs-react": "^5.0.2",
+ "lodash": "^4.17.21",
"react": "^17.0.2",
"react-bootstrap": "^2.1.2",
"react-dom": "^17.0.2",
diff --git a/src/base/App.jsx b/src/base/App.jsx
index 2662d2e..5585db6 100644
--- a/src/base/App.jsx
+++ b/src/base/App.jsx
@@ -1,10 +1,26 @@
+import { useEffect, useContext } from 'react';
import mockedData from '../mocked/MockedData';
import BudgetCardView from '../views/BudgetCardView';
+import { BudgetContext, BudgetState } from '../data/context/BudgetContext';
import './App.css';
-function App() {
+const AppImpl = () => {
+ const context = useContext(BudgetContext);
+
+ useEffect(() => {
+ context.api.loadData(mockedData.budgetPeriods);
+ }, []);
+
return (
-
+
+ );
+};
+
+const App = (props) => {
+ return (
+
+
+
);
}
diff --git a/src/data/context/BudgetContext.jsx b/src/data/context/BudgetContext.jsx
new file mode 100644
index 0000000..26afc04
--- /dev/null
+++ b/src/data/context/BudgetContext.jsx
@@ -0,0 +1,27 @@
+import { useReducer, createContext } from 'react';
+import BudgetReducer from './BudgetReducer';
+
+const BudgetContext = createContext();
+
+const BudgetState = (props) => {
+ const initialState = {
+ budgetPeriods: []
+ },
+ [state, dispatch] = useReducer(BudgetReducer, initialState),
+ api = {
+ loadData: (data) => {
+ dispatch({ type: 'budget/populate', data });
+ },
+ updateBillPaid: (periodId, billId) => {
+ dispatch({ type: 'budget/period/bills/update', data: { periodId, billId }});
+ }
+ };
+
+ return (
+
+ {props.children}
+
+ );
+};
+
+export { BudgetState, BudgetContext };
\ No newline at end of file
diff --git a/src/data/context/BudgetReducer.js b/src/data/context/BudgetReducer.js
new file mode 100644
index 0000000..4f96a86
--- /dev/null
+++ b/src/data/context/BudgetReducer.js
@@ -0,0 +1,21 @@
+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
+ }))
+ }))
+ };
+ }
+ return {...state };
+};
+
+export default BudgetReducer;
\ No newline at end of file
diff --git a/src/index.jsx b/src/index.jsx
index b5a595f..a462cb7 100644
--- a/src/index.jsx
+++ b/src/index.jsx
@@ -8,4 +8,4 @@ ReactDOM.render(
,
document.getElementById('root')
-);
+);
\ No newline at end of file
diff --git a/src/mocked/MockedData.js b/src/mocked/MockedData.js
index 0cd43da..77010ba 100644
--- a/src/mocked/MockedData.js
+++ b/src/mocked/MockedData.js
@@ -1,26 +1,30 @@
const mockedData = {
- ranges: [{
+ budgetPeriods: [{
id: 1,
locked: true,
startDate: new Date('1-7-2022 0:0:0'),
endDate: new Date('1-20-2022 23:59:59'),
startingAmount: 0.00,
bills: [{
+ id: 1,
description: 'Amazon purchase',
amount: 46.00,
paid: false
},
{
+ id: 2,
description: 'Bill 1',
amount: 40.00,
paid: true
},
{
+ id: 3,
description: 'Bill 2',
amount: 50.00,
paid: true
},
{
+ id: 4,
description: 'Bill 3',
amount: 30.00,
paid: true
@@ -39,21 +43,25 @@ const mockedData = {
endDate: new Date('2-3-2022 23:59:59'),
startingAmount: 0.00,
bills: [{
+ id: 5,
description: 'Amazon purchase',
amount: 46.00,
paid: false
},
{
+ id: 6,
description: 'Bill 1',
amount: 40.00,
paid: true
},
{
+ id: 7,
description: 'Bill 2',
amount: 50.00,
paid: true
},
{
+ id: 8,
description: 'Bill 3',
amount: 30.00,
paid: true
@@ -72,21 +80,25 @@ const mockedData = {
endDate: new Date('2-17-2022 23:59:59'),
startingAmount: 0.00,
bills: [{
+ id: 9,
description: 'Amazon purchase',
amount: 46.00,
paid: false
},
{
+ id: 10,
description: 'Bill 1',
amount: 40.00,
paid: true
},
{
+ id: 11,
description: 'Bill 2',
amount: 50.00,
paid: true
},
{
+ id: 12,
description: 'Bill 3',
amount: 30.00,
paid: true
@@ -105,21 +117,25 @@ const mockedData = {
endDate: new Date('3-3-2022 23:59:59'),
startingAmount: 0.00,
bills: [{
+ id: 13,
description: 'Amazon purchase',
amount: 46.00,
paid: false
},
{
+ id: 14,
description: 'Bill 1',
amount: 40.00,
paid: true
},
{
+ id: 15,
description: 'Bill 2',
amount: 50.00,
paid: true
},
{
+ id: 16,
description: 'Bill 3',
amount: 30.00,
paid: true
@@ -138,21 +154,25 @@ const mockedData = {
endDate: new Date('3-17-2022 23:59:59'),
startingAmount: 0.00,
bills: [{
+ id: 17,
description: 'Amazon purchase',
amount: 46.00,
paid: false
},
{
+ id: 18,
description: 'Bill 1',
amount: 40.00,
paid: true
},
{
+ id: 19,
description: 'Bill 2',
amount: 50.00,
paid: true
},
{
+ id: 20,
description: 'Bill 3',
amount: 30.00,
paid: true
diff --git a/src/views/BudgetCardView.jsx b/src/views/BudgetCardView.jsx
index 80fcaec..ed68779 100644
--- a/src/views/BudgetCardView.jsx
+++ b/src/views/BudgetCardView.jsx
@@ -1,13 +1,16 @@
-import BudgetPeriodCard from "./BudgetPeriodCard";
+import _ from 'lodash';
+import BudgetPeriodCard from './BudgetPeriodCard';
+import { BudgetContext } from '../data/context/BudgetContext'
+import { useContext } from 'react';
import './BudgetCardView.scss';
-const BudgetCardView = ({budgetPeriods}) => {
- const activePeriod = budgetPeriods.find(p => !p.locked);
+const BudgetCardView = () => {
+ const context = useContext(BudgetContext),
+ activePeriod = context?.budgetPeriods?.find?.(p => !p.locked);
return (
- {budgetPeriods.map(p => {
-
+ {context?.budgetPeriods?.map?.(p => {
return (
);
diff --git a/src/views/BudgetPeriodCard.jsx b/src/views/BudgetPeriodCard.jsx
index f76ed0d..e18bb63 100644
--- a/src/views/BudgetPeriodCard.jsx
+++ b/src/views/BudgetPeriodCard.jsx
@@ -1,19 +1,24 @@
-import { useState, useEffect, useRef } from 'react';
+import { useState, useEffect, useRef, useContext } from 'react';
import BudgetPeriodTransTable from './BudgetPeriodTransTable';
import Lock from '../resources/Lock.svg';
import OpenLock from '../resources/OpenLock.svg';
import './BudgetPeriodCard.scss';
+import { BudgetContext } from '../data/context/BudgetContext';
const BudgetPeriodCard = ({periodData, isActive}) => {
const cardRef = useRef(),
+ context = useContext(BudgetContext),
[bankBalance, setBankBalance] = useState(0),
[projectedLeftover, setProjectedLeftover] = useState(0),
- [bills, setBills] = useState(periodData.bills),
- [paidBills, setPaidBills] = useState([]),
- [unpaidBills, setUnpaidBills] = useState([]),
+ paidBills = periodData.bills.filter((bill) => bill.paid),
+ unpaidBills = periodData.bills.filter((bill) => !bill.paid),
[incomes, setIncomes] = useState(periodData.income),
[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(() => {
if(isActive) {
@@ -21,8 +26,6 @@ const BudgetPeriodCard = ({periodData, isActive}) => {
cardRef.current.parentElement.scrollBy(-40, 0);
}
}, []);
- 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(() => { setUnpaidIncomes(incomes.filter((income) => !income.paid)); }, [incomes]);
useEffect(() => {
@@ -61,9 +64,9 @@ const BudgetPeriodCard = ({periodData, isActive}) => {
Projected Leftover
{ '$' + parseFloat(projectedLeftover).toFixed(2) }
-
-
-
+
+
+
);
diff --git a/src/views/BudgetPeriodCard.scss b/src/views/BudgetPeriodCard.scss
index 0720368..3bbd5dd 100644
--- a/src/views/BudgetPeriodCard.scss
+++ b/src/views/BudgetPeriodCard.scss
@@ -3,7 +3,7 @@
border-radius: 5px;
border: 1px solid #ccc;
margin: 1em;
- width: 300px;
+ width: 400px;
.card-header {
height: 3em;
background-color: #ccc;
diff --git a/src/views/BudgetPeriodTransTable.jsx b/src/views/BudgetPeriodTransTable.jsx
index 1552d32..c227473 100644
--- a/src/views/BudgetPeriodTransTable.jsx
+++ b/src/views/BudgetPeriodTransTable.jsx
@@ -1,11 +1,24 @@
import { Grid } from 'gridjs-react';
+import { h } from 'gridjs';
import './BudgetPeriodTransTable.scss';
import '../../node_modules/gridjs/dist/theme/mermaid.css'
-const BudgetPeriodTransTable = ({title, transList, isBill}) => {
- const columns = ['Description', 'Amount'],
+const BudgetPeriodTransTable = ({title, transList, isBill, actionHandler}) => {
+ const columns = [
+ 'Description',
+ 'Amount',
+ {
+ name: 'Actions',
+ formatter: (cell, row) => {
+ return h('button', {
+ className: 'py-2 mb-4 px-4 border rounded-md text-white bg-blue-600',
+ onClick: () => actionHandler(row)
+ }, row.cells[2].data.paid ? 'Unpay' : 'Pay');
+ }
+ }
+ ],
toCurrency = (amount) => { return '$' + (isBill === true ? '-' : '') + amount; },
- tableData = transList.map(t => { return [t.description, toCurrency(t.amount)]});
+ tableData = transList.map(t => { return [t.description, toCurrency(t.amount), t]});
return (