Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/child refs hoc #39

Merged
merged 3 commits into from
Mar 27, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 24 additions & 37 deletions src/components/Business.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import LinkButton from '../patterns/LinkButton';
import ChildRefList from './ChildRefList';
import withChildSearch from './ChildSearchHOC';
import { getHighlightedText } from '../utils/helperMethods';
import industryCodeDescription from '../utils/siccode';
import { employmentBands, legalStatusBands, tradingStatusBands, turnoverBands } from '../utils/convertBands';
Expand All @@ -15,42 +15,29 @@ import { employmentBands, legalStatusBands, tradingStatusBands, turnoverBands }
* by setting the <LinkButton> loading prop to true, however the spinner
* wouldn't show (inspecting the HTML showed that it was there).
*/
class Business extends React.Component {
constructor(props) {
super(props);
this.state = {
showRefs: false,
isLoading: false,
};
}
showRefs = () => this.setState({ ...this.state.showRefs, showRefs: !this.state.showRefs });
isLoading = (isLoading) => this.setState({ ...this.state.isLoading, isLoading })
render = () => {
const business = this.props.business;
const description = (industryCodeDescription[business.industryCode] === undefined)
? 'No industry code description found' : industryCodeDescription[business.industryCode];
return (
<div className="search-item-container">
<h3 className="saturn sml-margin">{(this.props.toHighlight !== '') ? getHighlightedText(business, this.props.toHighlight) : business.businessName}</h3>
<table className="mars">
<tbody>
<tr><th className="table-grey-text">UBRN</th><td>{business.id}</td></tr>
<tr><th className="table-grey-text">Post code</th><td>{business.postCode}</td></tr>
<tr><th className="table-grey-text">Industry</th><td>{business.industryCode} – {description}</td></tr>
<tr><th className="table-grey-text">Trading status</th><td>{tradingStatusBands[business.tradingStatus]}</td></tr>
<tr><th className="table-grey-text">Legal status</th><td>{legalStatusBands[business.legalStatus]}</td></tr>
<tr><th className="table-grey-text">Employment band</th><td>{employmentBands[business.employmentBands]}</td></tr>
<tr><th className="table-grey-text">Turnover band</th><td>{turnoverBands[business.turnover]}</td></tr>
</tbody>
</table>
<LinkButton id="expandRefs" className="mars" text={(this.state.isLoading) ? 'Loading...' : 'Show reference numbers'} onClick={this.showRefs} loading={false} />
{this.state.showRefs &&
<ChildRefList id={business.id} onLoad={this.isLoading} />
}
</div>
);
}
}
const Business = (props) => {
const business = props.business;
const ChildList = withChildSearch(ChildRefList);
const description = (industryCodeDescription[business.industryCode] === undefined)
? 'No industry code description found' : industryCodeDescription[business.industryCode];
return (
<div className="search-item-container">
<h3 className="saturn sml-margin">{(props.toHighlight !== '') ? getHighlightedText(business, props.toHighlight) : business.businessName}</h3>
<table className="mars">
<tbody>
<tr><th className="table-grey-text">UBRN</th><td>{business.id}</td></tr>
<tr><th className="table-grey-text">Post code</th><td>{business.postCode}</td></tr>
<tr><th className="table-grey-text">Industry</th><td>{business.industryCode} – {description}</td></tr>
<tr><th className="table-grey-text">Trading status</th><td>{tradingStatusBands[business.tradingStatus]}</td></tr>
<tr><th className="table-grey-text">Legal status</th><td>{legalStatusBands[business.legalStatus]}</td></tr>
<tr><th className="table-grey-text">Employment band</th><td>{employmentBands[business.employmentBands]}</td></tr>
<tr><th className="table-grey-text">Turnover band</th><td>{turnoverBands[business.turnover]}</td></tr>
</tbody>
</table>
<ChildList id={business.id} />
</div>
);
};

Business.propTypes = {
business: PropTypes.object.isRequired,
Expand Down
87 changes: 34 additions & 53 deletions src/components/ChildRefList.js
Original file line number Diff line number Diff line change
@@ -1,69 +1,50 @@
import React from 'react';
import PropTypes from 'prop-types';
import LinkButton from '../patterns/LinkButton';
import Panel from '../patterns/Panel';
import config from '../config/api-urls';
import accessAPI from '../utils/accessAPI';

const { REROUTE_URL, API_VERSION, BUSINESS_ENDPOINT } = config;

/**
* @class ChildRefTable - This is a sub list of the child references of
* @const ChildRefTable - This is a sub list of the child references of
* a business that is displayed when a user clicks on the 'Show child
* references' link in the parent <Business /> component.
* references' link. This component is only ever used as a child of
* ChildSearchHOC which provides the loading/fetchData props.
*/
class ChildRefList extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
data: {},
error: false,
errorMessage: '',
};
this.fetchData(this.props.id);
}
fetchData = (id) => {
this.props.onLoad(true);
accessAPI(REROUTE_URL, 'POST', sessionStorage.accessToken, JSON.stringify({
method: 'GET',
endpoint: `${API_VERSION}/${BUSINESS_ENDPOINT}/${id}`,
}), 'business')
.then(json => {
this.setState({ ...this.state, data: json, isLoading: false });
this.props.onLoad(false);
})
.catch(() => {
const errorMessage = 'Error: Unable to get child references.';
this.setState({ ...this.state, errorMessage, error: true, isLoading: false });
this.props.onLoad(false);
});
}
chLink = (id) => (<a target="_blank" rel="noopener noreferrer" href={`http://data.companieshouse.gov.uk/doc/company/${id}`}>{id}</a>);
render = () => (
<div style={{ padding: '20px' }}>
<Panel id="refsErrorPanel" text={this.state.errorMessage} level="error" show={this.state.error} close={null} marginBottom="1rem" />
{(!this.state.isLoading && !this.state.error) &&
<table>
<tbody>
{(this.state.data.companyNo !== '') &&
<tr><th className="table-grey-text-reveal">CH</th><td>{this.chLink(this.state.data.companyNo)}</td></tr>
}
{ this.state.data.vatRefs.map(v => {
return (<tr key={v}><th className="table-grey-text-reveal">VAT</th><td>{v}</td></tr>);
}) }
{ this.state.data.payeRefs.map(p => {
return (<tr key={p}><th className="table-grey-text-reveal">PAYE</th><td>{p}</td></tr>);
}) }
</tbody>
</table>
}
</div>
);
}
const ChildRefList = (props) => (
<div style={{ padding: '20px' }}>
<LinkButton id="expandRefs" className="mars" text={(props.isLoading) ? 'Loading...' : 'Show reference numbers'} onClick={props.fetchData} loading={false} />
<Errorodal show={props.error} message={props.errorMessage} close={props.closeModal} />
<Panel id="refsErrorPanel" text={this.state.errorMessage} level="error" show={this.state.error} close={null} marginBottom="1rem" />
{props.finishedLoading &&
<table>
<tbody>
{(props.data.companyNo !== '') &&
<tr><th className="table-grey-text-reveal">CH</th><td>{props.createChLink(props.data.companyNo)}</td></tr>
}
{ props.data.vatRefs.map(v => {
return (<tr key={v}><th className="table-grey-text-reveal">VAT</th><td>{v}</td></tr>);
}) }
{ props.data.payeRefs.map(p => {
return (<tr key={p}><th className="table-grey-text-reveal">PAYE</th><td>{p}</td></tr>);
}) }
</tbody>
</table>
}
</div>
);

ChildRefList.propTypes = {
id: PropTypes.number.isRequired,
onLoad: PropTypes.func.isRequired,
error: PropTypes.bool.isRequired,
errorMessage: PropTypes.string.isRequired,
closeModal: PropTypes.func.isRequired,
data: PropTypes.object.isRequired,
finishedLoading: PropTypes.bool.isRequired,
isLoading: PropTypes.bool.isRequired,
createChLink: PropTypes.func.isRequired,
fetchData: PropTypes.func.isRequired,
};

export default ChildRefList;
49 changes: 15 additions & 34 deletions src/components/ChildRefTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,51 +3,26 @@ import PropTypes from 'prop-types';
import ReactTable from 'react-table';
import Panel from '../patterns/Panel';
import industryCodeDescription from '../utils/siccode';
import config from '../config/api-urls';
import { formatData } from '../utils/helperMethods';
import accessAPI from '../utils/accessAPI';

const { REROUTE_URL, API_VERSION, BUSINESS_ENDPOINT } = config;

/**
* @class ChildRefTable - This is a sub table shown as an expandable row
* within the main ReactTable results table. When this component is created,
* the data will be fetched. Redux isn't used as the data is only required
* by this component.
* by this component. This component is only ever used as a child of
* ChildSearchHOC, which provides the correct fetchData related props.
*/
class ChildRefTable extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
data: [],
error: false,
errorMessage: '',
};
this.fetchData(this.props.row);
}
fetchData = (row) => {
accessAPI(REROUTE_URL, 'POST', sessionStorage.accessToken, JSON.stringify({
method: 'GET',
endpoint: `${API_VERSION}/${BUSINESS_ENDPOINT}/${row.original.id}`,
}), 'business')
.then(json => this.setState({ ...this.state, data: formatData(json), isLoading: false }))
.catch(() => this.setState({
...this.state,
errorMessage: 'Error: Unable to get child references.',
error: true,
isLoading: false,
}));
}
closeModal = () => this.setState({ ...this.state, error: false, errorMessage: '' });
componentDidMount = () => this.props.fetchData();
render = () => {
const business = this.props.row.original;
const business = this.props.data;
const description = (industryCodeDescription[business.industryCode] === undefined)
? 'No industry code description found' : industryCodeDescription[business.industryCode];
const formattedData = (this.props.finishedLoading) ? formatData(this.props.data) : [];
return (
<div style={{ padding: '20px' }}>
<ReactTable
data={this.state.data}
data={formattedData}
columns={[
{
Header: 'Company Number',
Expand All @@ -65,8 +40,8 @@ class ChildRefTable extends React.Component {
accessor: 'payeRefs',
},
]}
pageSize={this.state.data.length}
loading={this.state.isLoading}
pageSize={formattedData.length}
loading={this.props.isLoading}
className="-striped -highlight"
showPaginationTop={false}
showPaginationBottom={false}
Expand All @@ -80,7 +55,13 @@ class ChildRefTable extends React.Component {
}

ChildRefTable.propTypes = {
row: PropTypes.object.isRequired,
data: PropTypes.object.isRequired,
isLoading: PropTypes.bool.isRequired,
error: PropTypes.bool.isRequired,
errorMessage: PropTypes.string.isRequired,
closeModal: PropTypes.func.isRequired,
fetchData: PropTypes.func.isRequired,
finishedLoading: PropTypes.bool.isRequired,
};

export default ChildRefTable;
71 changes: 71 additions & 0 deletions src/components/ChildSearchHOC.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import React from 'react';
import PropTypes from 'prop-types';
import config from '../config/api-urls';
import accessAPI from '../utils/accessAPI';

const { REROUTE_URL, API_VERSION, BUSINESS_ENDPOINT } = config;

/**
* @function withChildSearch - This is a higher order component that accepts a component
* (which is either the ChildRefTable or ChildRefList page) and wraps it with the common logic
* for getting child ref data from the API.
*
* https://reactjs.org/docs/higher-order-components.html
*
* @param {Object} Content - The child react component
*/
export default function withChildSearch(Content) {
class ChildSearchHOC extends React.Component {
constructor(props) {
super(props);
this.state = {
finishedLoading: false,
isLoading: false,
data: {},
error: false,
errorMessage: '',
};
}
toggle = () => this.setState({ finishedLoading: false });
fetchData = () => {
if (this.state.finishedLoading) {
this.setState({ finishedLoading: false });
} else {
const id = this.props.id;
this.setState({ ...this.state, isLoading: true }, () => {
accessAPI(REROUTE_URL, 'POST', sessionStorage.accessToken, JSON.stringify({
method: 'GET',
endpoint: `${API_VERSION}/${BUSINESS_ENDPOINT}/${id}`,
}), 'business')
.then(json => this.setState({ ...this.state, data: json, finishedLoading: true, isLoading: false }))
.catch(() => {
const errorMessage = 'Error: Unable to get child references.';
this.setState({ ...this.state, errorMessage, error: true, finishedLoading: true, isLoading: false });
});
});
}
}
createChLink = (id) => (<a target="_blank" rel="noopener noreferrer" href={`http://data.companieshouse.gov.uk/doc/company/${id}`}>{id}</a>);
closeModal = () => this.setState({ ...this.state, error: false, errorMessage: '' });
render = () => (
<div>
<Content
fetchData={this.fetchData}
errorMessage={this.state.errorMessage}
closeModal={this.closeModal}
error={this.state.error}
finishedLoading={this.state.finishedLoading}
isLoading={this.state.isLoading}
data={this.state.data}
createChLink={this.createChLink}
/>
</div>
)
}

ChildSearchHOC.propTypes = {
id: PropTypes.number.isRequired,
};

return ChildSearchHOC;
}
4 changes: 3 additions & 1 deletion src/components/ResultsTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import React from 'react';
import PropTypes from 'prop-types';
import ReactTable from 'react-table';
import 'react-table/react-table.css';
import withChildSearch from './ChildSearchHOC';
import ChildRefTable from '../components/ChildRefTable';
import { getHighlightedText } from '../utils/helperMethods';
import { employmentBands, legalStatusBands, tradingStatusBands, turnoverBands } from '../utils/convertBands';

const ResultsTable = (props) => {
const ChildTable = withChildSearch(ChildRefTable);
return (
<div id="react-table">
<ReactTable
Expand Down Expand Up @@ -58,7 +60,7 @@ const ResultsTable = (props) => {
]}
defaultPageSize={props.defaultPageSize}
className="-striped -highlight"
SubComponent={row => <ChildRefTable row={row} />}
SubComponent={row => <ChildTable id={row.original.id} />}
/>
</div>
);
Expand Down