Skip to content

Commit

Permalink
Feature/range match search (#2)
Browse files Browse the repository at this point in the history
* Fix validation for UBRN search

- Add config file for valid refs

* Add ONS inputs for Match search, used SDC styles

- Used Less to isolate SDC styles, prefix with 'sdc-isolation' to prevent conflicts with other styles

* Add inputs for Match search

- Add bands to utils/convertBands.js

* Add code for working Match search

- Uses all inputs

* Fix reload of Match data

- When going from match -> Home or another page, the data will be reloaded into the search inputs when you go back
- Turn off eslint rule

* Fix issue with clearing of Select boxes

* Remove UPRN field from Match page

* Add react-select input with working aria-hidden

- Use 'hacky' bit of js to fix buggy react-select component, on any update, look for classNames within react-select with an aria-hidden tag and change them
- Add filterable toggle (checkbox) to match search

* Refactor MatchForm, use seperate Components

- Move TextInput and SelectInput to seperate files
- Fix PropTypes

* Add scroll to results on search for Match

* Fix scroll to bottom on search feature

* Fix focus on first input after resetting the form

* Add minor fixes to UBRN page, naming etc.

* Fix naming of SearchRefForm -> UBRNForm

* Use CheckBoxInput component in Match

* Add fixed code for RangeSearch query

* Fix persistance of multiple select input values

* Fix bug for Clear button not clearing all the data

* Move SelectMultipleInput code to seperate file

* Add TextInputInline component for dual inputs

- i.e. for min/max inputs

* Fix use of filter props in RangeForm

* Add working RangeQuery search

- Add new formatter method in utils/formQuery.js
- Add (currently unused) consts in ApiConstants
- Fix apiAction for RangeQuery

* Fix formatRangeQuery, remove debugging statements

* Fix showFilter bug, shows after new search

* Transform businessName to upper case

* Add encodeSpecialChars method to formQuery

- For encoding businessName
- Copied method from business-index-ui
  • Loading branch information
ONS-Tom authored and ONS-Anthony committed Oct 25, 2017
1 parent 4099154 commit 45cc3ac
Show file tree
Hide file tree
Showing 28 changed files with 7,300 additions and 62 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
"react/forbid-prop-types": "off",
"arrow-body-style": "off",
"no-return-assign": "off",
"arrow-parens": "off"
"arrow-parens": "off",
"react/no-did-mount-set-state": "off"
},
"env": {
"browser": true
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
"react-redux": "^5.0.5",
"react-router": "^2.6.1",
"react-router-bootstrap": "^0.23.1",
"react-select": "^1.0.0-rc.10",
"react-stepper-horizontal": "^1.0.9",
"react-table": "^6.5.3",
"react-toggle": "^4.0.1",
Expand Down
15 changes: 12 additions & 3 deletions src/actions/ApiActions.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ADD_MOST_RECENT_ERROR, REMOVE_LAST_ERROR, SET_UBRN_ERROR_MESSAGE, SENDING_UBRN_REQUEST, SET_UBRN_RESULTS, SET_UBRN_QUERY, SET_UBRN_HEADERS, SET_RANGE_HEADERS, SET_RANGE_ERROR_MESSAGE, SENDING_RANGE_REQUEST, SET_RANGE_RESULTS, SET_RANGE_QUERY, SET_PERIOD, SET_MATCH_RESULTS, SET_MATCH_HEADERS, SENDING_MATCH_REQUEST, SET_MATCH_QUERY, SET_MATCH_ERROR_MESSAGE } from '../constants/ApiConstants';
import { SET_MATCH_FORMATTED_QUERY, SET_RANGE_FORMATTED_QUERY, ADD_MOST_RECENT_ERROR, REMOVE_LAST_ERROR, SET_UBRN_ERROR_MESSAGE, SENDING_UBRN_REQUEST, SET_UBRN_RESULTS, SET_UBRN_QUERY, SET_UBRN_HEADERS, SET_RANGE_HEADERS, SET_RANGE_ERROR_MESSAGE, SENDING_RANGE_REQUEST, SET_RANGE_RESULTS, SET_RANGE_QUERY, SET_PERIOD, SET_MATCH_RESULTS, SET_MATCH_HEADERS, SENDING_MATCH_REQUEST, SET_MATCH_QUERY, SET_MATCH_ERROR_MESSAGE } from '../constants/ApiConstants';
import apiSearch from '../utils/apiSearch';
import { formMatchQuery, formRangeQuery } from '../utils/formQuery';
import periods from '../config/periods';

/**
Expand All @@ -15,7 +16,9 @@ export function matchSearch(query) {
dispatch(sendingRequest(SENDING_MATCH_REQUEST, true));
dispatch(setResults(SET_MATCH_RESULTS, { results: [] }));
dispatch(setQuery(SET_MATCH_QUERY, query));
apiSearch.match(query, (success, data) => {
const formattedQuery = formMatchQuery(query);
dispatch(setFormattedQuery(SET_MATCH_FORMATTED_QUERY, formattedQuery));
apiSearch.match(formattedQuery, (success, data) => {
dispatch(sendingRequest(SENDING_MATCH_REQUEST, false));
if (success) {
dispatch(setResults(SET_MATCH_RESULTS, {
Expand Down Expand Up @@ -44,7 +47,9 @@ export function rangeSearch(query) {
dispatch(sendingRequest(SENDING_RANGE_REQUEST, true));
dispatch(setResults(SET_RANGE_RESULTS, { results: [] }));
dispatch(setQuery(SET_RANGE_QUERY, query));
apiSearch.match(query, (success, data) => {
const formattedQuery = formRangeQuery(query);
dispatch(setFormattedQuery(SET_RANGE_FORMATTED_QUERY, formattedQuery));
apiSearch.match(formattedQuery, (success, data) => {
dispatch(sendingRequest(SENDING_RANGE_REQUEST, false));
if (success) {
dispatch(setResults(SET_RANGE_RESULTS, {
Expand Down Expand Up @@ -109,6 +114,10 @@ export function setQuery(type, query) {
return { type, query };
}

export function setFormattedQuery(type, query) {
return { type, query };
}

export function setHeaders(type, newState) {
return { type, newState };
}
Expand Down
22 changes: 22 additions & 0 deletions src/components/CheckBoxInput.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
import PropTypes from 'prop-types';

const CheckBoxInput = ({ id, label, onChangeFilter, value }) => {
return (
<div className="sdc-isolation field field--checkbox field--multiplechoice">
<div className="field__item js-focusable-box">
<input onChange={onChangeFilter} value={value} className="input input--checkbox js-focusable" type="checkbox" id={id} />
<label className="label label--inline venus" htmlFor="checkbox">{label}</label>
</div>
</div>
);
};

CheckBoxInput.propTypes = {
id: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
onChangeFilter: PropTypes.func.isRequired,
value: PropTypes.string.isRequired,
};

export default CheckBoxInput;
47 changes: 47 additions & 0 deletions src/components/MatchForm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button } from 'registers-react-library';
import { employmentBands, legalStatusBands, turnoverBands, tradingStatusBands } from '../utils/convertBands';
import TextInput from './TextInput';
import SelectInput from './SelectInput';
import CheckBoxInput from './CheckBoxInput';

class MatchForm extends React.Component {
// For the id of each input, we use the same name as the business-index-api input
render() {
return (
<form>
<TextInput ref={ip => (this.childTextInput = ip)} value={this.props.initialValues.BusinessName} label="Business Name" id="BusinessName" autoFocus onChange={this.props.onChange} /><br />
<TextInput value={this.props.initialValues.VatRefs} label="VAT Number" id="VatRefs" onChange={this.props.onChange} /><br />
<TextInput value={this.props.initialValues.PayeRefs} label="PAYE Number" id="PayeRefs" onChange={this.props.onChange} /><br />
<TextInput value={this.props.initialValues.CompanyNo} label="Company Number" id="CompanyNo" onChange={this.props.onChange} /><br />
<TextInput value={this.props.initialValues.IndustryCode} label="Industry Code" id="IndustryCode" onChange={this.props.onChange} /><br />
<SelectInput value={this.props.initialValues.EmploymentBands} label="Employment Bands" id="EmploymentBands" onChange={this.props.onChange} bands={employmentBands} /><br />
<SelectInput value={this.props.initialValues.LegalStatus} label="Legal Status" id="LegalStatus" onChange={this.props.onChange} bands={legalStatusBands} /><br />
<SelectInput value={this.props.initialValues.Turnover} label="Turnover" id="Turnover" onChange={this.props.onChange} bands={turnoverBands} /><br />
<SelectInput value={this.props.initialValues.TradingStatus} label="Trading Status" id="TradingStatus" onChange={this.props.onChange} bands={tradingStatusBands} /><br />
<TextInput label="Post Code" id="PostCode" onChange={this.props.onChange} /><br />
<Button id="loginButton" size="wide" text="Search" onClick={!this.props.currentlySending ? this.props.onSubmit : null} ariaLabel="Login Button" type="submit" loading={this.props.currentlySending} />
&nbsp;
<Button id="clearButton" size="wide" text="Clear" onClick={this.props.onClear} ariaLabel="Clear Button" type="reset" />
<br /><br />
{this.props.showFilter &&
<CheckBoxInput value={this.props.filter} label="Filter Results" id="FilterCheckbox" onChangeFilter={this.props.onChangeFilter} />
}
</form>
);
}
}

MatchForm.propTypes = {
currentlySending: PropTypes.bool.isRequired,
onSubmit: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
onClear: PropTypes.func.isRequired,
onChangeFilter: PropTypes.func.isRequired,
filter: PropTypes.bool.isRequired,
showFilter: PropTypes.bool.isRequired,
initialValues: PropTypes.object.isRequired,
};

export default MatchForm;
47 changes: 47 additions & 0 deletions src/components/RangeForm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button } from 'registers-react-library';
import Select from 'react-select';
import 'react-select/dist/react-select.min.css';
import { employmentBands, legalStatusBands, turnoverBands, tradingStatusBands } from '../utils/convertBands';
import TextInput from './TextInput';
import CheckBoxInput from './CheckBoxInput';
import SelectMultipleInput from './SelectMultipleInput';
import TextInputInline from './TextInputInline';

class RangeForm extends React.Component {
// For the id of each input, we use the same name as the business-index-api input
render() {
return (
<form>
<TextInputInline autoFocus ref={ip => (this.childTextInput = ip)} placeholderMin="Industry Code Min" placeholderMax="Industry Code Max" value={this.props.initialValues.IndustryCode} label="Industry Code" id="IndustryCode" onChange={this.props.onChange} /><br />
<TextInput value={this.props.initialValues.PostCode} label="Post Code" id="PostCode" onChange={this.props.onChange} /><br />
<SelectMultipleInput value={this.props.initialValues.EmploymentBands} id="EmploymentBands" onChange={this.props.onChange} label="Employment Bands" bands={employmentBands} /><br />
<SelectMultipleInput value={this.props.initialValues.LegalStatus} id="LegalStatus" onChange={this.props.onChange} label="Legal Status Bands" bands={legalStatusBands} /><br />
<SelectMultipleInput value={this.props.initialValues.Turnover} id="Turnover" onChange={this.props.onChange} label="Turnover Bands" bands={turnoverBands} /><br />
<SelectMultipleInput value={this.props.initialValues.TradingStatus} id="TradingStatus" onChange={this.props.onChange} label="Trading Status Bands" bands={tradingStatusBands} /><br />
<br />
<Button id="loginButton" size="wide" text="Search" onClick={!this.props.currentlySending ? this.props.onSubmit : null} ariaLabel="Login Button" type="submit" loading={this.props.currentlySending} />
&nbsp;
<Button id="clearButton" size="wide" text="Clear" onClick={this.props.onClear} ariaLabel="Clear Button" type="reset" />
<br /><br />
{this.props.showFilter &&
<CheckBoxInput value={this.props.filter} label="Filter Results" id="FilterCheckbox" onChangeFilter={this.props.onChangeFilter} />
}
</form>
);
}
}

RangeForm.propTypes = {
currentlySending: PropTypes.bool.isRequired,
onSubmit: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
onClear: PropTypes.func.isRequired,
onChangeFilter: PropTypes.func.isRequired,
filter: PropTypes.bool.isRequired,
showFilter: PropTypes.bool.isRequired,
initialValues: PropTypes.object.isRequired,
};

export default RangeForm;
28 changes: 28 additions & 0 deletions src/components/SelectInput.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react';
import PropTypes from 'prop-types';

const SelectInput = ({ id, label, bands, onChange, value }) => {
if (value === undefined) {
value = '';
}
return (
<div className="sdc-isolation field field--select">
<label className="label" htmlFor="select">{label}
</label>
<select id={id} value={value} className="input input--select" name="select" onInput={onChange} style={{ padding: '0.3rem', fontSize: '1rem' }}>
<option value="">Select an option</option>
{ Object.keys(bands).map(band => (<option key={band} value={band}>{band} [{bands[band]}]</option>))}
</select>
</div>
);
};

SelectInput.propTypes = {
id: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
bands: PropTypes.object.isRequired,
value: PropTypes.string.isRequired,
};

export default SelectInput;
85 changes: 85 additions & 0 deletions src/components/SelectMultipleInput.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React from 'react';
import PropTypes from 'prop-types';
import Select from 'react-select';
import 'react-select/dist/react-select.min.css';

class SelectMultipleInput extends React.Component {
constructor(props) {
super(props);
this.state = {
values: this.formValues(this.props.value),
inputsJson: this.formProperJson(this.props.bands),
};
this.handleSelectChange = this.handleSelectChange.bind(this);
// Look at index.css for .Select-menu-outer for a 'position' fix
// to push down items below when the options menu is open
}
componentWillReceiveProps(nextProps) {
if (this.props.value !== nextProps.value) {
this.setState({ values: this.formValues(nextProps.value) });
}
}
componentDidUpdate() {
// This is to fix a bug with react-select where the little cross symbol does
// not appear due to an aria-hidden attribute.
const elements = document.getElementsByClassName('Select-value-icon');
for (let i = 0; i <= elements.length; i += 1) {
if (document.getElementsByClassName('Select-value-icon')[i] !== undefined) {
document.getElementsByClassName('Select-value-icon')[i].setAttribute('aria-hidden', 'false');
}
}
}
formValues(value) {
if (value === undefined) {
return [];
}
return value.join(',');
}
formProperJson(json) {
const arr = Object.keys(json).map((key) => {
return { label: `${key} [${json[key]}]`, value: key };
});
return arr;
}
handleSelectChange(values) {
this.setState({ values });
// Make our lives easier by mimicking the exact json of an input event
const evt = {
target: {
id: this.props.id,
value: values.split(','),
},
};
this.props.onChange(evt);
}
render() {
return (
<div id={this.props.id}>
<div className="sdc-isolation">
<label className="label" htmlFor="select">{this.props.label}</label>
</div>
<Select
style={{ width: '400px' }}
closeOnSelect={false}
disabled={false}
multi
onChange={this.handleSelectChange}
options={this.state.inputsJson}
placeholder=""
simpleValue
value={this.state.values}
/>
</div>
);
}
}

SelectMultipleInput.propTypes = {
id: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
bands: PropTypes.object.isRequired,
onChange: PropTypes.func.isRequired,
value: PropTypes.array.isRequired,
};

export default SelectMultipleInput;
34 changes: 34 additions & 0 deletions src/components/TextInput.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react';
import PropTypes from 'prop-types';

class TextInput extends React.Component {
render() {
// This is to fix an issue where if this.props.value is undefined, the previous
// value of this.props.value is used.
let value = this.props.value;
if (this.props.value === undefined) {
value = '';
}
return (
<div className="sdc-isolation field">
<label className="label" htmlFor="text-input">{this.props.label}
</label>
<input ref={ip => (this.myInput = ip)} value={value} autoFocus={this.props.autoFocus} className="input input--text" onChange={this.props.onChange} type="text" id={this.props.id} />
</div>
);
}
}

TextInput.defaultProps = {
autoFocus: false,
};

TextInput.propTypes = {
id: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
autoFocus: PropTypes.bool.isRequired,
value: PropTypes.string.isRequired,
};

export default TextInput;
Loading

0 comments on commit 45cc3ac

Please sign in to comment.