Skip to content

Commit

Permalink
Add types to home-api (#202)
Browse files Browse the repository at this point in the history
  • Loading branch information
shaldengeki authored Jun 29, 2024
1 parent 03763fb commit ab34984
Show file tree
Hide file tree
Showing 12 changed files with 3,433 additions and 159 deletions.
2 changes: 2 additions & 0 deletions home_api/frontend/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ TYPE_DEPS = [
"//:node_modules/@types/node",
"//:node_modules/@types/react",
"//:node_modules/@types/react-dom",
"//:node_modules/@types/plotly.js",
"//:node_modules/@types/react-plotly.js",
]

# Dependencies required to run tests.
Expand Down
2 changes: 1 addition & 1 deletion home_api/frontend/src/components/DatePicker.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ import React from 'react';

it('should render', async () => {
render(
<DatePicker start={1} end={2} onChangeStart={() => {}} onChangeEnd={() => {}} />
<DatePicker start={"2024-06-01"} end={"2024-06-10"} onChangeStart={() => {}} onChangeEnd={() => {}} />
);
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import React from 'react';

const DatePicker = (props) => {
const {start, end, onChangeStart, onChangeEnd} = props;
type DatePickerProps = {
start: string,
end: string,
onChangeStart: Function,
onChangeEnd: Function
}

const DatePicker = ({start, end, onChangeStart, onChangeEnd}: DatePickerProps) => {
return (
<div>
<div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
import React from 'react';
import _ from 'lodash';

const MultiSelect = (props) => {
const {
name,
value,
onChange,
allValues
} = props;
type MultiSelectProps = {
name: string,
value: Array<string>,
onChange: Function,
allValues: Array<string>
}

const optionElement = (type, selectedValues) => {
const MultiSelect = ({name, value, onChange, allValues}: MultiSelectProps) => {
const optionElement = (type: string, selectedValues: Array<string>) => {
if (selectedValues.includes(type)) {
return (<option value={type} selected>{type}</option>);
} else {
return (<option value={type}>{type}</option>);
}
}

const getSelectedOptions = (select) => {
const getSelectedOptions = (options: HTMLOptionsCollection) => {
return _.map(
_.filter(select.options, (opt) => {return opt.selected;}),
_.filter(options, (opt) => {return opt.selected;}),
(opt) => { return opt.value; }
);
}
Expand All @@ -29,7 +29,7 @@ const MultiSelect = (props) => {
multiple={true}
name={name}
value={value}
onChange={(e) => {onChange(getSelectedOptions(e.target));}}
onChange={(e) => {onChange(getSelectedOptions(e.target.options));}}
>
{_.map(
allValues,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@ import React from 'react';
import { gql } from '@apollo/client/core';
import { useQuery } from '@apollo/client/react/hooks';
import _ from 'lodash';
import createPlotlyComponent from 'react-plotlyjs';
import Plotly from 'plotly.js-basic-dist';

const PlotlyComponent = createPlotlyComponent(Plotly);
import Plot from 'react-plotly.js';

const GET_MONTHLY_SPEND = gql`
query MonthlySpend(
Expand All @@ -32,17 +29,26 @@ const GET_MONTHLY_SPEND = gql`
}
`;

const TransactionChart = (props) => {
const {
earliestDate,
latestDate,
minAmount,
maxAmount,
types,
categories,
accounts
} = props;
type TransactionChartProps = {
earliestDate: number,
latestDate: number,
minAmount: number,
maxAmount: number,
types: string[],
categories: string[],
accounts: string[],

}

const TransactionChart = ({
earliestDate,
latestDate,
minAmount,
maxAmount,
types,
categories,
accounts
}: TransactionChartProps) => {
const { data, loading, error } = useQuery(GET_MONTHLY_SPEND, {
variables: {
earliestDate,
Expand All @@ -61,23 +67,23 @@ const TransactionChart = (props) => {
if (loading) return loadingDisplay;
if (error) return errorDisplay;

const graphData = [
{
x: _.map(data.amountByMonth, (a) => { return a.formattedMonth; }),
y: _.map(data.amountByMonth, (a) => { return a.amount / 100.0; }),
type: 'bar'
}
];
const layout = {

};
const config = {

};
return (
<PlotlyComponent
<Plot
className="whatever"
data={graphData}
data={[
{
x: _.map(data.amountByMonth, (a) => { return a.formattedMonth; }),
y: _.map(data.amountByMonth, (a) => { return a.amount / 100.0; }),
type: 'bar',
mode: 'lines',
},
]}
layout={layout}
config={config}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import TransactionFilters from './TransactionFilters';
import TransactionList from './TransactionList';
import TransactionChart from './TransactionChart';

function updateStateInUrl(param, stateFn) {
function updateStateInUrl(param: string, stateFn: Function): Function {
const history = createBrowserHistory();
const query = new URLSearchParams(history.location.search)
function update(value) {
function update(value: any) {
if (Array.isArray(value)) {
query.set(param, value.join(','))
} else {
Expand All @@ -21,41 +21,41 @@ function updateStateInUrl(param, stateFn) {
return update;
}

function useStart(initialStart) {
function useStart(initialStart: string | null): [string, Function] {
const defaultDate = new Date();
const earliestDate = (defaultDate.getFullYear() - 1) + '-' + (defaultDate.getMonth() + 1) + '-' + defaultDate.getDate();
const [start, setStart] = useState(initialStart || earliestDate);
return [start, updateStateInUrl('start', setStart)];
}

function useEnd(initialEnd) {
function useEnd(initialEnd: string | null): [string, Function] {
const defaultDate = new Date();
const latestDate = defaultDate.getFullYear() + '-' + (defaultDate.getMonth() + 1) + '-' + defaultDate.getDate();
const [end, setEnd] = useState(initialEnd || latestDate);
return [end, updateStateInUrl('end', setEnd)];
}

function useMinAmount(initialMinAmount) {
function useMinAmount(initialMinAmount: number): [number, Function] {
const [minAmount, setMinAmount] = useState(initialMinAmount);
return [minAmount, updateStateInUrl('minAmount', setMinAmount)];
}

function useMaxAmount(initialMaxAmount) {
function useMaxAmount(initialMaxAmount: number): [number, Function] {
const [maxAmount, setMaxAmount] = useState(initialMaxAmount);
return [maxAmount, updateStateInUrl('maxAmount', setMaxAmount)];
}

function useTypes(initialTypes) {
function useTypes(initialTypes: string[]): [string[], Function] {
const [types, setTypes] = useState(initialTypes);
return [types, updateStateInUrl('types', setTypes)];
}

function useCategories(initialCategories) {
function useCategories(initialCategories: string[]): [string[], Function] {
const [categories, setCategories] = useState(initialCategories);
return [categories, updateStateInUrl('categories', setCategories)];
}

function useAccounts(initialAccounts) {
function useAccounts(initialAccounts: string[]): [string[], Function] {
const [accounts, setAccounts] = useState(initialAccounts);
return [accounts, updateStateInUrl('accounts', setAccounts)];
}
Expand All @@ -74,19 +74,19 @@ const TransactionDisplay = () => {
const parsedStartSeconds = Math.round(parsedStart / 1000);
const parsedEndSeconds = Math.round(parsedEnd / 1000);

const [minAmount, setMinAmount] = useMinAmount(parseInt(query.get('minAmount'), 10));
const [maxAmount, setMaxAmount] = useMaxAmount(parseInt(query.get('maxAmount'), 10));
const [minAmount, setMinAmount] = useMinAmount(parseInt(query.get('minAmount') || '0', 10));
const [maxAmount, setMaxAmount] = useMaxAmount(parseInt(query.get('maxAmount') || '0', 10));

const queryTypes = query.get('types');
const defaultTypes = (queryTypes === null || queryTypes === '') ? null : queryTypes.split(",")
const defaultTypes = (queryTypes === null || queryTypes === '') ? [] : queryTypes.split(",")
const [types, setTypes] = useTypes(defaultTypes);

const queryCategories = query.get('categories')
const defaultCategories = (queryCategories === null || queryCategories === '') ? null : queryCategories.split(",")
const defaultCategories = (queryCategories === null || queryCategories === '') ? [] : queryCategories.split(",")
const [categories, setCategories] = useCategories(defaultCategories);

const queryAccounts = query.get('accounts')
const defaultAccounts = (queryAccounts === null || queryAccounts === '') ? null : queryAccounts.split(",")
const defaultAccounts = (queryAccounts === null || queryAccounts === '') ? [] : queryAccounts.split(",")
const [accounts, setAccounts] = useAccounts(defaultAccounts);

const chartElement = validDates ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,39 @@ const GET_FILTERS = gql`
}
`;

const TransactionFilters = (props) => {
const {
start,
onChangeStart,
end,
onChangeEnd,
minAmount,
onChangeMinAmount,
maxAmount,
onChangeMaxAmount,
types,
onChangeTypes,
categories,
onChangeCategories,
accounts,
onChangeAccounts
} = props;
type TransactionFiltersProps = {
start: string,
onChangeStart: Function,
end: string,
onChangeEnd: Function,
minAmount: number,
onChangeMinAmount: Function,
maxAmount: number,
onChangeMaxAmount: Function,
types: string[],
onChangeTypes: Function,
categories: string[],
onChangeCategories: Function,
accounts: string[],
onChangeAccounts: Function,
}

const TransactionFilters = ({
start,
onChangeStart,
end,
onChangeEnd,
minAmount,
onChangeMinAmount,
maxAmount,
onChangeMaxAmount,
types,
onChangeTypes,
categories,
onChangeCategories,
accounts,
onChangeAccounts
}: TransactionFiltersProps) => {
const { data, loading, error } = useQuery(GET_FILTERS);

const loadingDisplay = <h1>Loading filters...</h1>;
Expand All @@ -57,7 +72,7 @@ const TransactionFilters = (props) => {
onChangeEnd={onChangeEnd}
/>
<div className="mb-6">
<label className="block mb-2" for="minAmount">Minimum Amount</label>
<label className="block mb-2" htmlFor="minAmount">Minimum Amount</label>
<input
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
id="minAmount"
Expand All @@ -68,7 +83,7 @@ const TransactionFilters = (props) => {
/>
</div>
<div className="mb-6">
<label className="block mb-2" for="maxAmount">Maximum Amount</label>
<label className="block mb-2" htmlFor="maxAmount">Maximum Amount</label>
<input
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
id="maxAmount"
Expand All @@ -79,7 +94,7 @@ const TransactionFilters = (props) => {
/>
</div>
<div className="mb-6">
<label className="block mb-2" for="types">Types</label>
<label className="block mb-2" htmlFor="types">Types</label>
<MultiSelect
name="types"
value={types}
Expand All @@ -88,7 +103,7 @@ const TransactionFilters = (props) => {
/>
</div>
<div className="mb-6">
<label className="block mb-2" for="types">Accounts</label>
<label className="block mb-2" htmlFor="types">Accounts</label>
<MultiSelect
name="accounts"
value={accounts}
Expand All @@ -97,7 +112,7 @@ const TransactionFilters = (props) => {
/>
</div>
<div className="mb-6">
<label className="block mb-2" for="types">Categories</label>
<label className="block mb-2" htmlFor="types">Categories</label>
<MultiSelect
name="categories"
value={categories}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,21 +34,30 @@ const GET_TRANSACTIONS = gql`
}
`;

const formatCurrency = (amt, type) => {
const formatCurrency = (amt: number, type: string): string => {
const dollarAmt = `$${amt / 100.0}`;
return (type === 'debit') ? dollarAmt : '-' + dollarAmt;
}

const TransactionList = (props) => {
const {
earliestDate,
latestDate,
minAmount,
maxAmount,
types,
categories,
accounts
} = props;
type TransactionListProps = {
earliestDate: number,
latestDate: number,
minAmount: number,
maxAmount: number,
types: string[],
categories: string[],
accounts: string[],
}

export type FormattedTransaction = {
formattedDate: string,
account: string,
description: string,
category: string,
amount: string,
}

const TransactionList = ({earliestDate, latestDate, minAmount, maxAmount, types, categories, accounts}: TransactionListProps) => {
const { data, loading, error } = useQuery(GET_TRANSACTIONS, {
variables: {
earliestDate,
Expand All @@ -67,7 +76,7 @@ const TransactionList = (props) => {
if (loading) return loadingDisplay;
if (error) return errorDisplay;

const formattedTransactions = _.map(data.transactions || [], (txn) => {
const formattedTransactions = _.map(data.transactions || [], (txn): FormattedTransaction => {
return {
formattedDate: txn.formattedDate,
account: txn.account,
Expand Down
Loading

0 comments on commit ab34984

Please sign in to comment.