Skip to content
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
1 change: 1 addition & 0 deletions doc/release/RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ Also was added sanitization on the server side by using the `Django-Bleach` libr
* [OSDEV-1840](https://opensupplyhub.atlassian.net/browse/OSDEV-1840) - Fixed the snapshot status checking procedure. This will prevent a crash when trying to restore a database from an inaccessible snapshot.
* [OSDEV-1831](https://opensupplyhub.atlassian.net/browse/OSDEV-1831) - Updated copies of tooltips on the “Thank you for adding data” pop-up. The texts vary depending on the claim status for a particular location.
* [OSDEV-1827](https://opensupplyhub.atlassian.net/browse/OSDEV-1827) - Fixed the condition logic for the email template when approving a contribution to an existing production location that has either been claimed or has a pending claim request.
* [OSDEV-1781](https://opensupplyhub.atlassian.net/browse/OSDEV-1781) - A clear error messages for the number of workers field have been added to the SLC form and Claimed Facility Details page.

### What's new
* [OSDEV-1814](https://opensupplyhub.atlassian.net/browse/OSDEV-1814) - Added toggle switch button for production location info page to render additional data if necessary. If toggle switch button is inactive (default behavior), additional data won't be send to the server along with name, address and country.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import { MemoryRouter, Route, BrowserRouter as Router } from "react-router-dom";
import ProductionLocationInfo from "../../components/Contribute/ProductionLocationInfo";
import renderWithProviders from "../../util/testUtils/renderWithProviders";

beforeAll(() => {
window.scrollTo = jest.fn();
});

jest.mock("../../components/Filters/StyledSelect", () => (props) => {
const { options = [], value, onChange, onBlur, placeholder } = props;
return (
Expand Down Expand Up @@ -262,7 +266,7 @@ describe("ProductionLocationInfo component, test invalid incoming data for UPDAT
const renderComponent = (props = {}) =>
renderWithProviders(
<MemoryRouter initialEntries={[`/contribute/single-location/${osID}/info/`]}>
<Route
<Route
path="/contribute/single-location/:osID/info/"
component={() => <ProductionLocationInfo {...defaultProps} {...props} />}
/>
Expand All @@ -272,8 +276,7 @@ describe("ProductionLocationInfo component, test invalid incoming data for UPDAT

test("update button should be enabled when number of workers invalid but additional info is hidden", () => {
const { getByRole, getByText, getByTestId, getByPlaceholderText, queryByText } = renderComponent();

expect(queryByText("Enter the number of workers as a number or range")).not.toBeInTheDocument();
expect(queryByText("The value of zero is not valid. Enter a positive whole number or a valid range (e.g., 1-5).")).not.toBeInTheDocument();

const updateButton = getByRole("button", { name: /Update/i });
expect(updateButton).toBeEnabled();
Expand All @@ -283,7 +286,7 @@ describe("ProductionLocationInfo component, test invalid incoming data for UPDAT

const numberOfWorkersInput = getByPlaceholderText("Enter the number of workers as a number or range");
expect(numberOfWorkersInput).toHaveAttribute("aria-invalid", "true");
expect(getByText("Enter the number of workers as a number or range")).toBeInTheDocument();
expect(getByText("The value of zero is not valid. Enter a positive whole number or a valid range (e.g., 1-5).")).toBeInTheDocument();

expect(updateButton).toBeDisabled();

Expand Down
43 changes: 37 additions & 6 deletions src/react/src/__tests__/utils.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ const {
parseContribData,
isRequiredFieldValid,
getSelectStyles,
getNumberOfWorkersValidationError,
isValidNumberOfWorkers,
} = require('../util/util');

const {
Expand Down Expand Up @@ -2192,7 +2194,7 @@ describe('getSelectStyles', () => {
boxShadow: 'none',
color: 'blue',
};

const stateFocused = { isFocused: true };
const stateNotFocused = { isFocused: false };

Expand All @@ -2208,36 +2210,65 @@ describe('getSelectStyles', () => {
expect(controlStyles.borderColor).toBe(COLOURS.PURPLE);
expect(controlStyles.boxShadow).toBe(`inset 0 0 0 1px ${COLOURS.PURPLE}`);
});

it('applies RED border when error state is true', () => {
const styles = getSelectStyles(true);
const controlStyles = styles.control(provided, stateFocused);
expect(controlStyles.borderColor).toBe(COLOURS.RED);
});

it('applies correct placeholder style when error state is true', () => {
const styles = getSelectStyles(true);
const placeholderStyles = styles.placeholder(provided);
expect(placeholderStyles.opacity).toBe(0.7);
expect(placeholderStyles.color).toBe(COLOURS.RED);
});

it('uses the provided color for placeholder when error state is false', () => {
const styles = getSelectStyles();
const placeholderStyles = styles.placeholder(provided);
expect(placeholderStyles.opacity).toBe(0.7);
expect(placeholderStyles.color).toBe(provided.color);
});

it('sets hover borderColor to "black" when not focused and no error', () => {
const styles = getSelectStyles();
const controlStyles = styles.control(provided, stateNotFocused);
expect(controlStyles['&:hover']).toEqual({ borderColor: 'black' });
});

it('sets hover borderColor to false when error state is true', () => {
const styles = getSelectStyles(true);
const controlStyles = styles.control(provided, stateNotFocused);
expect(controlStyles['&:hover']).toEqual({ borderColor: false });
});
});

describe('getNumberOfWorkersValidationError', () => {
it('clear error messages for number of workers field', () => {
const expectedValueOfZeroText =
'The value of zero is not valid. Enter a positive whole number or a valid range (e.g., 1-5).';
const expectedLessThenOrEqualText =
'Invalid range. The minimum value must be less than or equal to the maximum value.';
const expectedInvalidEntryText = 'Invalid entry. The value cannot start from zero.';
const expectedInvalidFormatText =
'Invalid format. Enter a whole number or a valid numeric range (e.g., 1-5).';

expect(getNumberOfWorkersValidationError('0')).toBe(expectedValueOfZeroText);
expect(getNumberOfWorkersValidationError('0-3')).toBe(expectedValueOfZeroText);
expect(getNumberOfWorkersValidationError('1-0')).toBe(expectedValueOfZeroText);
expect(getNumberOfWorkersValidationError('500-300')).toBe(expectedLessThenOrEqualText);
expect(getNumberOfWorkersValidationError('010')).toBe(expectedInvalidEntryText);
expect(getNumberOfWorkersValidationError('1-')).toBe(expectedInvalidFormatText);
expect(getNumberOfWorkersValidationError('some text or &$*_')).toBe(expectedInvalidFormatText);
expect(getNumberOfWorkersValidationError('3.9')).toBe(expectedInvalidFormatText);
});

it('valid numberOfWorkers has no errors, invalid shows error message', () => {
const expectedValueOfZeroText =
'The value of zero is not valid. Enter a positive whole number or a valid range (e.g., 1-5).';

expect(!isValidNumberOfWorkers('100') && getNumberOfWorkersValidationError('100')).toBe(false);
expect(!isValidNumberOfWorkers('0-300') && getNumberOfWorkersValidationError('0-300')).toBe(expectedValueOfZeroText);
});
})
41 changes: 36 additions & 5 deletions src/react/src/components/ClaimFacilityAdditionalData.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useEffect } from 'react';
import { bool, func, string, PropTypes } from 'prop-types';
import { bool, func, string, PropTypes, object } from 'prop-types';
import { connect } from 'react-redux';
import { withStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import TextField from '@material-ui/core/TextField';
import InputLabel from '@material-ui/core/InputLabel';
Expand Down Expand Up @@ -29,13 +30,20 @@ import { fetchSectorOptions } from '../actions/filterOptions';

import { sectorOptionsPropType } from '../util/propTypes';

import { getValueFromEvent, isValidNumberOfWorkers } from '../util/util';

import { claimAFacilitySupportDocsFormStyles } from '../util/styles';
import {
getValueFromEvent,
isValidNumberOfWorkers,
getNumberOfWorkersValidationError,
} from '../util/util';
import {
claimAFacilitySupportDocsFormStyles,
textFieldErrorStyles,
} from '../util/styles';

import { claimAFacilityAdditionalDataFormFields } from '../util/constants';

import COLOURS from '../util/COLOURS';
import InputErrorText from './Contribute/InputErrorText';

const infoTitleStyle = Object.freeze({
paddingBottom: '10px',
Expand Down Expand Up @@ -245,6 +253,7 @@ function ClaimFacilityAdditionalData({
sectorOptions,
fetchSectors,
fetching,
classes,
}) {
useEffect(() => {
if (!sectorOptions) {
Expand Down Expand Up @@ -291,6 +300,27 @@ function ClaimFacilityAdditionalData({
placeholder={numberOfWorkersForm.placeholder}
onChange={updateNumberOfWorkers}
disabled={fetching}
helperText={
!isValidNumberOfWorkers(numberOfWorkers) && (
<InputErrorText
text={getNumberOfWorkersValidationError(
numberOfWorkers,
)}
/>
)
}
FormHelperTextProps={{
className: classes.helperText,
}}
InputProps={{
classes: {
input: `
${
!isValidNumberOfWorkers(numberOfWorkers) &&
classes.errorStyle
}`,
},
}}
/>
</div>
<div style={claimAFacilitySupportDocsFormStyles.inputGroupStyles}>
Expand Down Expand Up @@ -330,6 +360,7 @@ ClaimFacilityAdditionalData.propTypes = {
sectorOptions: sectorOptionsPropType,
fetchSectors: func.isRequired,
fetching: bool.isRequired,
classes: object.isRequired,
};

function mapStateToProps({
Expand Down Expand Up @@ -368,4 +399,4 @@ function mapDispatchToProps(dispatch) {
export default connect(
mapStateToProps,
mapDispatchToProps,
)(ClaimFacilityAdditionalData);
)(withStyles(textFieldErrorStyles)(ClaimFacilityAdditionalData));
58 changes: 52 additions & 6 deletions src/react/src/components/ClaimedFacilitiesDetails.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import { arrayOf, bool, func, string } from 'prop-types';
import { arrayOf, bool, func, string, object } from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import CircularProgress from '@material-ui/core/CircularProgress';
import Typography from '@material-ui/core/Typography';
import TextField from '@material-ui/core/TextField';
Expand Down Expand Up @@ -28,6 +29,8 @@ import ShowOnly from './ShowOnly';
import CreatableInputOnly from './CreatableInputOnly';

import COLOURS from '../util/COLOURS';
import InputErrorText from './Contribute/InputErrorText';
import { textFieldErrorStyles } from '../util/styles';

import {
fetchClaimedFacilityDetails,
Expand Down Expand Up @@ -81,6 +84,7 @@ import {
makeClaimGeocoderURL,
logErrorToRollbar,
isValidNumberOfWorkers,
getNumberOfWorkersValidationError,
} from '../util/util';

import {
Expand Down Expand Up @@ -178,6 +182,8 @@ const InputSection = ({
hasValidationErrorFn = stubFalse,
aside = null,
selectPlaceholder = 'Select...',
helperText = '',
formHelperTextProps = {},
}) => {
let SelectComponent = null;

Expand Down Expand Up @@ -277,11 +283,23 @@ const InputSection = ({
onChange={onChange}
disabled={disabled}
error={hasValidationErrorFn()}
helperText={helperText}
FormHelperTextProps={formHelperTextProps}
/>
</div>
);
};

InputSection.defaultProps = {
helperText: '',
formHelperTextProps: {},
};

InputSection.propTypes = {
helperText: string,
formHelperTextProps: object,
};

const createCountrySelectOptions = memoize(
mapDjangoChoiceTuplesToSelectOptions,
);
Expand Down Expand Up @@ -328,6 +346,7 @@ function ClaimedFacilitiesDetails({
parentCompanyOptions,
fetchSectors,
fetchParentCompanies,
classes,
}) {
/* eslint-disable react-hooks/exhaustive-deps */
// disabled because we want to use this as just
Expand Down Expand Up @@ -523,14 +542,40 @@ function ClaimedFacilitiesDetails({
onChange={updateFacilityAverageLeadTime}
disabled={updating}
/>
<InputSection
label="Number of workers"
<Typography component="h2" className={classes.titleStyles}>
Number of Workers
</Typography>
<TextField
variant="outlined"
className={classes.textInputStyles}
value={data.facility_workers_count}
onChange={updateFacilityWorkersCount}
disabled={updating}
hasValidationErrorFn={() =>
!isValidNumberOfWorkers(data.facility_workers_count)
error={!isValidNumberOfWorkers(data.facility_workers_count)}
helperText={
!isValidNumberOfWorkers(
data.facility_workers_count,
) && (
<InputErrorText
text={getNumberOfWorkersValidationError(
data.facility_workers_count,
)}
/>
)
}
FormHelperTextProps={{
className: classes.helperText,
}}
InputProps={{
classes: {
input: `
${
!isValidNumberOfWorkers(
data.facility_workers_count,
) && classes.errorStyle
}`,
},
}}
/>
<InputSection
label="Percentage of female workers"
Expand Down Expand Up @@ -756,6 +801,7 @@ ClaimedFacilitiesDetails.propTypes = {
sectorOptions: sectorOptionsPropType,
parentCompanyOptions: parentCompanyOptionsPropType,
fetchSectors: func.isRequired,
classes: object.isRequired,
};

function mapStateToProps({
Expand Down Expand Up @@ -888,4 +934,4 @@ function mapDispatchToProps(
export default connect(
mapStateToProps,
mapDispatchToProps,
)(ClaimedFacilitiesDetails);
)(withStyles(textFieldErrorStyles)(ClaimedFacilitiesDetails));
11 changes: 9 additions & 2 deletions src/react/src/components/Contribute/ProductionLocationInfo.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import {
convertRangeField,
updateStateFromData,
getSelectStyles,
getNumberOfWorkersValidationError,
} from '../../util/util';
import {
mockedSectors,
Expand Down Expand Up @@ -762,14 +763,20 @@ const ProductionLocationInfo = ({
className={classes.textInputStyles}
value={numberOfWorkers}
onChange={e =>
setNumberOfWorkers(e.target.value)
setNumberOfWorkers(
e.target.value.trim(),
)
}
placeholder="Enter the number of workers as a number or range"
helperText={
!isValidNumberOfWorkers(
numberOfWorkers,
) && (
<InputErrorText text="Enter the number of workers as a number or range" />
<InputErrorText
text={getNumberOfWorkersValidationError(
numberOfWorkers,
)}
/>
)
}
FormHelperTextProps={{
Expand Down
19 changes: 19 additions & 0 deletions src/react/src/util/styles.js
Original file line number Diff line number Diff line change
Expand Up @@ -1602,3 +1602,22 @@ export const makeRejectModerationEventDialogStyles = () =>
width: '150px',
}),
});

export const textFieldErrorStyles = () =>
Object.freeze({
helperText: Object.freeze({ marginLeft: '0' }),
errorStyle: Object.freeze({ color: 'red' }),
textInputStyles: Object.freeze({
width: '100%',
borderRadius: '4px',
}),
titleStyles: Object.freeze({
fontSize: '18px',
fontWeight: '400',
padding: '10px 0px',
color: 'rgb(0, 0, 0)',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
}),
});
Loading