Skip to content

Commit

Permalink
Merge pull request #224 from hql287/better-dueDate
Browse files Browse the repository at this point in the history
Better dueDate (Take 2)
  • Loading branch information
hql287 authored Feb 22, 2018
2 parents d62901f + 7dd6984 commit d6fcb9a
Show file tree
Hide file tree
Showing 22 changed files with 708 additions and 223 deletions.
116 changes: 67 additions & 49 deletions app/components/form/DueDate.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,8 @@ import PropTypes from 'prop-types';

// Custom Components
import { Section } from '../shared/Section';

// React Dates
import { SingleDatePicker } from 'react-dates';
import moment from 'moment';

// Styles
import styled from 'styled-components';
const DueDateContent = styled.div`
display: flex;
`;
import DueDatePicker from './DueDatePicker';
import DueDateTerms from './DueDateTerms';

// Animation
import _withFadeInAnimation from '../shared/hoc/_withFadeInAnimation';
Expand All @@ -22,60 +14,86 @@ import _withFadeInAnimation from '../shared/hoc/_withFadeInAnimation';
export class DueDate extends Component {
constructor(props) {
super(props);
this.state = { focused: false };
this.onFocusChange = this.onFocusChange.bind(this);
this.onDateChange = this.onDateChange.bind(this);
this.clearDate = this.clearDate.bind(this);
this.state = this.props.dueDate;
this.toggleDatePicker = this.toggleDatePicker.bind(this);
this.updateCustomDate = this.updateCustomDate.bind(this);
this.updatePaymentTerm = this.updatePaymentTerm.bind(this);
this.updateDueDate = this.updateDueDate.bind(this);
}

shouldComponentUpdate(nextProps, nextState) {
return this.state !== nextState || this.props.dueDate != nextProps.dueDate;
// Handle Clear Form
componentWillReceiveProps(nextProps) {
const { selectedDate, paymentTerm, useCustom } = nextProps.dueDate;
if (selectedDate === null && paymentTerm === null && useCustom === true) {
this.setState(nextProps.dueDate);
}
}

onFocusChange() {
this.setState({ focused: !this.state.focused });
toggleDatePicker() {
this.setState({ useCustom: !this.state.useCustom }, () => {
this.updateDueDate(this.state);
});
}

onDateChange(date) {
const selectedDate = date === null ? null : moment(date).toObject();
this.props.updateFieldData('dueDate', { selectedDate });
updateCustomDate(selectedDate) {
this.setState({ selectedDate }, () => {
this.updateDueDate(this.state);
});
}

clearDate() {
this.onDateChange(null);
updatePaymentTerm(paymentTerm) {
this.setState({ paymentTerm }, () => {
this.updateDueDate(this.state);
});
}

updateDueDate(data) {
this.props.updateFieldData('dueDate', data);
}

render() {
const { t, dueDate } = this.props;
const selectedDate = dueDate.selectedDate
? moment(dueDate.selectedDate)
: null;
const { t } = this.props;
const { selectedDate, paymentTerm } = this.state;
return (
<Section>
<label className="itemLabel">{t('form:fields:dueDate:name')}</label>
<DueDateContent>
<SingleDatePicker
id="invoice-duedate"
placeholder={t('form:fields:dueDate:placeHolder')}
firstDayOfWeek={1}
withFullScreenPortal
displayFormat="DD/MM/YYYY"
hideKeyboardShortcutsPanel
date={selectedDate}
focused={this.state.focused}
onFocusChange={this.onFocusChange}
onDateChange={newDate => this.onDateChange(newDate)}
{this.state.useCustom ? (
<DueDatePicker
t={t}
selectedDate={selectedDate}
updateCustomDate={this.updateCustomDate}
/>
) : (
<DueDateTerms
t={t}
paymentTerm={paymentTerm}
updatePaymentTerm={this.updatePaymentTerm}
/>
{selectedDate !== null && (
<a
className="clearDateBtn active"
href="#"
onClick={this.clearDate}
>
<i className="ion-close-circled" />
</a>
)}
</DueDateContent>
)}
<div>
<div className="radio">
<label>
<input
type="radio"
onChange={this.toggleDatePicker}
checked={this.state.useCustom === true}
value="new"
/>
Custom Date
</label>
</div>
<div className="radio">
<label>
<input
type="radio"
onChange={this.toggleDatePicker}
checked={this.state.useCustom === false}
value="select"
/>
Select Payment Term
</label>
</div>
</div>
</Section>
);
}
Expand Down
78 changes: 78 additions & 0 deletions app/components/form/DueDatePicker.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Libraries
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';

// React Dates
import { SingleDatePicker } from 'react-dates';

// Styles
import _withFadeInAnimation from '../shared/hoc/_withFadeInAnimation';
import styled from 'styled-components';
const Container = styled.div`
display: flex;
margin-bottom: 20px;
`;

// Component
export class DueDatePicker extends PureComponent {
constructor(props) {
super(props);
this.state = { focused: false };
this.onFocusChange = this.onFocusChange.bind(this);
this.onDateChange = this.onDateChange.bind(this);
this.clearDate = this.clearDate.bind(this);
}

onFocusChange() {
this.setState({ focused: !this.state.focused });
}

onDateChange(date) {
const selectedDate = date === null ? null : moment(date).toObject();
this.props.updateCustomDate(selectedDate);
}

clearDate() {
this.onDateChange(null);
}

render() {
const { t, selectedDate } = this.props;
const dueDate = selectedDate === null ? null : moment(selectedDate);
return (
<Container>
<SingleDatePicker
id="invoice-duedate"
placeholder={t('form:fields:dueDate:placeHolder')}
firstDayOfWeek={1}
withFullScreenPortal
displayFormat="DD/MM/YYYY"
hideKeyboardShortcutsPanel
date={dueDate}
focused={this.state.focused}
onFocusChange={this.onFocusChange}
onDateChange={newDate => this.onDateChange(newDate)}
/>
{selectedDate !== null && (
<a className="clearDateBtn active" href="#" onClick={this.clearDate}>
<i className="ion-close-circled" />
</a>
)}
</Container>
);
}
}

DueDatePicker.propTypes = {
selectedDate: PropTypes.object,
t: PropTypes.func.isRequired,
updateCustomDate: PropTypes.func.isRequired,
};

DueDatePicker.defaultProps = {
selectedDate: null,
};

// Export
export default _withFadeInAnimation(DueDatePicker);
68 changes: 68 additions & 0 deletions app/components/form/DueDateTerms.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Libraries
import React, { Component } from 'react';
import PropTypes from 'prop-types';

// Styles
import _withFadeInAnimation from '../shared/hoc/_withFadeInAnimation';
import styled from 'styled-components';
const Container = styled.div`
display: flex;
margin-bottom: 20px;
`;

// Payment Terms
import { paymentTerms } from '../../../libs/paymentTerms';

// Component
export class DueDateTerms extends Component {
constructor(props) {
super(props);
this.handleInputChange = this.handleInputChange.bind(this);
}

componentWillMount() {
if (this.props.paymentTerm === null) {
this.updateSelectedTerm(paymentTerms[0]);
}
}

handleInputChange(event) {
this.updateSelectedTerm(event.target.value);
}

updateSelectedTerm(term) {
const { updatePaymentTerm } = this.props;
updatePaymentTerm(term);
}

render() {
const { t, paymentTerm } = this.props;
const termOptions = paymentTerms.map(term => (
<option key={term} value={term}>
{ t(`form:fields:dueDate:paymentTerms:${term}:label`) }
{' - '}
{ t(`form:fields:dueDate:paymentTerms:${term}:description`) }
</option>
));
return (
<Container>
<select
onChange={this.handleInputChange}
value={paymentTerm === null ? paymentTerms[0] : paymentTerm}
>
{ termOptions }
</select>
</Container>
);
}
}

DueDateTerms.propTypes = {
paymentTerm: PropTypes.string,
t: PropTypes.func.isRequired,
updatePaymentTerm: PropTypes.func.isRequired,
};

// Export
export default _withFadeInAnimation(DueDateTerms);

96 changes: 48 additions & 48 deletions app/components/form/__tests__/DueDate.spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,52 +34,52 @@ describe('Renders correctly to the DOM', () => {
// TODO
it('will not rerender if threre is no change in Props', () => {});

it('receives correct props', () => {
expect(wrapper.prop('dueDate')).toEqual(dueDate);
expect(wrapper.prop('dueDate')).not.toEqual({});
expect(wrapper.prop('updateFieldData')).toEqual(updateFieldData);
});

it('renders necessary element', () => {
expect(wrapper.find('label')).toHaveLength(1);
expect(wrapper.find('label')).not.toHaveLength(2);
expect(wrapper.find(SingleDatePicker)).toHaveLength(1);
expect(wrapper.find(SingleDatePicker)).not.toHaveLength(2);
});

it('call clearDate when click clearDate Button', () => {
// Setup
const spy = jest.spyOn(DueDate.prototype, 'clearDate');
const wrap = mount(
<DueDate t={t} dueDate={dueDate} updateFieldData={updateFieldData} />
);
const clearDateBtn = wrap.find('.clearDateBtn');
// Execute
clearDateBtn.simulate('click');
// Assertion
expect(spy).toHaveBeenCalled();
});

it('clearDate clears selectedDate', () => {
// Setup
const instance = wrapper.instance();
const spy = jest.spyOn(instance, 'clearDate');
// Execute
instance.clearDate();
// Assertion
expect(spy).toHaveBeenCalled();
expect(updateFieldData).toHaveBeenCalled();
expect(updateFieldData).toHaveBeenCalledWith('dueDate', {
selectedDate: null,
});
});

it('matches snapshot', () => {
const tree = renderer
.create(
<DueDate t={t} dueDate={dueDate} updateFieldData={updateFieldData} />
)
.toJSON();
expect(tree).toMatchSnapshot();
});
// it('receives correct props', () => {
// expect(wrapper.prop('dueDate')).toEqual(dueDate);
// expect(wrapper.prop('dueDate')).not.toEqual({});
// expect(wrapper.prop('updateFieldData')).toEqual(updateFieldData);
// });
//
// it('renders necessary element', () => {
// expect(wrapper.find('label')).toHaveLength(1);
// expect(wrapper.find('label')).not.toHaveLength(2);
// expect(wrapper.find(SingleDatePicker)).toHaveLength(1);
// expect(wrapper.find(SingleDatePicker)).not.toHaveLength(2);
// });
//
// it('call clearDate when click clearDate Button', () => {
// // Setup
// const spy = jest.spyOn(DueDate.prototype, 'clearDate');
// const wrap = mount(
// <DueDate t={t} dueDate={dueDate} updateFieldData={updateFieldData} />
// );
// const clearDateBtn = wrap.find('.clearDateBtn');
// // Execute
// clearDateBtn.simulate('click');
// // Assertion
// expect(spy).toHaveBeenCalled();
// });
//
// it('clearDate clears selectedDate', () => {
// // Setup
// const instance = wrapper.instance();
// const spy = jest.spyOn(instance, 'clearDate');
// // Execute
// instance.clearDate();
// // Assertion
// expect(spy).toHaveBeenCalled();
// expect(updateFieldData).toHaveBeenCalled();
// expect(updateFieldData).toHaveBeenCalledWith('dueDate', {
// selectedDate: null,
// });
// });
//
// it('matches snapshot', () => {
// const tree = renderer
// .create(
// <DueDate t={t} dueDate={dueDate} updateFieldData={updateFieldData} />
// )
// .toJSON();
// expect(tree).toMatchSnapshot();
// });
});
Loading

0 comments on commit d6fcb9a

Please sign in to comment.