diff --git a/client/src/Components/TrendsComparison.jsx b/client/src/Components/TrendsComparison.jsx index 154c1d5..5259e75 100644 --- a/client/src/Components/TrendsComparison.jsx +++ b/client/src/Components/TrendsComparison.jsx @@ -1,18 +1,81 @@ import React from 'react'; import { Bar } from 'react-chartjs-2'; - +import PropTypes from 'prop-types'; // test data for display -const data = { - labels: ['Red', 'Green', 'Yellow'], - datasets: [ - { - data: [300, 50, 100], - backgroundColor: ['#FF6384', '#36A2EB', '#FFCE56'], - hoverBackgroundColor: ['#FF6384', '#36A2EB', '#FFCE56'] - } - ] -}; -const TrendsComparison = () => ; +const TrendsComparison = props => { + const [m1Data, setm1Data] = React.useState({}); + const [m2Data, setm2Data] = React.useState({}); + const [categories, setCategories] = React.useState({}); + const [graphData, setGraphData] = React.useState({}); + + React.useEffect(() => { + const cat1 = {}; + const cat2 = {}; + const totalCats = {}; + props.data.accountData.accounts.forEach(account => { + if (account.transactions[props.y1]) { + if (account.transactions[props.y1][props.m1]) { + account.transactions[props.y1][props.m1].forEach(transaction => { + if (cat1[transaction.category]) { + cat1[transaction.category] += Number(transaction.amount); + } else { + cat1[transaction.category] = Number(transaction.amount); + totalCats[transaction.category] = 1; + } + }); + } + } + if (account.transactions[props.y2]) { + if (account.transactions[props.y2][props.m2]) { + account.transactions[props.y2][props.m2].forEach(transaction => { + if (cat2[transaction.category]) { + cat2[transaction.category] += Number(transaction.amount); + } else { + cat2[transaction.category] = Number(transaction.amount); + totalCats[transaction.category] = 1; + } + }); + } + } + }); + setm1Data(cat1); + setm2Data(cat2); + setCategories(totalCats); + }, [props.data.accountData.accounts, props.m1, props.m2, props.y1, props.y2]); + + React.useEffect(() => { + setGraphData({ + labels: Object.keys(categories).sort(), + datasets: [ + { + label: 'Month 1', + data: Object.keys(categories) + .sort() + .map(cat => m1Data[cat] || 0), + backgroundColor: ['#FF6384', '#36A2EB', '#FFCE56'], + hoverBackgroundColor: ['#FF6384', '#36A2EB', '#FFCE56'] + }, + { + label: 'Month 2', + data: Object.keys(categories) + .sort() + .map(cat => m2Data[cat] || 0), + backgroundColor: ['#FF6384', '#36A2EB', '#FFCE56'], + hoverBackgroundColor: ['#FF6384', '#36A2EB', '#FFCE56'] + } + ] + }); + }, [m1Data, m2Data, categories]); + return ; +}; export default TrendsComparison; + +TrendsComparison.propTypes = { + data: PropTypes.object, + m1: PropTypes.string, + m2: PropTypes.string, + y1: PropTypes.string, + y2: PropTypes.string +}; diff --git a/client/src/Components/TrendsComparisonSelector.jsx b/client/src/Components/TrendsComparisonSelector.jsx new file mode 100644 index 0000000..5774dbb --- /dev/null +++ b/client/src/Components/TrendsComparisonSelector.jsx @@ -0,0 +1,168 @@ +import React from 'react'; +import { makeStyles } from '@material-ui/core/styles'; +import InputLabel from '@material-ui/core/InputLabel'; +import FormControl from '@material-ui/core/FormControl'; +import Select from '@material-ui/core/Select'; +import PropTypes from 'prop-types'; + +const useStyles = makeStyles(theme => ({ + root: { + display: 'flex', + flexWrap: 'wrap' + }, + formControl: { + margin: theme.spacing(1), + minWidth: 120 + }, + selectEmpty: { + marginTop: theme.spacing(2) + } +})); + +const date = new Date(); +const year = date.getFullYear(); +const month = date.getMonth(); +const month2 = month === 0 ? 11 : month - 1; + +export default function ComparisonSelector(props) { + const classes = useStyles(); + const [state, setState] = React.useState({ + month1: month.toString(), + year1: year.toString(), + month2: month2.toString(), + year2: year.toString(), + years: [] + }); + + const handleChange = name => event => { + setState({ + ...state, + [name]: event.target.value + }); + }; + + const updateYear = () => { + let result = {}; + for (let i = 0; i < props.data.accountData.accounts.length; i++) { + for (let obj in props.data.accountData.accounts[i].transactions) { + result[obj] = 1; + } + } + return Object.keys(result); + }; + + React.useEffect(() => { + setState({ + ...state, + years: updateYear() + }); + }, []); + + React.useEffect(() => props.setM1(state.month1), [state.month1]); + React.useEffect(() => props.setM2(state.month2), [state.month2]); + React.useEffect(() => props.setY1(state.year1), [state.year1]); + React.useEffect(() => props.setY2(state.year2), [state.year2]); + + return ( +
+
+ + Month 1 + + + + Year 1 + + +
+
+ + Month 2 + + + + Year 2 + + +
+
+ ); +} + +ComparisonSelector.propTypes = { + data: PropTypes.object, + setM1: PropTypes.func, + setM2: PropTypes.func, + setY1: PropTypes.func, + setY2: PropTypes.func +}; diff --git a/client/src/Components/TrendsHabits.jsx b/client/src/Components/TrendsHabits.jsx index ed7bc14..f8fefa0 100644 --- a/client/src/Components/TrendsHabits.jsx +++ b/client/src/Components/TrendsHabits.jsx @@ -1,19 +1,153 @@ import React from 'react'; import { Line } from 'react-chartjs-2'; +import PropTypes from 'prop-types'; // test data for display -const data = { - labels: ['Red', 'Green', 'Yellow'], - datasets: [ - { - data: [300, 50, 100], - backgroundColor: ['#FF6384', '#36A2EB', '#FFCE56'], - hoverBackgroundColor: ['#FF6384', '#36A2EB', '#FFCE56'], - lineTension: 0 +const numToMonths = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December' +]; +const date = new Date(); +const currentYear = date.getFullYear(); + +const TrendsHabits = props => { + const [category, setCategory] = React.useState([{ name: '' }]); + const [graphData, setGraph] = React.useState({}); + const [months, setMonths] = React.useState([]); + const [years, setYears] = React.useState([]); + + React.useEffect(() => { + setCategory( + props.data.accountData.budgetCategories.filter(category => { + return category.name == props.category; + }) + ); + }, [props.category, props.data.accountData.budgetCategories]); + + React.useEffect(() => { + if (props.view === 'month' && category.length > 0) { + const possibleMonths = {}; + props.data.accountData.accounts.map(account => { + Object.keys(account.transactions[currentYear]).map(month => { + if (possibleMonths[month]) { + possibleMonths[month] += account.transactions[currentYear][ + month + ].reduce((x, y) => { + if (y.category === category[0].name) { + return x + Number(y.amount); + } + return x + 0; + + }, 0); + } else { + possibleMonths[month] = account.transactions[currentYear][ + month + ].reduce((x, y) => { + if (y.category === category[0].name) { + return x + Number(y.amount); + } + return x + 0; + + }, 0); + } + }); + setMonths(possibleMonths); + }); + } else if (category.length > 0) { + const possibleYears = {}; + props.data.accountData.accounts.map(account => { + Object.keys(account.transactions).map(year => { + const possibleMonths = {}; + Object.keys(account.transactions[year]).map(month => { + if (possibleMonths[month]) { + possibleMonths[month] += account.transactions[year][month].reduce( + (x, y) => { + if (y.category === category[0].name) { + return x + Number(y.amount); + } + return x + 0; + + }, + 0 + ); + } else { + possibleMonths[month] = account.transactions[year][month].reduce( + (x, y) => { + if (y.category === category[0].name) { + return x + Number(y.amount); + } + return x + 0; + + }, + 0 + ); + } + }); + let total = 0; + Object.keys(possibleMonths).forEach( + month => (total += possibleMonths[month]) + ); + if (possibleYears[year]) { + possibleYears[year] += total; + } else { + possibleYears[year] = total; + } + }); + }); + setYears(possibleYears); } - ] -}; + }, [category, props.data.accountData.accounts, props.view]); -const TrendsHabits = () => ; + React.useEffect(() => { + if (category) { + if (category[0]) { + if (category[0].name) { + if (props.view === 'month') { + const numbers = Object.keys(months); + setGraph({ + labels: numbers.map(num => numToMonths[num]), + datasets: [ + { + label: category[0].name, + data: numbers.map( + num => Math.floor(months[num] * 1000) / 1000 + ) + } + ] + }); + } else { + const numbers = Object.keys(years); + setGraph({ + labels: numbers, + datasets: [ + { + label: category[0].name, + data: numbers.map(num => Math.floor(years[num] * 1000) / 1000) + } + ] + }); + } + } + } + } + }, [months, years, props.view, category]); + return ; +}; export default TrendsHabits; + +TrendsHabits.propTypes = { + data: PropTypes.object, + view: PropTypes.string, + category: PropTypes.string +}; diff --git a/client/src/Components/TrendsHabitsSelector.jsx b/client/src/Components/TrendsHabitsSelector.jsx new file mode 100644 index 0000000..1dc31e2 --- /dev/null +++ b/client/src/Components/TrendsHabitsSelector.jsx @@ -0,0 +1,103 @@ +import React from 'react'; +import { makeStyles } from '@material-ui/core/styles'; +import InputLabel from '@material-ui/core/InputLabel'; +import FormControl from '@material-ui/core/FormControl'; +import Select from '@material-ui/core/Select'; +import Radio from '@material-ui/core/Radio'; +import RadioGroup from '@material-ui/core/RadioGroup'; +import FormControlLabel from '@material-ui/core/FormControlLabel'; +import FormLabel from '@material-ui/core/FormLabel'; +import PropTypes from 'prop-types'; + +const useStyles = makeStyles(theme => ({ + root: { + display: 'flex', + flexWrap: 'wrap' + }, + formControl: { + margin: theme.spacing(1), + minWidth: 120 + }, + selectEmpty: { + marginTop: theme.spacing(2) + }, + radioformControl: { + margin: theme.spacing(3) + }, + group: { + margin: theme.spacing(1, 0) + } +})); + +export default function HabitsSelector(props) { + const classes = useStyles(); + const [state, setState] = React.useState({ + category: '', + categories: [] + }); + const [value, setValue] = React.useState('month'); + + const handleChange1 = name => event => { + setState({ + ...state, + [name]: event.target.value + }); + }; + + const handleChange2 = event => { + setValue(event.target.value); + }; + + React.useEffect(() => { + props.setView(value); + props.setCategory(state.category); + }, [value, state.category]); + + return ( +
+ + Category + + + + Time Period + + } + label="Month to Month" + /> + } + label="Year to Year" + /> + + +
+ ); +} + +HabitsSelector.propTypes = { + data: PropTypes.object, + setView: PropTypes.func, + setCategory: PropTypes.func +}; diff --git a/client/src/Components/TrendsOverview.jsx b/client/src/Components/TrendsOverview.jsx index 77c92d5..de86de6 100644 --- a/client/src/Components/TrendsOverview.jsx +++ b/client/src/Components/TrendsOverview.jsx @@ -1,18 +1,55 @@ import React from 'react'; import { Doughnut } from 'react-chartjs-2'; +import PropTypes from 'prop-types'; -// test data for display -const data = { - labels: ['Red', 'Green', 'Yellow'], - datasets: [ - { - data: [300, 50, 100], - backgroundColor: ['#FF6384', '#36A2EB', '#FFCE56'], - hoverBackgroundColor: ['#FF6384', '#36A2EB', '#FFCE56'] - } - ] -}; +const TrendsOverview = props => { + const [labels, setLabels] = React.useState([]); + const [data, setData] = React.useState([]); + const [transactions, setTransactions] = React.useState([]); + + React.useEffect(() => { + const transactions = {}; + props.data.accountData.accounts.map(account => { + if (account.transactions[props.year]) { + if (account.transactions[props.year][props.month]) { + account.transactions[props.year][props.month].forEach(transaction => { + if (transactions[transaction.category]) { + transactions[transaction.category] += Number(transaction.amount); + } else { + transactions[transaction.category] = Number(transaction.amount); + } + }); + } + } + }); + setTransactions(transactions); + }, [props.year, props.month, props.data.accountData.accounts]); -const TrendsOverview = () => ; + React.useEffect(() => { + setLabels(Object.keys(transactions)); + }, [transactions]); + + React.useEffect(() => { + setData(labels.map(label => Math.round(transactions[label] * 1000) / 1000)); + }, [labels, transactions]); + + const userData = { + labels, + datasets: [ + { + data, + backgroundColor: ['#FF6384', '#36A2EB', '#FFCE56'], + hoverBackgroundColor: ['#FF6384', '#36A2EB', '#FFCE56'] + } + ] + }; + return ; +}; export default TrendsOverview; + +TrendsOverview.propTypes = { + data: PropTypes.object, + month: PropTypes.string, + year: PropTypes.string +}; diff --git a/client/src/Components/TrendsOverviewSelector.jsx b/client/src/Components/TrendsOverviewSelector.jsx new file mode 100644 index 0000000..2ee6b1c --- /dev/null +++ b/client/src/Components/TrendsOverviewSelector.jsx @@ -0,0 +1,114 @@ +import React from 'react'; +import { makeStyles } from '@material-ui/core/styles'; +import InputLabel from '@material-ui/core/InputLabel'; +import FormControl from '@material-ui/core/FormControl'; +import Select from '@material-ui/core/Select'; +import PropTypes from 'prop-types'; + +const useStyles = makeStyles(theme => ({ + root: { + display: 'flex', + flexWrap: 'wrap' + }, + formControl: { + margin: theme.spacing(1), + minWidth: 120 + }, + selectEmpty: { + marginTop: theme.spacing(2) + } +})); +const date = new Date(); +const currentMonth = date.getMonth().toString(); +const currentYear = date.getFullYear().toString(); +export default function OverviewSelector(props) { + const classes = useStyles(); + const [state, setState] = React.useState({ + month: currentMonth, + year: currentYear, + years: [] + }); + + const handleChange = name => event => { + setState({ + ...state, + [name]: event.target.value + }); + }; + + const updateYear = () => { + let result = {}; + for (let i = 0; i < props.data.accountData.accounts.length; i++) { + for (let obj in props.data.accountData.accounts[i].transactions) { + result[obj] = 1; + } + } + return Object.keys(result); + }; + + React.useEffect(() => { + setState({ + ...state, + years: updateYear() + }); + }, []); + + React.useEffect(() => { + props.setMonth(state.month); + props.setYear(state.year); + }, [state.month, state.year]); + + return ( +
+ + Month + + + + Year + + +
+ ); +} + +OverviewSelector.propTypes = { + data: PropTypes.object, + setMonth: PropTypes.func, + setYear: PropTypes.func +}; diff --git a/client/src/Components/TrendsPage.jsx b/client/src/Components/TrendsPage.jsx index c394eed..abc253e 100644 --- a/client/src/Components/TrendsPage.jsx +++ b/client/src/Components/TrendsPage.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useCallback } from 'react'; import { makeStyles } from '@material-ui/core/styles'; import PropTypes from 'prop-types'; import Paper from '@material-ui/core/Paper'; @@ -9,6 +9,9 @@ import Box from '@material-ui/core/Box'; import TrendsOverview from './TrendsOverview.jsx'; import TrendsHabits from './TrendsHabits.jsx'; import TrendsComparison from './TrendsComparison.jsx'; +import OverviewSelector from './TrendsOverviewSelector.jsx'; +import HabitsSelector from './TrendsHabitsSelector.jsx'; +import ComparisonSelector from './TrendsComparisonSelector.jsx'; import Loading from './Loading.jsx'; const useStyles = makeStyles({ @@ -50,6 +53,14 @@ function a11yProps(index) { export default function TrendsPage(props) { const classes = useStyles(); const [value, setValue] = React.useState(0); + const [overviewYear, setOverviewYear] = React.useState(''); + const [overviewMonth, setOverviewMonth] = React.useState(''); + const [habitView, setHabitView] = React.useState('month'); + const [habitCategory, setHabitCategory] = React.useState(''); + const [compareMonth1, setCompareMonth1] = React.useState(''); + const [compareMonth2, setCompareMonth2] = React.useState(''); + const [compareYear1, setCompareYear1] = React.useState(''); + const [compareYear2, setCompareYear2] = React.useState(''); function handleChange(event, newValue) { setValue(newValue); @@ -79,13 +90,40 @@ export default function TrendsPage(props) { - + + - + + - + + );