Skip to content
This repository has been archived by the owner on Oct 26, 2021. It is now read-only.

Commit

Permalink
Working on new expense dialog.
Browse files Browse the repository at this point in the history
  • Loading branch information
elliotcourant committed Mar 28, 2021
1 parent 2a2f97d commit 3ebae15
Show file tree
Hide file tree
Showing 12 changed files with 357 additions and 31 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@date-io/moment": "1.x",
"@material-ui/core": "4.11.3",
"@material-ui/icons": "4.11.2",
"@material-ui/lab": "4.0.0-alpha.57",
Expand Down Expand Up @@ -47,7 +48,7 @@
"jest-watch-typeahead": "0.6.1",
"js-cookie": "2.2.1",
"mini-css-extract-plugin": "1.4.0",
"moment": "2.29.1",
"moment": "^2.29.1",
"node-notifier": "9.0.1",
"optimize-css-assets-webpack-plugin": "5.0.4",
"path-to-regexp": "6.2.0",
Expand Down
272 changes: 251 additions & 21 deletions src/components/Expenses/NewExpenseDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,32 @@
import { Button, Dialog, DialogActions, DialogContent, DialogTitle, IconButton, Typography } from "@material-ui/core";
import React, { Component } from "react";
import MomentUtils from '@date-io/moment';
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
FormControl,
Input,
InputAdornment,
InputLabel,
Step,
StepContent,
StepLabel,
Stepper,
TextField
} from "@material-ui/core";
import { KeyboardDatePicker, MuiPickersUtilsProvider } from '@material-ui/pickers';
import FundingSchedule from "data/FundingSchedule";
import { Formik, FormikErrors } from "formik";
import { Map } from 'immutable';
import moment from "moment";
import React, { Component, Fragment } from "react";
import { connect } from "react-redux";

enum NewExpenseStep {
Name,
Amount,
Date,
Recurrence,
Funding,
}
Expand All @@ -13,36 +36,243 @@ export interface PropTypes {
isOpen: boolean;
}

export interface WithConnectionPropTypes extends PropTypes {
fundingSchedules: Map<number, FundingSchedule>;
}

export interface State {
step
step: NewExpenseStep;
canNextStep: boolean;
}

interface newExpenseForm {
name: string;
amount: number;
nextOccurrence: moment.Moment;
recurrenceRule: string;
fundingScheduleId: number;
}

export class NewExpenseDialog extends Component<PropTypes, any> {
const initialValues: newExpenseForm = {
name: '',
amount: 0.00,
nextOccurrence: moment(),
recurrenceRule: '',
fundingScheduleId: 0,
};

class NewExpenseDialog extends Component<WithConnectionPropTypes, State> {

state = {
step: NewExpenseStep.Name,
canNextStep: true,
};

validateInput = (values: newExpenseForm): FormikErrors<any> => {
const { step, canNextStep } = this.state;

switch (step) {
// case NewExpenseStep.Name:
// if (values.name.length === 0) {
// return {
// name: 'Name cannot be blank'
// };
// }
}

// if (!canNextStep) {
// this.setState({
// canNextStep: true
// });
// }

return {};
};

submit = (values: newExpenseForm, { setSubmitting }) => {

};

nextStep = () => {
return this.setState(prevState => ({
canNextStep: true,
step: Math.min(NewExpenseStep.Funding, prevState.step + 1),
}));
};

previousStep = () => {
return this.setState(prevState => ({
canNextStep: true, // Math.min(NewExpenseStep.Name, prevState.step - 1) < prevState.step,
step: Math.max(NewExpenseStep.Name, prevState.step - 1),
}));
};

renderActions = () => {
const { onClose } = this.props;
const { step, canNextStep } = this.state;

const cancelButton = (
<Button color="secondary" onClick={ onClose }>
Cancel
</Button>
);

renderStepContent = () => {
const previousButton = (
<Button color="secondary" onClick={ this.previousStep }>
Previous
</Button>
);

const nextButton = (
<Button color="primary" onClick={ this.nextStep } disabled={ !canNextStep }>
Next
</Button>
);

const submitButton = (
<Button color="primary" type="submit">
Create
</Button>
);

switch (step) {
case NewExpenseStep.Name:
return (
<Fragment>
{ cancelButton }
{ nextButton }
</Fragment>
);
case NewExpenseStep.Funding:
return (
<Fragment>
{ previousButton }
{ submitButton }
</Fragment>
);
default:
return (
<Fragment>
{ previousButton }
{ nextButton }
</Fragment>
);
}
};

render() {
const { onClose, isOpen } = this.props;
const { step } = this.state;

return (
<Dialog onClose={ onClose } open={ isOpen }>
<DialogTitle>
Create a new expense
</DialogTitle>
<DialogContent>

</DialogContent>
<DialogActions>
<Button color="primary">
Disagree
</Button>
<Button color="primary">
Agree
</Button>
</DialogActions>
</Dialog>
<Formik
initialValues={ initialValues }
validate={ this.validateInput }
onSubmit={ this.submit }
>
{ ({
values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
setFieldValue,
isSubmitting,
}) => (
<form onSubmit={ handleSubmit }>
<MuiPickersUtilsProvider utils={ MomentUtils }>
<Dialog open={ isOpen }>
<DialogTitle>
Create a new expense
</DialogTitle>
<DialogContent>
<div className="w-96">
<Stepper activeStep={ step } orientation="vertical">
<Step key="What is your expense for?">
<StepLabel>What is your expense for?</StepLabel>
<StepContent>
<TextField
error={ touched.name && !!errors.name }
helperText={ (touched.name && errors.name) ? errors.name : null }
autoFocus
id="new-expense-name"
name="name"
className="w-full"
label="Name"
onChange={ handleChange }
onBlur={ handleBlur }
value={ values.name }
disabled={ isSubmitting }
/>
</StepContent>
</Step>
<Step key="How much do you need?">
<StepLabel>How much do you need?</StepLabel>
<StepContent>
<FormControl fullWidth>
<InputLabel htmlFor="new-expense-amount">Amount</InputLabel>
<Input
id="new-expense-amount"
name="amount"
value={ values.amount }
onBlur={ handleBlur }
onChange={ handleChange }
disabled={ isSubmitting }
startAdornment={ <InputAdornment position="start">$</InputAdornment> }
/>
</FormControl>
</StepContent>
</Step>
<Step key="When do you need it next?">
<StepLabel>When do you need it next?</StepLabel>
<StepContent>
<KeyboardDatePicker
fullWidth
minDate={ moment().subtract('1 day') }
name="date"
margin="normal"
id="date-picker-dialog"
label="Date picker dialog"
format="MM/DD/yyyy"
value={ values.nextOccurrence }
onChange={ (value) => setFieldValue('nextOccurrence', value) }
KeyboardButtonProps={ {
'aria-label': 'change date',
} }
/>
</StepContent>
</Step>
<Step key="How frequently do you need it?">
<StepLabel>How frequently do you need it?</StepLabel>
<StepContent>
<TextField id="new-expense-frequency" className="w-full" label="Frequency"/>
</StepContent>
</Step>
<Step key="How do you want to fund it?">
<StepLabel>How do you want to fund it?</StepLabel>
<StepContent>
<TextField id="new-expense-funding" className="w-full" label="Funding"/>
</StepContent>
</Step>
</Stepper>
</div>
</DialogContent>
<DialogActions>
{ this.renderActions() }
</DialogActions>
</Dialog>
</MuiPickersUtilsProvider>
</form>
) }
</Formik>
)
}
}

export default connect(
(state, props: PropTypes) => ({
fundingSchedules: Map<number, FundingSchedule>(),
}),
{}
)(NewExpenseDialog)
3 changes: 2 additions & 1 deletion src/components/Transactions/TransactionDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ interface WithConnectionPropTypes {
spending: Map<number, Spending>;
}

class TransactionDetailView extends Component<WithConnectionPropTypes, {}> {
export class TransactionDetailView extends Component<WithConnectionPropTypes, {}> {

render() {
const { transaction } = this.props;
Expand Down Expand Up @@ -57,6 +57,7 @@ class TransactionDetailView extends Component<WithConnectionPropTypes, {}> {
</div>
<div className="col-span-3 row-span-1">
{
transaction.categories &&
transaction.categories.map(cat => (
<Chip
className="mr-1 mb-1"
Expand Down
21 changes: 21 additions & 0 deletions src/components/Transactions/spec/TransactionDetail.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { render } from "@testing-library/react";
import { TransactionDetailView } from "components/Transactions/TransactionDetail";
import Transaction from "data/Transaction";
import { Map } from 'immutable';
import moment from "moment";


describe('transaction detail view', () => {
it('will render', () => {
render(<TransactionDetailView
transaction={ new Transaction({
name: 'Dumb Stuff',
date: moment(),
}) }
spending={ new Map() }
/>);

// Make sure it's actually there.
expect(document.querySelector('.transaction-detail')).not.toBeEmptyDOMElement();
});
});
4 changes: 4 additions & 0 deletions src/components/Transactions/spec/TransactionItem.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import Transaction from "data/Transaction";
import moment from "moment";
import { queryText } from "testutils/queryText";

TransactionItem.defaultProps = {
isSelected: false,
selectTransaction: jest.fn(),
};

describe('transaction item', () => {
it('will render', () => {
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Dispatch } from "redux";
import { getSelectedBankAccountId } from "shared/bankAccounts/selectors/getSelectedBankAccountId";
import request from "shared/util/request";

interface GetState {
(): object
}

interface ActionWithState {
(dispatch: Dispatch, getState: GetState): Promise<void>
}

export function fetchFundingSchedulesIfNeeded(): ActionWithState {
return (dispatch, getState) => {
const selectedBankAccountId = getSelectedBankAccountId(getState());
if (!selectedBankAccountId) {
// If the user does not have a bank account selected, then there are no transactions we can request.
return Promise.resolve();
}

return request()
.get(`/bank_accounts/${ selectedBankAccountId }/funding_schedules`)
.then(result => {

})
.catch(error => {

});
};
}
Loading

0 comments on commit 3ebae15

Please sign in to comment.