diff --git a/doc/release/RELEASE-NOTES.md b/doc/release/RELEASE-NOTES.md index c18866c08..b1e2db421 100644 --- a/doc/release/RELEASE-NOTES.md +++ b/doc/release/RELEASE-NOTES.md @@ -3,12 +3,14 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). The format is based on the `RELEASE-NOTES-TEMPLATE.md` file. - ## Release 1.32.0 ## Introduction * Product name: Open Supply Hub * Release date: March 22, 2025 +### 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. + ### Bugfix * [OSDEV-1806](https://opensupplyhub.atlassian.net/browse/OSDEV-1806) - Refactored the Parent Company field validation. The field is now validated as a regular character field. * [OSDEV-1787](https://opensupplyhub.atlassian.net/browse/OSDEV-1787) - The tooltip messages for the Claim button have been removed for all statuses of moderation events on the `Contribution Record` page and changed according to the design on `Thanks for adding data for this production location` pop-up. diff --git a/src/react/src/__tests__/components/ProductionLocationInfo.test.js b/src/react/src/__tests__/components/ProductionLocationInfo.test.js index 83c3ec03b..e9c1b2736 100644 --- a/src/react/src/__tests__/components/ProductionLocationInfo.test.js +++ b/src/react/src/__tests__/components/ProductionLocationInfo.test.js @@ -1,6 +1,6 @@ import React from "react"; import { fireEvent } from "@testing-library/react"; -import { BrowserRouter as Router } from "react-router-dom"; +import { MemoryRouter, Route, BrowserRouter as Router } from "react-router-dom"; import ProductionLocationInfo from "../../components/Contribute/ProductionLocationInfo"; import renderWithProviders from "../../util/testUtils/renderWithProviders"; @@ -29,7 +29,7 @@ jest.mock("../../components/Filters/StyledSelect", () => (props) => { ); }); -describe("ProductionLocationInfo component", () => { +describe("ProductionLocationInfo component, test input fields for POST v1/production-locations", () => { const defaultState = { filterOptions: { countries: { @@ -89,8 +89,8 @@ describe("ProductionLocationInfo component", () => { expect(countrySelect).toBeInTheDocument(); expect(countrySelect).toHaveValue(""); - const iconButton = getByTestId("toggle-additional-info"); - expect(iconButton).toBeInTheDocument(); + const switchButton = getByTestId("switch-additional-info-fields"); + expect(switchButton).toBeInTheDocument(); }); test("displays error (and disables submit) when required fields are empty after blur", () => { @@ -135,8 +135,8 @@ describe("ProductionLocationInfo component", () => { test("displays additional information form when icon button is clicked", () => { const { getByTestId, getByText, queryByText } = renderComponent(); - const iconButton = getByTestId("toggle-additional-info"); - fireEvent.click(iconButton); + const switchButton = getByTestId("switch-additional-info-fields"); + fireEvent.click(switchButton); expect(getByText("Sector(s)")).toBeInTheDocument(); expect(getByText("Select the sector(s) that this location operates in. For example: Apparel, Electronics, Renewable Energy.")).toBeInTheDocument(); @@ -151,7 +151,7 @@ describe("ProductionLocationInfo component", () => { expect(getByText("Parent Company")).toBeInTheDocument(); expect(getByText("Enter the company that holds majority ownership for this production.")).toBeInTheDocument(); - fireEvent.click(iconButton); + fireEvent.click(switchButton); expect(queryByText("Sector(s)")).not.toBeInTheDocument(); expect(queryByText("Product Type(s)")).not.toBeInTheDocument(); @@ -177,28 +177,118 @@ describe("ProductionLocationInfo component", () => { expect(submitButton).toBeEnabled(); - const iconButton = getByTestId("toggle-additional-info"); - fireEvent.click(iconButton); + const switchButton = getByTestId("switch-additional-info-fields"); + expect(switchButton).not.toBeChecked(); - const numberInput = getByPlaceholderText("Enter the number of workers as a number or range"); - fireEvent.change(numberInput, { target: { value: "Test" } }); + fireEvent.click(switchButton); + expect(switchButton).toBeChecked(); + + const numberOfWorkersInput = getByPlaceholderText("Enter the number of workers as a number or range"); + fireEvent.change(numberOfWorkersInput, { target: { value: "Test" } }); expect(getByPlaceholderText("Enter the number of workers as a number or range")).toHaveAttribute("aria-invalid", "true"); expect(submitButton).toBeDisabled(); - fireEvent.change(numberInput, { target: { value: "100" } }); + fireEvent.change(numberOfWorkersInput, { target: { value: "100" } }); expect(getByPlaceholderText("Enter the number of workers as a number or range")).toHaveAttribute("aria-invalid", "false"); expect(submitButton).toBeEnabled(); - fireEvent.change(numberInput, { target: { value: "100-150" } }); + fireEvent.change(numberOfWorkersInput, { target: { value: "100-150" } }); expect(getByPlaceholderText("Enter the number of workers as a number or range")).toHaveAttribute("aria-invalid", "false"); expect(submitButton).toBeEnabled(); - fireEvent.change(numberInput, { target: { value: "200-100" } }); + fireEvent.change(numberOfWorkersInput, { target: { value: "200-100" } }); expect(getByPlaceholderText("Enter the number of workers as a number or range")).toHaveAttribute("aria-invalid", "true"); expect(submitButton).toBeDisabled(); }); }); + +describe("ProductionLocationInfo component, test invalid incoming data for UPDATE v1/production-locations", () => { + const osID = 'GR2019098DC1P4A'; + const defaultState = { + auth: { + user: { user: { isAnon: false } }, + session: { fetching: false }, + }, + contributeProductionLocation: { + singleProductionLocation: { + data: { + processing_type: ['Apparel'], + name: 'Modelina', + coordinates: { + lat: 40.6875863, + lng: 22.9389083 + }, + os_id: osID, + location_type: ['Apparel'], + country: { + name: 'Greece', + numeric: '300', + alpha_3: 'GRC', + alpha_2: 'GR' + }, + address: '1 Agiou Petrou Street, Oreokastrou, Thessaloniki, 56430', + claim_status: 'unclaimed', + sector: ['Apparel'], + number_of_workers: { + max: 150, + min: 0 + }, + product_type: ['Accessories'] + }, + fetching: false, + error: null + }, + productionLocations: { + data: [], + fetching: false, + error: null + }, + pendingModerationEvent: { + data: {}, + fetching: false, + error: null + } + }, + }; + + const defaultProps = { + submitMethod: "UPDATE", + }; + + const renderComponent = (props = {}) => + renderWithProviders( + + } + /> + , + { preloadedState: defaultState }, + ) + + 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(); + + const updateButton = getByRole("button", { name: /Update/i }); + expect(updateButton).toBeEnabled(); + + const switchButton = getByTestId("switch-additional-info-fields"); + fireEvent.click(switchButton); + + 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(updateButton).toBeDisabled(); + + fireEvent.click(switchButton); + expect(queryByText("Enter the number of workers as a number or range")).not.toBeInTheDocument(); + expect(updateButton).toBeEnabled(); + }); +}); diff --git a/src/react/src/components/Contribute/ProductionLocationInfo.jsx b/src/react/src/components/Contribute/ProductionLocationInfo.jsx index fdbe76ed1..49e4bcaf8 100644 --- a/src/react/src/components/Contribute/ProductionLocationInfo.jsx +++ b/src/react/src/components/Contribute/ProductionLocationInfo.jsx @@ -8,11 +8,9 @@ import { endsWith, isEmpty, toString } from 'lodash'; import Button from '@material-ui/core/Button'; import CircularProgress from '@material-ui/core/CircularProgress'; import Paper from '@material-ui/core/Paper'; +import Switch from '@material-ui/core/Switch'; import TextField from '@material-ui/core/TextField'; -import IconButton from '@material-ui/core/IconButton'; import Typography from '@material-ui/core/Typography'; -import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown'; -import ArrowDropUpIcon from '@material-ui/icons/ArrowDropUp'; import RequireAuthNotice from '../RequireAuthNotice'; import StyledSelect from '../Filters/StyledSelect'; import RequiredAsterisk from '../RequiredAsterisk'; @@ -92,7 +90,6 @@ const ProductionLocationInfo = ({ 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); @@ -108,6 +105,39 @@ const ProductionLocationInfo = ({ const customSelectComponents = { DropdownIndicator: null }; const isCountryError = countryTouched && !inputCountry?.value; + const fillAdditionalDataFields = () => { + setNumberOfWorkers( + convertRangeField(singleProductionLocationData.number_of_workers) ?? + '', + ); + updateStateFromData(singleProductionLocationData, 'sector', setSector); + updateStateFromData( + singleProductionLocationData, + 'product_type', + setProductType, + ); + updateStateFromData( + singleProductionLocationData, + 'location_type', + setLocationType, + ); + updateStateFromData( + singleProductionLocationData, + 'processing_type', + setProcessingType, + ); + setParentCompany(singleProductionLocationData.parent_company ?? ''); + }; + + const resetAdditionalDataFields = () => { + setSector(''); + setProductType([]); + setLocationType(null); + setProcessingType(null); + setNumberOfWorkers(''); + setParentCompany(''); + }; + useEffect(() => { window.scrollTo(0, 0); }, [location]); @@ -142,9 +172,6 @@ const ProductionLocationInfo = ({ setShowProductionLocationDialog, ] = useState(null); - const toggleExpand = () => { - setIsExpanded(!isExpanded); - }; const handleNameChange = event => { setInputName(event.target.value); }; @@ -194,6 +221,19 @@ const ProductionLocationInfo = ({ : ''; const submitButtonText = submitMethod === 'POST' ? 'Submit' : 'Update'; + const [showAdditionalInfo, setShowAdditionalInfo] = useState(false); + const onSwitchChange = () => { + setShowAdditionalInfo(prevShowAdditionalInfo => { + const newShowAdditionalInfo = !prevShowAdditionalInfo; + if (newShowAdditionalInfo) { + fillAdditionalDataFields(); + } else { + resetAdditionalDataFields(); + } + return newShowAdditionalInfo; + }); + }; + useEffect(() => { if (submitMethod === 'PATCH' && osID) { fetchProductionLocation(osID); @@ -222,38 +262,12 @@ const ProductionLocationInfo = ({ if (singleProductionLocationData && osID) { setInputName(singleProductionLocationData.name ?? ''); setInputAddress(singleProductionLocationData.address ?? ''); - setNumberOfWorkers( - convertRangeField( - singleProductionLocationData.number_of_workers, - ) ?? '', - ); if (singleProductionLocationData.country) { setInputCountry({ value: singleProductionLocationData?.country.alpha_2, label: singleProductionLocationData?.country.name, }); } - updateStateFromData( - singleProductionLocationData, - 'sector', - setSector, - ); - updateStateFromData( - singleProductionLocationData, - 'product_type', - setProductType, - ); - updateStateFromData( - singleProductionLocationData, - 'location_type', - setLocationType, - ); - updateStateFromData( - singleProductionLocationData, - 'processing_type', - setProcessingType, - ); - setParentCompany(singleProductionLocationData.parent_company ?? ''); } }, [singleProductionLocationData, osID]); @@ -572,16 +586,17 @@ const ProductionLocationInfo = ({ > Additional information - - {isExpanded ? ( - - ) : ( - - )} - + - {isExpanded && ( + {showAdditionalInfo && ( <>
width: '100%', alignItems: 'center', }), - marginRight: Object.freeze({ - marginRight: '20px', - }), buttonsContainerStyles: Object.freeze({ display: 'flex', flexDirection: 'row', width: '100%', justifyContent: 'center', }), + switchButton: Object.freeze({ + marginTop: '10px', + }), selectStyles: Object.freeze({ maxWidth: '528px', }),