Skip to content

Commit

Permalink
feat: date validation
Browse files Browse the repository at this point in the history
DateField is now a component with internal state and validation
Will now only query server if data range is valid: startDate <= endDate, and yyyy-mm-dd
  • Loading branch information
HendrikThePendric committed Dec 19, 2018
1 parent 8867a94 commit 7be3c9f
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 56 deletions.
10 changes: 8 additions & 2 deletions i18n/en.pot
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"POT-Creation-Date: 2018-12-18T12:28:50.843Z\n"
"PO-Revision-Date: 2018-12-18T12:28:50.843Z\n"
"POT-Creation-Date: 2018-12-19T10:09:31.407Z\n"
"PO-Revision-Date: 2018-12-19T10:09:31.407Z\n"

msgid "Usage Analytics"
msgstr ""
Expand All @@ -17,6 +17,12 @@ msgstr ""
msgid "Please use the format yyyy-mm-dd"
msgstr ""

msgid "End date is before start date"
msgstr ""

msgid "Start date is before end date"
msgstr ""

msgid "Category"
msgstr ""

Expand Down
25 changes: 4 additions & 21 deletions src/actions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,35 +29,27 @@ export const updateCategory = (_, newCategory) => (dispatch, getState) => {
}
}

// Fetch data if input is valid
export const updateStartDate = ({ target }) => (dispatch, getState) =>
updateDateField(target, ACTIONS.START_DATE_UPDATED, dispatch, getState)
export const updateStartDate = value => (dispatch, getState) =>
update(ACTIONS.START_DATE_UPDATED, value, dispatch, getState)

// Fetch data if input is valid
export const updateEndDate = ({ target }) => (dispatch, getState) =>
updateDateField(target, ACTIONS.END_DATE_UPDATED, dispatch, getState)
export const updateEndDate = value => (dispatch, getState) =>
update(ACTIONS.END_DATE_UPDATED, value, dispatch, getState)

// Always fetch data
export const updateInterval = (_, value) => (dispatch, getState) =>
update(ACTIONS.INTERVAL_UPDATED, value, dispatch, getState)

// Don't fetch data
export const updateAggregationLevel = (_, value) =>
createAction(ACTIONS.AGGREGATION_LEVEL_UPDATED, value)

// Don't fetch data
export const updateChartType = (_, value) =>
createAction(ACTIONS.CHART_TYPE_UPDATED, value)

// Always fetch data
export const updateEventType = (_, value) => (dispatch, getState) =>
update(ACTIONS.EVENT_TYPE_UPDATED, value, dispatch, getState)

// Always fetch data
export const updatePageSize = (_, value) => (dispatch, getState) =>
update(ACTIONS.PAGE_SIZE_UPDATED, value, dispatch, getState)

// Always fetch data
export const updateSortOrder = (_, value) => (dispatch, getState) =>
update(ACTIONS.SORT_ORDER_UPDATED, value, dispatch, getState)

Expand All @@ -68,15 +60,6 @@ function update(actionName, value, dispatch, getState) {
getUsageData(filter, dispatch)
}

function updateDateField(input, actionName, dispatch, getState) {
dispatch(createAction(actionName, input.value))

if (input.validity.valid) {
const { filter } = getState()
getUsageData(filter, dispatch)
}
}

async function getUsageData(filter, dispatch) {
dispatch(createAction(ACTIONS.USAGE_DATA_REQUESTED))

Expand Down
19 changes: 6 additions & 13 deletions src/components/FilterFields/DateField.css
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
.uaa-date-field {
margin-bottom: 8px;
padding: 15px 14px 0 14px;
height: 56px;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
background-color: rgba(0, 0, 10, 0.05);
position: relative;
}

.uaa-date-field input {
font-size: 16px;
font-family: Roboto, sans-serif;
width: 100%;
height: 100%;
height: 41px;
width: calc(100% - 28px);
border: none;
background-color: transparent;
padding: 0;
padding: 15px 14px 0 14px;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
background-color: rgba(0, 0, 10, 0.05);
}

.uaa-date-field input:focus {
Expand All @@ -29,11 +26,7 @@
font-size: 12px;
}
.uaa-date-input-error {
display: none;
color: #e53935;
font-size: 12px;
margin-top: -14px;
}
.uaa-date-field input:invalid + .uaa-date-input-error {
display: block;
}
83 changes: 65 additions & 18 deletions src/components/FilterFields/DateField.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,78 @@
import React from 'react'
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import i18n from '@dhis2/d2-i18n'
import './DateField.css'

// Very basic date input. If browser doesn't support input type date,
// it will use the pattern validation to display an error
function DateField({ label, value, onChange }) {
return (
<div className="uaa-date-field">
<label>{label}</label>
<input
type="date"
value={value}
onChange={onChange}
required
pattern="[0-9]{4}-[0-9]{2}-[0-9]{2}"
/>
<span className="uaa-date-input-error">
{i18n.t('Please use the format yyyy-mm-dd')}
</span>
</div>
)
class DateField extends Component {
constructor(props) {
super(props)
this.state = {
value: props.initialValue,
error: null,
}
this.onChange = this.onChange.bind(this)
}

shouldComponentUpdate(nextProps) {
if (
nextProps.initialValue !== this.props.initialValue &&
this.state.value === nextProps.initialValue
) {
return false
}

return true
}

onChange(event) {
const value = event.target.value
const error = this.getError(value)

this.setState({ value, error })

if (!error) {
this.props.onChange(value)
}
}

getError(value) {
const { startDate, endDate } = this.props

console.log('value, start, end\n', value, startDate, endDate)

if (!/[0-9]{4}-[0-9]{2}-[0-9]{2}/.test(value)) {
return i18n.t('Please use the format yyyy-mm-dd')
}

if (startDate && value < startDate) {
return i18n.t('End date is before start date')
}

if (endDate && value > endDate) {
return i18n.t('Start date is before end date')
}
}

render() {
const { label } = this.props
const { error, value } = this.state
return (
<div className="uaa-date-field">
<label>{label}</label>
<input type="date" value={value} onChange={this.onChange} />
{error && <span className="uaa-date-input-error">{error}</span>}
</div>
)
}
}

DateField.propTypes = {
label: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
initialValue: PropTypes.string.isRequired,
startDate: PropTypes.string,
endDate: PropTypes.string,
onChange: PropTypes.func.isRequired,
}

Expand Down
6 changes: 4 additions & 2 deletions src/components/FilterFields/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,16 @@ export const CategoryDropDown = connect(

export const StartDateInput = connect(
state => ({
value: state.filter.startDate,
initialValue: state.filter.startDate,
endDate: state.filter.endDate,
}),
{ onChange: updateStartDate }
)(props => <DateField {...props} label={i18n.t('Start date')} />)

export const EndDateInput = connect(
state => ({
value: state.filter.endDate,
initialValue: state.filter.endDate,
startDate: state.filter.startDate,
}),
{ onChange: updateEndDate }
)(props => <DateField {...props} label={i18n.t('End date')} />)
Expand Down

0 comments on commit 7be3c9f

Please sign in to comment.