[#1] Stubbed out a functional context to begin prototyping

This commit is contained in:
Justin Walrath 2023-02-25 13:54:37 -05:00
parent 6133aca0ef
commit 28bd306b59
11 changed files with 128 additions and 23 deletions

1
package-lock.json generated
View File

@ -14,6 +14,7 @@
"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",

View File

@ -9,6 +9,7 @@
"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",

View File

@ -1,10 +1,26 @@
import { useEffect, useContext } from 'react';
import mockedData from '../mocked/MockedData'; import mockedData from '../mocked/MockedData';
import BudgetCardView from '../views/BudgetCardView'; import BudgetCardView from '../views/BudgetCardView';
import { BudgetContext, BudgetState } from '../data/context/BudgetContext';
import './App.css'; import './App.css';
function App() { const AppImpl = () => {
const context = useContext(BudgetContext);
useEffect(() => {
context.api.loadData(mockedData.budgetPeriods);
}, []);
return ( return (
<BudgetCardView budgetPeriods={mockedData.ranges} /> <BudgetCardView />
);
};
const App = (props) => {
return (
<BudgetState>
<AppImpl {...props}/>
</BudgetState>
); );
} }

View File

@ -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 (
<BudgetContext.Provider value={{...state, api}}>
{props.children}
</BudgetContext.Provider>
);
};
export { BudgetState, BudgetContext };

View File

@ -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;

View File

@ -1,26 +1,30 @@
const mockedData = { const mockedData = {
ranges: [{ budgetPeriods: [{
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
@ -39,21 +43,25 @@ 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
@ -72,21 +80,25 @@ 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
@ -105,21 +117,25 @@ 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
@ -138,21 +154,25 @@ 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

View File

@ -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'; import './BudgetCardView.scss';
const BudgetCardView = ({budgetPeriods}) => { const BudgetCardView = () => {
const activePeriod = budgetPeriods.find(p => !p.locked); const context = useContext(BudgetContext),
activePeriod = context?.budgetPeriods?.find?.(p => !p.locked);
return ( return (
<div className="budget-card-view-tray"> <div className="budget-card-view-tray">
{budgetPeriods.map(p => { {context?.budgetPeriods?.map?.(p => {
return ( return (
<BudgetPeriodCard key={p.id} periodData={p} isActive={p === activePeriod} /> <BudgetPeriodCard key={p.id} periodData={p} isActive={p === activePeriod} />
); );

View File

@ -1,19 +1,24 @@
import { useState, useEffect, useRef } from 'react'; import { useState, useEffect, useRef, useContext } 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(0), [projectedLeftover, setProjectedLeftover] = useState(0),
[bills, setBills] = useState(periodData.bills), paidBills = periodData.bills.filter((bill) => bill.paid),
[paidBills, setPaidBills] = useState([]), unpaidBills = periodData.bills.filter((bill) => !bill.paid),
[unpaidBills, setUnpaidBills] = useState([]),
[incomes, setIncomes] = useState(periodData.income), [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) {
@ -21,8 +26,6 @@ const BudgetPeriodCard = ({periodData, isActive}) => {
cardRef.current.parentElement.scrollBy(-40, 0); 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(() => { 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(() => {
@ -61,9 +64,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 } isBill /> <BudgetPeriodTransTable title={'Unpaid'} transList={ unpaidBills } actionHandler={moveTransItemAction} isBill />
<BudgetPeriodTransTable title={'Paid'} transList={ paidBills } isBill /> <BudgetPeriodTransTable title={'Paid'} transList={ paidBills } actionHandler={moveTransItemAction} isBill />
<BudgetPeriodTransTable title={'Income'} transList={ incomes } /> <BudgetPeriodTransTable title={'Income'} transList={ incomes } isPaid />
</div> </div>
</div> </div>
); );

View File

@ -3,7 +3,7 @@
border-radius: 5px; border-radius: 5px;
border: 1px solid #ccc; border: 1px solid #ccc;
margin: 1em; margin: 1em;
width: 300px; width: 400px;
.card-header { .card-header {
height: 3em; height: 3em;
background-color: #ccc; background-color: #ccc;

View File

@ -1,11 +1,24 @@
import { Grid } from 'gridjs-react'; import { Grid } from 'gridjs-react';
import { h } from 'gridjs';
import './BudgetPeriodTransTable.scss'; import './BudgetPeriodTransTable.scss';
import '../../node_modules/gridjs/dist/theme/mermaid.css' import '../../node_modules/gridjs/dist/theme/mermaid.css'
const BudgetPeriodTransTable = ({title, transList, isBill}) => { const BudgetPeriodTransTable = ({title, transList, isBill, actionHandler}) => {
const columns = ['Description', 'Amount'], 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; }, 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 ( return (
<div className='budget-period-trans-table'> <div className='budget-period-trans-table'>