src/Components/ResultsMultiSearchHeader/ResultsMultiSearchHeader.jsx
import React, { Component } from 'react';import PropTypes from 'prop-types';import FontAwesome from 'react-fontawesome';import { orderBy } from 'lodash';import { FILTER_ITEMS_ARRAY, USER_PROFILE, EMPTY_FUNCTION } from '../../Constants/PropTypes';import { ENDPOINT_PARAMS } from '../../Constants/EndpointParams';import SearchBar from '../SearchBar/SearchBar';import SkillCodeFilter from '../HomePageFiltersSection/SkillCodeFilter';import SelectForm from '../SelectForm';import { sortGrades } from '../../utilities'; // Set our params as state names so we can easily// use them as properties to query on.const SKILL_PARAM = ENDPOINT_PARAMS.skill;const BUREAU_PARAM = ENDPOINT_PARAMS.org;const GRADE_PARAM = ENDPOINT_PARAMS.grade; class ResultsMultiSearchHeader extends Component { constructor(props) { super(props); this.onChangeText = this.onChangeText.bind(this); this.submitSearch = this.submitSearch.bind(this); this.onChangeGrade = this.onChangeGrade.bind(this); this.onChangeBureau = this.onChangeBureau.bind(this); this.onChangeSkills = this.onChangeSkills.bind(this); this.filterChange = this.filterChange.bind(this); this.state = { q: this.props.defaultFilters.q || '', defaultGrade: null, defaultBureau: null, skillsWasUpdated: false, gradeWasUpdated: false, qWasUpdated: false, bureauWasUpdated: false, }; this.state[SKILL_PARAM] = []; this.state[BUREAU_PARAM] = null; this.state[GRADE_PARAM] = null; } componentWillMount() { this.setupDefaultValues(this.props); } componentWillReceiveProps(props) { this.setupDefaultValues(props); } onChangeSkills(e) { this.setState({ [SKILL_PARAM]: e, skillsWasUpdated: true }, this.filterChange); } onChangeBureau(e) { this.setState({ [BUREAU_PARAM]: e.target.value, bureauWasUpdated: true }, this.filterChange); } onChangeGrade(e) { this.setState({ [GRADE_PARAM]: e.target.value, gradeWasUpdated: true }, this.filterChange); } onChangeText(e) { this.setState({ q: e.target.value, qWasUpdated: true }, this.filterChange); } // Setup default values only once so that we can mount the component while waiting // for async actions to complete. We'll also save state in redux so that the component can be // unmounted and remounted and maintain state. This is useful for responsively rendering this // component while maintaining the selected filters. We also have to balance that // with loading the user's defaults, but only on the initial page load.Function `setupDefaultValues` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring. setupDefaultValues(props) { const { skillsWasUpdated, gradeWasUpdated, qWasUpdated, bureauWasUpdated } = this.state; const { userProfile, defaultFilters } = props; // set default values for our filters const defaultGrade = defaultFilters[GRADE_PARAM] || userProfile.grade; const defaultBureau = defaultFilters[BUREAU_PARAM] || userProfile.bureau; const defaultQuery = defaultFilters.q; const defaultSkills = defaultFilters[SKILL_PARAM] || userProfile.skills; // set keyword to correct state if (!qWasUpdated && defaultQuery) { this.setState({ q: defaultQuery, qWasUpdated: true }); } // set grade to correct state if (!gradeWasUpdated && defaultGrade) { this.setState({ defaultGrade, gradeWasUpdated: true }); } // set bureau to correct state if (!bureauWasUpdated && defaultBureau) { this.setState({ defaultBureau, bureauWasUpdated: true }); } // set skills to correct state if (!skillsWasUpdated && (userProfile.skills || defaultFilters[SKILL_PARAM])) { const mappedDefaultSkills = defaultSkills.length ? // map the skills as either a string or an object property 'code' defaultSkills.slice().map(s => ({ code: s.code || s })) : []; this.setState({ [SKILL_PARAM]: mappedDefaultSkills }); } } formatQuery() { const { q, [SKILL_PARAM]: skillCodes, [BUREAU_PARAM]: bureaus, [GRADE_PARAM]: grades, defaultBureau, defaultGrade } = this.state; const skills = skillCodes.slice().map(s => s.code); // use the defaults if the new value doesn't exist const query = { q, [SKILL_PARAM]: skills, [BUREAU_PARAM]: bureaus || defaultBureau, [GRADE_PARAM]: grades || defaultGrade, }; return query; } // return all the filters upon submission submitSearch(e) { // resolves “Form submission canceled because the form is not connected” warning e.preventDefault(); const query = this.formatQuery(); this.props.onSubmit(query); } // return all the filters as an object whenever any change is made filterChange() { const query = this.formatQuery(); this.props.onFilterChange(query); } render() { const { placeholder, filters, userProfile, filtersIsLoading } = this.props; const { q, defaultGrade, defaultBureau, [SKILL_PARAM]: skills } = this.state; // format skill codes const skillCodes = filters.find(f => f.item && f.item.description === 'skill'); const skillCodesData = skillCodes ? skillCodes.data : []; // format grades const grades = filters.find(f => f.item && f.item.description === 'grade'); let mappedGrades = grades && grades.data ? grades.data.slice().map(g => ({ ...g, value: g.code, text: g.code })) : []; // sort the grades using custom sorting mappedGrades = mappedGrades.sort(sortGrades); // format bureaus const bureaus = filters.find(f => f.item && f.item.description === 'region'); const mappedBureaus = bureaus && bureaus.data ? bureaus.data.slice().map(g => ({ ...g, value: g.code, text: g.custom_description })) : []; // sort the regional bureaus by their calculated label const sortedBureuas = orderBy(mappedBureaus, ['text']); // set the default skills const defaultSkills = skills || userProfile.skills || []; return ( <div className="results-search-bar padded-main-content results-multi-search"> <div className="usa-grid-full results-search-bar-container"> <form className="usa-grid-full" onSubmit={this.submitSearch} > <fieldset className="usa-width-one-whole"> <div className="usa-grid-full"> <div className="usa-grid-full"> <div className="usa-width-five-twelfths search-results-inputs search-keyword"> <legend className="usa-grid-full">Find your next position</legend> <SearchBar id="multi-search-keyword-field" label="Keywords" type="medium" submitText="Search" labelSrOnly noForm noButton placeholder={placeholder} onChangeText={this.onChangeText} defaultValue={q} /> <div className="search-sub-text">Example: Abuja, Nigeria, Political Affairs (5505), Russian 3/3...</div> </div> <div className="usa-width-one-fourth search-results-inputs search-keyword"> <SkillCodeFilter label="Skill Code" isLoading={filtersIsLoading} filters={skillCodesData} onFilterSelect={this.onChangeSkills} userSkills={defaultSkills} /> </div> <div className="usa-width-one-twelfth search-results-inputs search-keyword"> <SelectForm id="grade-searchbar-filter" label="Grade" options={mappedGrades} defaultSort={defaultGrade} includeFirstEmptyOption onSelectOption={this.onChangeGrade} emptyOptionText="" className="select-black select-small" /> </div> <div className="usa-width-one-sixth search-results-inputs search-keyword"> <SelectForm id="bureau-searchbar-filter" label="Regional Bureau" options={sortedBureuas} defaultSort={defaultBureau} includeFirstEmptyOption onSelectOption={this.onChangeBureau} className="select-black select-small" /> </div> <div className="usa-width-one-twelfth search-submit-button"> <button className="usa-button" type="submit"> <FontAwesome name="search" className="label-icon" /> Search </button> </div> </div> </div> </fieldset> </form> </div> </div> ); }} ResultsMultiSearchHeader.propTypes = { onSubmit: PropTypes.func.isRequired, defaultFilters: PropTypes.shape({ q: PropTypes.string, }), placeholder: PropTypes.string, filters: FILTER_ITEMS_ARRAY.isRequired, filtersIsLoading: PropTypes.bool, userProfile: USER_PROFILE.isRequired, onFilterChange: PropTypes.func,}; ResultsMultiSearchHeader.defaultProps = { filters: [], defaultFilters: {}, filtersIsLoading: false, placeholder: 'Location, Skill cone (code), Grade, Language, Position number', userProfile: {}, onFilterChange: EMPTY_FUNCTION,}; export default ResultsMultiSearchHeader;