diff --git a/doc/release/RELEASE-NOTES.md b/doc/release/RELEASE-NOTES.md index 2d01ea885..94898b50b 100644 --- a/doc/release/RELEASE-NOTES.md +++ b/doc/release/RELEASE-NOTES.md @@ -29,6 +29,15 @@ This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html * [OSDEV-1376](https://opensupplyhub.atlassian.net/browse/OSDEV-1376) - Updated automated emails for closure reports (report_result) to remove the term "Rejected" for an improved user experience. Added link to Closure Policy and instructions for submitting a Reopening Report to make the process easier to understand for users. * [OSDEV-1383](https://opensupplyhub.atlassian.net/browse/OSDEV-1383) - Edited text of the automated email that notifies a contributor when one of their facilities has been claimed. The new text provides more information to the contributor to understand the claim process and how they can encourage more of their facilities to claim their profile. * [OSDEV-1474](https://opensupplyhub.atlassian.net/browse/OSDEV-1474) - Added contributor type value to response of `/api/contributors/` endpoint. +* [OSDEV-1130](https://opensupplyhub.atlassian.net/browse/OSDEV-1130) A new page, `Production Location Information`, has been implemented. It includes the following inputs: + * Required and pre-fillable fields: + - Name + - Address + - Country + * Additional information section: Fields for optional contributions from the owner or manager of the production location, including sector(s), product type(s), location type(s), processing type(s), number of workers, and parent company. +The page also features `Go Back` and `Submit` buttons for navigation and form submission. + + ### Release instructions: * Ensure that the following commands are included in the `post_deployment` command: diff --git a/src/react/src/Routes.jsx b/src/react/src/Routes.jsx index 49d50cc6c..507edf89c 100644 --- a/src/react/src/Routes.jsx +++ b/src/react/src/Routes.jsx @@ -31,6 +31,7 @@ import ExternalRedirect from './components/ExternalRedirect'; import Facilities from './components/Facilities'; import ContributeProductionLocation from './components/Contribute/ContributeProductionLocation'; import SearchByOsIdResult from './components/Contribute/SearchByOsIdResult'; +import ProductionLocationInfo from './components/Contribute/ProductionLocationInfo'; import { sessionLogin } from './actions/auth'; import { fetchFeatureFlags } from './actions/featureFlags'; @@ -57,6 +58,7 @@ import { InfoPaths, contributeProductionLocationRoute, searchByOsIdResultRoute, + productionLocationInfoRoute, } from './util/constants'; class Routes extends Component { @@ -169,6 +171,12 @@ class Routes extends Component { path={searchByOsIdResultRoute} component={SearchByOsIdResult} /> + + { - dispatch(completeFetchFacilityProcessingTypeOptions(data)); - }) + .then(({ data }) => + dispatch(completeFetchFacilityProcessingTypeOptions(data)), + ) .catch(err => dispatch( logErrorAndDispatchFailure( @@ -309,9 +309,9 @@ export function fetchNumberOfWorkersOptions() { return apiRequest .get(makeGetNumberOfWorkersURL()) .then(({ data }) => mapDjangoChoiceTuplesValueToSelectOptions(data)) - .then(data => { - dispatch(completeFetchNumberOfWorkersTypeOptions(data)); - }) + .then(data => + dispatch(completeFetchNumberOfWorkersTypeOptions(data)), + ) .catch(err => dispatch( logErrorAndDispatchFailure( diff --git a/src/react/src/components/Contribute/InputErrorText.jsx b/src/react/src/components/Contribute/InputErrorText.jsx new file mode 100644 index 000000000..45bd955cf --- /dev/null +++ b/src/react/src/components/Contribute/InputErrorText.jsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { withStyles } from '@material-ui/core/styles'; +import Typography from '@material-ui/core/Typography'; +import ErrorOutlineIcon from '@material-ui/icons/ErrorOutline'; +import { inputErrorText } from '../../util/styles'; + +const InputErrorText = ({ classes, text = 'This field is required.' }) => ( + + + + {text} + + +); + +export default withStyles(inputErrorText)(InputErrorText); diff --git a/src/react/src/components/Contribute/ProductionLocationInfo.jsx b/src/react/src/components/Contribute/ProductionLocationInfo.jsx new file mode 100644 index 000000000..963358a39 --- /dev/null +++ b/src/react/src/components/Contribute/ProductionLocationInfo.jsx @@ -0,0 +1,523 @@ +import React, { useEffect, useState } from 'react'; +import { useLocation } from 'react-router-dom'; +import { withStyles } from '@material-ui/core/styles'; +import { func, object } from 'prop-types'; +import { connect } from 'react-redux'; +import Button from '@material-ui/core/Button'; +import Paper from '@material-ui/core/Paper'; +import TextField from '@material-ui/core/TextField'; +import Typography from '@material-ui/core/Typography'; +import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown'; +import ArrowDropUpIcon from '@material-ui/icons/ArrowDropUp'; +import StyledSelect from '../Filters/StyledSelect'; +import { productionLocationInfoStyles } from '../../util/styles'; +import { + countryOptionsPropType, + facilityProcessingTypeOptionsPropType, + numberOfWorkerOptionsPropType, +} from '../../util/propTypes'; +import { + fetchCountryOptions, + fetchFacilityProcessingTypeOptions, + fetchNumberOfWorkersOptions, +} from '../../actions/filterOptions'; +import InputErrorText from './InputErrorText'; +import { + mapDjangoChoiceTuplesToSelectOptions, + mapFacilityTypeOptions, + mapProcessingTypeOptions, +} from '../../util/util'; +import { mockedSectors } from '../../util/constants'; +import COLOURS from '../../util/COLOURS'; + +const ProductionLocationInfo = ({ + classes, + countriesOptions, + fetchCountries, + facilityProcessingTypeOptions, + fetchFacilityProcessingType, + numberOfWorkersOptions, + fetchNumberOfWorkers, +}) => { + const location = useLocation(); + + const queryParams = new URLSearchParams(location.search); + const nameInQuery = queryParams.get('name'); + const addressInQuery = queryParams.get('address'); + const countryInQuery = queryParams.get('country'); + const [isExpanded, setIsExpanded] = useState(false); + const [inputName, setInputName] = useState(nameInQuery ?? ''); + const [inputAddress, setInputAddress] = useState(addressInQuery ?? ''); + const [inputCountry, setInputCountry] = useState(null); + const [nameTouched, setNameTouched] = useState(false); + const [addressTouched, setAddressTouched] = useState(false); + const [sector, setSector] = useState(''); + const [productType, setProductType] = useState([]); + const [locationType, setLocationType] = useState(null); + const [processingType, setProcessingType] = useState(null); + const [numberOfWorkers, setNumberOfWorkers] = useState(null); + const [parentCompany, setParentCompany] = useState([]); + + const selectStyles = { + control: provided => ({ + ...provided, + height: '56px', + borderRadius: '0', + '&:focus,&:active,&:focus-within': { + borderColor: COLOURS.PURPLE, + boxShadow: `0 0 0 1px ${COLOURS.PURPLE}`, + }, + }), + }; + const validate = val => { + if (val) { + return val.length > 0; + } + return false; + }; + const toggleExpand = () => { + setIsExpanded(!isExpanded); + }; + const handleNameChange = event => { + setNameTouched(true); + setInputName(event.target.value); + }; + const handleAddressChange = event => { + setAddressTouched(true); + setInputAddress(event.target.value); + }; + const handleCountryChange = event => setInputCountry(event); + + useEffect(() => { + if (!countriesOptions) { + fetchCountries(); + } + }, [countriesOptions, fetchCountries]); + + useEffect(() => { + if (countriesOptions && validate(countryInQuery)) { + const prefilledCountry = countriesOptions.filter( + el => el.value === countryInQuery, + ); + + handleCountryChange(prefilledCountry[0]); + } + }, [countriesOptions]); + + useEffect(() => { + if (!facilityProcessingTypeOptions) { + fetchFacilityProcessingType(); + } + }, [facilityProcessingTypeOptions, fetchFacilityProcessingType]); + + useEffect(() => { + if (!numberOfWorkersOptions) { + fetchNumberOfWorkers(); + } + }, [numberOfWorkersOptions, fetchNumberOfWorkers]); + + return ( +
+ + Production Location Information + + + Use the form below to edit the name, address, and country for + your production location. These fields are pre-filled with the + data from your search, but you can edit them. + + +
+ + Location Name + + + Enter the name of the production location that you are + uploading. + + + } + FormHelperTextProps={{ + className: classes.helperText, + }} + error={nameTouched && !validate(inputName)} + /> +
+
+ + Address + + + Enter the address of the production location. We will + use this to plot the location on a map. + + + } + FormHelperTextProps={{ + className: classes.helperText, + }} + error={addressTouched && !validate(inputAddress)} + /> +
+
+ + Country + + + Select the country where the production site is located. + + +
+
+
+
+ + Additional information + +
+ {isExpanded ? ( + + ) : ( + + )} +
+
+ + Expand this section to add more data about your + production location, including product types, number of + workers, parent company and more. + + {isExpanded && ( + <> +
+ + Sector(s) + + + Select the sector(s) that this location + operates in. For example: Apparel, + Electronics, Renewable Energy. + + +
+
+ + Product Type(s) + + + Enter the type of products produced at this + location. For example: Shirts, Laptops, + Solar Panels. + + + +
+
+ + Location Type(s) + + + Select the location type(s) for this + production location. For example: Final + Product Assembly, Raw Materials Production + or Processing, Office/HQ. + + +
+
+ + Processing Type(s) + + + Select the type of processing activities + that take place at this location. For + example: Printing, Tooling, Assembly. + + +
+
+ + Number of Workers + + + Select a number or a range for the number of + people employed at the location. For + example: 100, 100-150. + + +
+
+ + Parent Company + + + Enter the company that holds majority + ownership for this production. + + +
+ + )} +
+ +
+ + +
+
+
+ ); +}; + +ProductionLocationInfo.defaultProps = { + countriesOptions: null, + facilityProcessingTypeOptions: null, + numberOfWorkersOptions: null, +}; + +ProductionLocationInfo.propTypes = { + countriesOptions: countryOptionsPropType, + fetchCountries: func.isRequired, + fetchFacilityProcessingType: func.isRequired, + facilityProcessingTypeOptions: facilityProcessingTypeOptionsPropType, + numberOfWorkersOptions: numberOfWorkerOptionsPropType, + classes: object.isRequired, +}; + +const mapStateToProps = ({ + filterOptions: { + countries: { data: countriesOptions }, + facilityProcessingType: { data: facilityProcessingTypeOptions }, + numberOfWorkers: { data: numberOfWorkersOptions }, + }, +}) => ({ + countriesOptions, + facilityProcessingTypeOptions, + numberOfWorkersOptions, +}); + +function mapDispatchToProps(dispatch) { + return { + fetchCountries: () => dispatch(fetchCountryOptions()), + fetchFacilityProcessingType: () => + dispatch(fetchFacilityProcessingTypeOptions()), + fetchNumberOfWorkers: () => dispatch(fetchNumberOfWorkersOptions()), + }; +} + +export default connect( + mapStateToProps, + mapDispatchToProps, +)(withStyles(productionLocationInfoStyles)(ProductionLocationInfo)); diff --git a/src/react/src/components/Contribute/SearchByNameAndAddressTab.jsx b/src/react/src/components/Contribute/SearchByNameAndAddressTab.jsx index d8aab5d42..895ed99af 100644 --- a/src/react/src/components/Contribute/SearchByNameAndAddressTab.jsx +++ b/src/react/src/components/Contribute/SearchByNameAndAddressTab.jsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { bool, string, func, object } from 'prop-types'; +import { bool, string, func, object, arrayOf } from 'prop-types'; import { useHistory } from 'react-router-dom'; import { withStyles } from '@material-ui/core/styles'; import { connect } from 'react-redux'; @@ -7,24 +7,16 @@ import Button from '@material-ui/core/Button'; import Paper from '@material-ui/core/Paper'; import TextField from '@material-ui/core/TextField'; import Typography from '@material-ui/core/Typography'; -import InfoOutlinedIcon from '@material-ui/icons/InfoOutlined'; import CircularProgress from '@material-ui/core/CircularProgress'; import StyledSelect from '../Filters/StyledSelect'; +import InputErrorText from './InputErrorText'; +import { productionLocationInfoRoute } from '../../util/constants'; import { makeSearchByNameAddressTabStyles } from '../../util/styles'; import { countryOptionsPropType } from '../../util/propTypes'; import { fetchCountryOptions } from '../../actions/filterOptions'; -const InputHelperText = ({ classes }) => ( - - - - This field is required. - - -); - const defaultCountryOption = { label: "What's the country?", value: '', @@ -65,7 +57,7 @@ const SearchByNameAndAddressTab = ({ }; const handleSearch = () => { - const baseUrl = '/contribute/production-location/search/'; + const baseUrl = productionLocationInfoRoute; const params = new URLSearchParams({ name: inputName, address: inputAddress, @@ -137,7 +129,7 @@ const SearchByNameAndAddressTab = ({ helperText={ nameTouched && !validate(inputName) && ( - + ) } error={nameTouched && !validate(inputName)} @@ -169,9 +161,7 @@ const SearchByNameAndAddressTab = ({ }} helperText={ addressTouched && - !validate(inputAddress) && ( - - ) + !validate(inputAddress) && } error={addressTouched && !validate(inputAddress)} /> @@ -217,7 +207,7 @@ SearchByNameAndAddressTab.defaultProps = { SearchByNameAndAddressTab.propTypes = { countriesData: countryOptionsPropType, fetching: bool.isRequired, - error: string, + error: arrayOf(string), fetchCountries: func.isRequired, classes: object.isRequired, }; diff --git a/src/react/src/components/FilterSidebarExtendedSearch.jsx b/src/react/src/components/FilterSidebarExtendedSearch.jsx index 3b0af5079..8268a73d2 100644 --- a/src/react/src/components/FilterSidebarExtendedSearch.jsx +++ b/src/react/src/components/FilterSidebarExtendedSearch.jsx @@ -2,7 +2,6 @@ import React, { useEffect } from 'react'; import { bool, func } from 'prop-types'; import { connect } from 'react-redux'; import CircularProgress from '@material-ui/core/CircularProgress'; -import uniq from 'lodash/uniq'; import ShowOnly from './ShowOnly'; import StyledSelect from './Filters/StyledSelect'; @@ -37,7 +36,8 @@ import { import { getValueFromEvent, - mapDjangoChoiceTuplesValueToSelectOptions, + mapProcessingTypeOptions, + mapFacilityTypeOptions, } from '../util/util'; const CONTRIBUTOR_TYPES = 'CONTRIBUTOR_TYPES'; @@ -47,42 +47,6 @@ const PROCESSING_TYPE = 'PROCESSING_TYPE'; const PRODUCT_TYPE = 'PRODUCT_TYPE'; const NUMBER_OF_WORKERS = 'NUMBER_OF_WORKERS'; -const mapFacilityTypeOptions = (fPTypes, pTypes) => { - let fTypes = []; - if (pTypes.length === 0) { - fTypes = fPTypes.map(type => type.facilityType); - } else { - // When there are processing types, only return the - // facility types that have those processing types - pTypes.forEach(pType => { - fPTypes.forEach(fPType => { - if (fPType.processingTypes.includes(pType.value)) { - fTypes = fTypes.concat(fPType.facilityType); - } - }); - }); - } - return mapDjangoChoiceTuplesValueToSelectOptions(uniq(fTypes.sort())); -}; - -const mapProcessingTypeOptions = (fPTypes, fTypes) => { - let pTypes = []; - if (fTypes.length === 0) { - pTypes = fPTypes.map(type => type.processingTypes).flat(); - } else { - // When there are facility types, only return the - // processing types that are under those facility types - fTypes.forEach(fType => { - fPTypes.forEach(fPType => { - if (fType.value === fPType.facilityType) { - pTypes = pTypes.concat(fPType.processingTypes); - } - }); - }); - } - return mapDjangoChoiceTuplesValueToSelectOptions(uniq(pTypes.sort())); -}; - const isExtendedFieldForThisContributor = (field, extendedFields) => extendedFields.includes(field.toLowerCase()); diff --git a/src/react/src/util/COLOURS.js b/src/react/src/util/COLOURS.js index 7d6baf494..1e624a73b 100644 --- a/src/react/src/util/COLOURS.js +++ b/src/react/src/util/COLOURS.js @@ -25,4 +25,6 @@ export default { NAVIGATION: '#FCCF3F', PALE_LIGHT_YELLOW: '#FFF2CE', ACCENT_GREY: '#E7E8EA', + + PURPLE: '#8428FA', }; diff --git a/src/react/src/util/constants.jsx b/src/react/src/util/constants.jsx index b84170557..779b49a75 100644 --- a/src/react/src/util/constants.jsx +++ b/src/react/src/util/constants.jsx @@ -351,6 +351,8 @@ export const aboutClaimedFacilitiesRoute = `${InfoLink}/${InfoPaths.claimedFacil export const contributeProductionLocationRoute = '/contribute/production-location'; export const searchByOsIdResultRoute = '/contribute/production-location/search'; +export const productionLocationInfoRoute = + '/contribute/production-location/info'; export const contributeFieldsEnum = Object.freeze({ name: 'name', @@ -1368,3 +1370,137 @@ export const MODERATION_DEFAULT_ROWS_PER_PAGE = 25; export const MAINTENANCE_MESSAGE = 'Open Supply Hub is undergoing maintenance and not accepting new data at the moment. Please try again in a few minutes.'; + +export const mockedSectors = [ + ['Electronics', 'Electronics'], + ['Accommodation', 'Accommodation'], + ['Aerospace', 'Aerospace'], + ['Agriculture', 'Agriculture'], + ['Air Transportation', 'Air Transportation'], + ['Allied Products', 'Allied Products'], + ['Animal Production', 'Animal Production'], + ['Apparel', 'Apparel'], + ['Apparel Accessories', 'Apparel Accessories'], + ['Appliances', 'Appliances'], + ['Aquaculture', 'Aquaculture'], + ['Archives', 'Archives'], + ['Arts', 'Arts'], + ['Arts & Entertainment', 'Arts & Entertainment'], + ['Automotive', 'Automotive'], + ['Automotive Parts', 'Automotive Parts'], + ['Banking', 'Banking'], + ['Beauty Products', 'Beauty Products'], + ['Beverages', 'Beverages'], + ['Biotechnology', 'Biotechnology'], + ['Books', 'Books'], + ['Building Construction', 'Building Construction'], + ['Building Materials', 'Building Materials'], + ['Chemicals', 'Chemicals'], + ['Civics', 'Civics'], + ['Civil Engineering Construction', 'Civil Engineering Construction'], + ['Coal', 'Coal'], + ['Commodities', 'Commodities'], + ['Components', 'Components'], + ['Computers', 'Computers'], + ['Computing Infrastructure', 'Computing Infrastructure'], + ['Construction', 'Construction'], + ['Consumer Products', 'Consumer Products'], + ['Crop Production', 'Crop Production'], + ['Durable Goods', 'Durable Goods'], + ['Educational Services', 'Educational Services'], + ['Electrical Devices', 'Electrical Devices'], + ['Electricity', 'Electricity'], + ['Electronic Product Manufacturing', 'Electronic Product Manufacturing'], + ['Energy', 'Energy'], + ['Energy Production & Utilities', 'Energy Production & Utilities'], + ['Entertainment', 'Entertainment'], + ['Equipment', 'Equipment'], + ['Farming', 'Farming'], + ['Finance', 'Finance'], + ['Financial Services', 'Financial Services'], + ['Fishing', 'Fishing'], + ['Food', 'Food'], + ['Food & Beverage', 'Food & Beverage'], + ['Food Industry', 'Food Industry'], + ['Food Manufacturing', 'Food Manufacturing'], + ['Footwear', 'Footwear'], + ['Forestry', 'Forestry'], + ['Furniture', 'Furniture'], + ['Garden Tools', 'Garden Tools'], + ['Gas', 'Gas'], + ['General Merchandise', 'General Merchandise'], + ['Ground Passenger Transportation', 'Ground Passenger Transportation'], + ['Hard Goods', 'Hard Goods'], + ['Health', 'Health'], + ['Healthcare', 'Healthcare'], + ['Hobby', 'Hobby'], + ['Home Accessories', 'Home Accessories'], + ['Home Furnishings', 'Home Furnishings'], + ['Hospitals', 'Hospitals'], + ['Home Textiles', 'Home Textiles'], + ['Hunting', 'Hunting'], + ['Information', 'Information'], + ['International Affairs', 'International Affairs'], + ['Jewelry', 'Jewelry'], + ['Leather', 'Leather'], + ['Logging', 'Logging'], + ['Machinery Manufacturing', 'Machinery Manufacturing'], + ['Maintenance', 'Maintenance'], + ['Manufacturing', 'Manufacturing'], + ['Material Production', 'Material Production'], + ['Medical Equipment & Services', 'Medical Equipment & Services'], + ['Merchant Wholesalers', 'Merchant Wholesalers'], + ['Metal Manufacturing', 'Metal Manufacturing'], + ['Mining', 'Mining'], + ['Multi-Category', 'Multi-Category'], + ['Musical Instruments', 'Musical Instruments'], + ['Nondurable Goods', 'Nondurable Goods'], + ['Nursing', 'Nursing'], + ['Oil & Gas', 'Oil & Gas'], + ['Paper Products', 'Paper Products'], + ['Parts Dealers', 'Parts Dealers'], + ['Personal Care Products', 'Personal Care Products'], + ['Pharmaceuticals', 'Pharmaceuticals'], + ['Pipeline Transportation', 'Pipeline Transportation'], + ['Plastics', 'Plastics'], + ['Printing', 'Printing'], + ['Professional Services', 'Professional Services'], + ['Quarrying', 'Quarrying'], + ['Rail Transportation', 'Rail Transportation'], + ['Recreation', 'Recreation'], + ['Renewable Energy', 'Renewable Energy'], + ['Renting', 'Renting'], + ['Repair', 'Repair'], + ['Rubber Products', 'Rubber Products'], + ['Solar Energy', 'Solar Energy'], + ['Research', 'Research'], + ['Specialty Trade Contractors', 'Specialty Trade Contractors'], + ['Sports Equipment', 'Sports Equipment'], + ['Sporting Goods', 'Sporting Goods'], + ['Storage', 'Storage'], + ['Supplies Dealers', 'Supplies Dealers'], + ['Technical Services', 'Technical Services'], + ['Technology', 'Technology'], + ['Telecommunications', 'Telecommunications'], + ['Textiles', 'Textiles'], + ['Tobacco Products', 'Tobacco Products'], + ['Toys', 'Toys'], + ['Transportation Equipment', 'Transportation Equipment'], + ['Trucking', 'Trucking'], + ['Utilities', 'Utilities'], + ['Water Utilities', 'Water Utilities'], + ['Warehousing', 'Warehousing'], + ['Wholesale Trade', 'Wholesale Trade'], + ['Wood Products', 'Wood Products'], + ['Consumer Electronics', 'Consumer Electronics'], + ['Home', 'Home'], + ['Maritime Transportation', 'Maritime Transportation'], + [ + 'Technical and Scientific Activities', + 'Technical and Scientific Activities', + ], + ['Waste Management', 'Waste Management'], + ['Recycling', 'Recycling'], + ['Pets', 'Pets'], + ['Packaging', 'Packaging'], +]; diff --git a/src/react/src/util/styles.js b/src/react/src/util/styles.js index de55be8a4..4566ddfce 100644 --- a/src/react/src/util/styles.js +++ b/src/react/src/util/styles.js @@ -914,20 +914,6 @@ export const makeSearchByNameAddressTabStyles = theme => errorPlaceholder: Object.freeze({ color: COLOURS.RED, }), - helperTextWrapStyles: Object.freeze({ - display: 'flex', - alignItems: 'center', - gap: '4px', - }), - iconInfoStyles: Object.freeze({ - fontSize: '16px', - verticalAlign: 'middle', - }), - inputHelperTextStyles: Object.freeze({ - fontSize: '16px', - fontWeight: theme.typography.fontWeightSemiBold, - color: COLOURS.RED, - }), instructionStyles: Object.freeze({ fontSize: '18px', fontWeight: theme.typography.fontWeightSemiBold, @@ -989,3 +975,126 @@ export const makeSearchByNameAddressTabStyles = theme => }, }), }); + +export const productionLocationInfoStyles = theme => + Object.freeze({ + helperText: Object.freeze({ + marginLeft: '0', + }), + errorStyle: Object.freeze({ + color: COLOURS.RED, + }), + rowContainerStyles: Object.freeze({ + display: 'flex', + flexDirection: 'row', + width: '100%', + alignItems: 'center', + }), + marginRight: Object.freeze({ + marginRight: '20px', + }), + buttonsContainerStyles: Object.freeze({ + display: 'flex', + flexDirection: 'row', + width: '100%', + justifyContent: 'center', + }), + selectStyles: Object.freeze({ + maxWidth: '528px', + }), + textInputStyles: Object.freeze({ + maxWidth: '528px', + borderRadius: '0', + }), + instructionStyles: Object.freeze({ + fontSize: '18px', + fontWeight: theme.typography.fontWeightSemiBold, + margin: '24px 0 32px 0', + maxWidth: '730px', + }), + inputSectionWrapStyles: Object.freeze({ + padding: '40px 0', + }), + wrapStyles: Object.freeze({ + display: 'inline-flex', + flexDirection: 'column', + }), + sectionWrapStyles: Object.freeze({ + padding: '24px 0 32px 0', + }), + infoWrapStyles: Object.freeze({ + display: 'flex', + flexDirection: 'column', + padding: '0 110px', + borderRadius: '0', + boxShadow: 'none', + }), + mainContainerStyles: Object.freeze({ + background: theme.palette.background.grey, + padding: '48px 5% 120px 5%', + }), + headerStyles: Object.freeze({ + fontWeight: theme.typography.fontWeightExtraBold, + fontSize: '56px', + lineHeight: '60px', + }), + titleStyles: Object.freeze({ + fontSize: '36px', + fontWeight: theme.typography.fontWeightSemiBoldPlus, + }), + subTitleStyles: Object.freeze({ + fontSize: '18px', + fontWeight: theme.typography.fontWeightSemiBold, + margin: '0 0 20px 0', + }), + separator: Object.freeze({ + margin: '1px 0', + color: COLOURS.GREY, + }), + goBackButtonStyles: Object.freeze({ + width: '200px', + height: '49px', + borderRadius: 0, + textTransform: 'none', + margin: '48px 20px 48px 0', + fontSize: '18px', + fontWeight: theme.typography.fontWeightExtraBold, + border: '1px solid #0D1128', + }), + submitButtonStyles: Object.freeze({ + width: '200px', + height: '49px', + borderRadius: 0, + textTransform: 'none', + backgroundColor: theme.palette.action.main, + margin: '48px 0', + color: theme.palette.common.black, + '&:hover': { + backgroundColor: theme.palette.action.dark, + }, + fontSize: '18px', + fontWeight: theme.typography.fontWeightExtraBold, + boxShadow: 'none', + }), + notchedOutlineStyles: Object.freeze({ + borderRadius: '0', + }), + }); + +export const inputErrorText = theme => + Object.freeze({ + errorTextWrapStyles: Object.freeze({ + display: 'flex', + alignItems: 'center', + }), + iconInfoStyles: Object.freeze({ + fontSize: '16px', + verticalAlign: 'middle', + marginRight: '5px', + }), + inputErrorTextStyles: Object.freeze({ + fontSize: '16px', + fontWeight: theme.typography.fontWeightSemiBold, + color: COLOURS.RED, + }), + }); diff --git a/src/react/src/util/util.js b/src/react/src/util/util.js index 313a102a8..ad1d7f392 100644 --- a/src/react/src/util/util.js +++ b/src/react/src/util/util.js @@ -735,6 +735,42 @@ const mapSingleChoiceValueToSelectOption = value => export const mapDjangoChoiceTuplesValueToSelectOptions = data => Object.freeze(data.map(mapSingleChoiceValueToSelectOption)); +export const mapProcessingTypeOptions = (fPTypes, fTypes) => { + let pTypes = []; + if (fTypes.length === 0) { + pTypes = fPTypes.map(type => type.processingTypes).flat(); + } else { + // When there are facility types, only return the + // processing types that are under those facility types + fTypes.forEach(fType => { + fPTypes.forEach(fPType => { + if (fType.value === fPType.facilityType) { + pTypes = pTypes.concat(fPType.processingTypes); + } + }); + }); + } + return mapDjangoChoiceTuplesValueToSelectOptions(uniq(pTypes.sort())); +}; + +export const mapFacilityTypeOptions = (fPTypes, pTypes) => { + let fTypes = []; + if (pTypes.length === 0) { + fTypes = fPTypes.map(type => type.facilityType); + } else { + // When there are processing types, only return the + // facility types that have those processing types + pTypes.forEach(pType => { + fPTypes.forEach(fPType => { + if (fPType.processingTypes.includes(pType.value)) { + fTypes = fTypes.concat(fPType.facilityType); + } + }); + }); + } + return mapDjangoChoiceTuplesValueToSelectOptions(uniq(fTypes.sort())); +}; + export const mapSectorGroupsToSelectOptions = data => Object.freeze( data.map(group => ({