From aba3ceff574dc11b40acd48658c78596257fcf84 Mon Sep 17 00:00:00 2001 From: David Graham Date: Mon, 8 Jan 2024 09:09:08 -0500 Subject: [PATCH] Move logic for Handler search form to our central redux state, and add a custom hook (useHandlerSearchConfigs) to interface with the logic. This commit also stores generator and TSDF epa ID in url search parameter for draft manifests --- .../Generator/GeneratorSection.spec.tsx | 25 ++++++++ .../Manifest/Generator/GeneratorSection.tsx | 59 +++++++++++++++---- .../Handler/Search/HandlerSearchForm.tsx | 12 +++- .../HandlerSearchModal.tsx} | 55 ++++++++--------- .../src/components/Manifest/Handler/index.ts | 4 +- .../src/components/Manifest/ManifestForm.tsx | 29 ++------- .../Transporter/TransporterSection.tsx | 18 ++---- .../Transporter/TransporterTable.spec.tsx | 3 +- .../Manifest/Tsdf/TsdfSection.spec.tsx | 25 ++++++++ .../components/Manifest/Tsdf/TsdfSection.tsx | 54 +++++++++++++++-- .../ManifestDetails/ManifestDetails.tsx | 2 + .../src/features/NewManifest/NewManifest.tsx | 10 +++- .../useHandlerSearchConfig.tsx | 31 ++++++++++ .../useOpenHandlerSearch.spec.tsx | 48 +++++++++++++++ .../manifest/useReadOnly/useReadOnly.tsx | 6 +- client/src/store/htApi.slice.ts | 8 ++- client/src/store/index.ts | 1 + .../manifestSlice/manifest.slice.spec.tsx | 12 ++++ .../src/store/manifestSlice/manifest.slice.ts | 21 +++++-- server/apps/site/urls.py | 2 +- server/apps/site/views.py | 6 +- 21 files changed, 326 insertions(+), 105 deletions(-) create mode 100644 client/src/components/Manifest/Generator/GeneratorSection.spec.tsx rename client/src/components/Manifest/Handler/{AddHandler.tsx => Search/HandlerSearchModal.tsx} (55%) create mode 100644 client/src/components/Manifest/Tsdf/TsdfSection.spec.tsx create mode 100644 client/src/hooks/manifest/useOpenHandlerSearch/useHandlerSearchConfig.tsx create mode 100644 client/src/hooks/manifest/useOpenHandlerSearch/useOpenHandlerSearch.spec.tsx diff --git a/client/src/components/Manifest/Generator/GeneratorSection.spec.tsx b/client/src/components/Manifest/Generator/GeneratorSection.spec.tsx new file mode 100644 index 000000000..cdfa61757 --- /dev/null +++ b/client/src/components/Manifest/Generator/GeneratorSection.spec.tsx @@ -0,0 +1,25 @@ +import '@testing-library/jest-dom'; +import { cleanup, renderWithProviders, screen } from 'test-utils'; +import { createMockHandler } from 'test-utils/fixtures'; +import { afterEach, describe, expect, test } from 'vitest'; +import { GeneratorSection } from './GeneratorSection'; + +afterEach(() => cleanup()); + +const TestComponent = () => { + return undefined} signAble={true} />; +}; + +describe('GeneratorSection', () => { + test('renders', () => { + renderWithProviders(, { + useFormProps: { + values: { + status: 'NotAssigned', + generator: createMockHandler({ epaSiteId: 'VATEST123' }), + }, + }, + }); + expect(screen.getByText(/vatest123/i)).toBeInTheDocument(); + }); +}); diff --git a/client/src/components/Manifest/Generator/GeneratorSection.tsx b/client/src/components/Manifest/Generator/GeneratorSection.tsx index 1f773ea47..b0e95d99c 100644 --- a/client/src/components/Manifest/Generator/GeneratorSection.tsx +++ b/client/src/components/Manifest/Generator/GeneratorSection.tsx @@ -3,30 +3,65 @@ import { ContactForm, PhoneForm } from 'components/Manifest/Contact'; import { Handler, Manifest } from 'components/Manifest/manifestSchema'; import { QuickSignBtn } from 'components/Manifest/QuickerSign'; import { RcraSiteDetails } from 'components/RcraSite'; -import { HtButton } from 'components/UI'; +import { HtButton, HtSpinner } from 'components/UI'; import { useReadOnly } from 'hooks/manifest'; -import React, { useState } from 'react'; +import { useHandlerSearchConfig } from 'hooks/manifest/useOpenHandlerSearch/useHandlerSearchConfig'; +import React, { useEffect, useState } from 'react'; import { Alert, Button, Col, Stack } from 'react-bootstrap'; import { useFormContext } from 'react-hook-form'; +import { useSearchParams } from 'react-router-dom'; +import { useGetRcrainfoSiteQuery } from 'store'; import { GeneratorForm } from './GeneratorForm'; interface GeneratorSectionProps { setupSign: () => void; - toggleShowAddGenerator: () => void; signAble: boolean; } -export function GeneratorSection({ - setupSign, - signAble, - toggleShowAddGenerator, -}: GeneratorSectionProps) { +export function GeneratorSection({ setupSign, signAble }: GeneratorSectionProps) { + const [, setSearchConfigs] = useHandlerSearchConfig(); const [readOnly] = useReadOnly(); + const [searchParams, setSearchParams] = useSearchParams(); const manifestForm = useFormContext(); - const errors = manifestForm.formState.errors; - const generator: Handler | undefined = manifestForm.getValues('generator'); + const { errors } = manifestForm.formState; + const generator: Handler | undefined = manifestForm.watch('generator'); const [showGeneratorForm, setShowGeneratorForm] = useState(false); const toggleShowGeneratorForm = () => setShowGeneratorForm(!showGeneratorForm); + const urlGeneratorId = searchParams.get('generator'); + + const { data, isLoading, error } = useGetRcrainfoSiteQuery(urlGeneratorId, { + skip: !urlGeneratorId, + }); + + useEffect(() => { + if (data) { + manifestForm.setValue('generator', data); + } + }, [data]); + + if (isLoading) { + return ; + } + + if (error) { + return ( + <> + + The requested Generator (EPA ID: {urlGeneratorId}) could not be found. + + { + searchParams.delete('generator'); + setSearchParams(searchParams); + }} + children={'Clear Generator'} + variant="outline-danger" + horizontalAlign + > + + ); + } + return ( <> {readOnly ? ( @@ -66,7 +101,9 @@ export function GeneratorSection({ { + setSearchConfigs({ siteType: 'generator', open: true }); + }} children={'Add Generator'} variant="outline-primary" /> diff --git a/client/src/components/Manifest/Handler/Search/HandlerSearchForm.tsx b/client/src/components/Manifest/Handler/Search/HandlerSearchForm.tsx index 0f5381d52..fa91327f3 100644 --- a/client/src/components/Manifest/Handler/Search/HandlerSearchForm.tsx +++ b/client/src/components/Manifest/Handler/Search/HandlerSearchForm.tsx @@ -12,6 +12,7 @@ import { useForm, useFormContext, } from 'react-hook-form'; +import { useSearchParams } from 'react-router-dom'; import Select from 'react-select'; import { useGetProfileQuery, useSearchRcrainfoSitesQuery, useSearchRcraSitesQuery } from 'store'; @@ -34,7 +35,7 @@ export function HandlerSearchForm({ appendTransporter, }: Props) { const { handleSubmit, control } = useForm(); - const manifestMethods = useFormContext(); + const manifestForm = useFormContext(); const [inputValue, setInputValue] = useState(''); const [selectedHandler, setSelectedHandler] = useState(null); const { org } = useGetProfileQuery(undefined, { @@ -63,6 +64,7 @@ export function HandlerSearchForm({ ); const { setGeneratorStateCode, setTsdfStateCode } = useContext(ManifestContext); + const [searchParams, setSearchParams] = useSearchParams(); const [options, setOptions] = useState([]); const [rcrainfoSitesLoading, setRcrainfoSitesLoading] = useState(false); @@ -71,10 +73,14 @@ export function HandlerSearchForm({ if (selectedHandler !== null) { if (handlerType === 'generator') { setGeneratorStateCode(selectedHandler.siteAddress.state.code); - manifestMethods.setValue('generator', { ...selectedHandler }); + manifestForm.setValue('generator', { ...selectedHandler }); + searchParams.append('generator', selectedHandler.epaSiteId); + setSearchParams(searchParams); } else if (handlerType === 'designatedFacility') { setTsdfStateCode(selectedHandler.siteAddress.state.code); - manifestMethods.setValue('designatedFacility', { ...selectedHandler }); + manifestForm.setValue('designatedFacility', { ...selectedHandler }); + searchParams.append('tsdf', selectedHandler.epaSiteId); + setSearchParams(searchParams); } else if (handlerType === 'transporter') { const numberOfTransporter = currentTransporters ? currentTransporters.length : 0; const newTransporter: Transporter = { diff --git a/client/src/components/Manifest/Handler/AddHandler.tsx b/client/src/components/Manifest/Handler/Search/HandlerSearchModal.tsx similarity index 55% rename from client/src/components/Manifest/Handler/AddHandler.tsx rename to client/src/components/Manifest/Handler/Search/HandlerSearchModal.tsx index 5d67c02da..0a8f935a9 100644 --- a/client/src/components/Manifest/Handler/AddHandler.tsx +++ b/client/src/components/Manifest/Handler/Search/HandlerSearchModal.tsx @@ -1,42 +1,39 @@ import { HandlerSearchForm } from 'components/Manifest/Handler'; -import { RcraSite } from 'components/RcraSite'; +import { Manifest, Transporter } from 'components/Manifest/manifestSchema'; import { HtModal } from 'components/UI'; +import { useHandlerSearchConfig } from 'hooks/manifest/useOpenHandlerSearch/useHandlerSearchConfig'; import React from 'react'; import { Col, Row } from 'react-bootstrap'; -import { UseFieldArrayAppend } from 'react-hook-form'; -import { Manifest, SiteType } from '../manifestSchema'; - -interface AddHandlerProps { - handleClose: () => void; - show: boolean | undefined; - handlerType: SiteType; - currentTransporters?: Array; - appendTransporter?: UseFieldArrayAppend; -} +import { useFieldArray, useFormContext } from 'react-hook-form'; /** * Returns a modal that wraps around the HandlerSearchForm for adding the manifest TSDF - * @param show - * @param handleClose - * @param handlerType - * @param currentTransporters - * @param appendTransporter * @constructor */ -export function AddHandler({ - show, - handleClose, - handlerType, - currentTransporters, - appendTransporter, -}: AddHandlerProps) { +export function HandlerSearchModal() { + const manifestForm = useFormContext(); + const [configs, setConfigs] = useHandlerSearchConfig(); + + const transporterForm = useFieldArray({ + control: manifestForm.control, + name: 'transporters', + }); + const transporters: Array = manifestForm.getValues('transporters'); + + if (!configs) return null; + const { siteType, open } = configs; + + const handleClose = () => { + setConfigs(undefined); + }; + // set the title and description based on the handlerType let title; let description; - if (handlerType === 'designatedFacility') { + if (siteType === 'designatedFacility') { title = 'Add Designated Facility'; description = 'The Treatment, Storage, or Disposal Facility the waste will shipped to.'; - } else if (handlerType === 'transporter') { + } else if (siteType === 'transporter') { title = 'Add Transporter'; description = 'Transporters of the hazardous waste shipment. Transporters are required to be registered with EPA at https://rcrainfo.epa.gov.'; @@ -47,7 +44,7 @@ export function AddHandler({ } return ( - + @@ -63,9 +60,9 @@ export function AddHandler({ diff --git a/client/src/components/Manifest/Handler/index.ts b/client/src/components/Manifest/Handler/index.ts index 331667f04..76c4baa98 100644 --- a/client/src/components/Manifest/Handler/index.ts +++ b/client/src/components/Manifest/Handler/index.ts @@ -1,6 +1,6 @@ import { Handler, handlerSchema, Signer } from 'components/Manifest/manifestSchema'; -import { AddHandler } from './AddHandler'; import { HandlerSearchForm } from './Search/HandlerSearchForm'; +import { HandlerSearchModal } from './Search/HandlerSearchModal'; -export { HandlerSearchForm, AddHandler, handlerSchema }; +export { HandlerSearchForm, HandlerSearchModal, handlerSchema }; export type { Handler, Signer }; diff --git a/client/src/components/Manifest/ManifestForm.tsx b/client/src/components/Manifest/ManifestForm.tsx index b5885ac0c..498ca1ff2 100644 --- a/client/src/components/Manifest/ManifestForm.tsx +++ b/client/src/components/Manifest/ManifestForm.tsx @@ -27,7 +27,7 @@ import { useSaveEManifestMutation, useUpdateManifestMutation, } from 'store'; -import { AddHandler } from './Handler'; +import { HandlerSearchModal } from './Handler'; import { Manifest, manifestSchema, SiteType } from './manifestSchema'; import { QuickerSignData, QuickerSignModal } from './QuickerSign'; import { EditWasteModal } from './WasteLine'; @@ -168,15 +168,11 @@ export function ManifestForm({ }; // Generator component state and methods - const [showGeneratorSearch, setShowGeneratorSearch] = useState(false); - const toggleShowAddGenerator = () => setShowGeneratorSearch(!showGeneratorSearch); const [generatorStateCode, setGeneratorStateCode] = useState( manifestData?.generator?.siteAddress.state.code ); // DesignatedFacility (TSDF) component state and methods - const [tsdfFormShow, setTsdfFormShow] = useState(false); - const toggleTsdfFormShow = () => setTsdfFormShow(!tsdfFormShow); const [tsdfStateCode, setTsdfStateCode] = useState( manifestData?.designatedFacility?.siteAddress.state.code ); @@ -258,11 +254,7 @@ export function ManifestForm({ - + @@ -277,11 +269,7 @@ export function ManifestForm({ - + @@ -299,16 +287,7 @@ export function ManifestForm({ {/*If taking action that involves updating a manifest in RCRAInfo*/} {taskId && showSpinner ? : <>} - - + (); const { errors } = manifestForm.formState; @@ -22,9 +22,6 @@ export function TransporterSection({ setupSign }: TransporterSectionProps) { }); const transporters: Array = manifestForm.getValues('transporters'); - const [showAddTransporterForm, setShowAddTransporterForm] = useState(false); - const toggleTranSearchShow = () => setShowAddTransporterForm(!showAddTransporterForm); - return ( <> ) : ( { + setSearchConfigs({ siteType: 'transporter', open: true }); + }} children={'Add Transporter'} variant="outline-primary" horizontalAlign @@ -51,13 +50,6 @@ export function TransporterSection({ setupSign }: TransporterSectionProps) { )} /> - ); } diff --git a/client/src/components/Manifest/Transporter/TransporterTable.spec.tsx b/client/src/components/Manifest/Transporter/TransporterTable.spec.tsx index a12f5d100..26a640e72 100644 --- a/client/src/components/Manifest/Transporter/TransporterTable.spec.tsx +++ b/client/src/components/Manifest/Transporter/TransporterTable.spec.tsx @@ -70,9 +70,8 @@ describe('TransporterTable', () => { // @ts-ignore arrayFieldMethods={emptyArrayFieldMethods} transporters={TRAN_ARRAY} - readOnly={true} />, - {} + { preloadedState: { manifest: { readOnly: true } } } ); const actionDropdown = screen.queryAllByRole('button', { name: /transporter [0-9] actions/, diff --git a/client/src/components/Manifest/Tsdf/TsdfSection.spec.tsx b/client/src/components/Manifest/Tsdf/TsdfSection.spec.tsx new file mode 100644 index 000000000..a2f3818e5 --- /dev/null +++ b/client/src/components/Manifest/Tsdf/TsdfSection.spec.tsx @@ -0,0 +1,25 @@ +import '@testing-library/jest-dom'; +import { cleanup, renderWithProviders, screen } from 'test-utils'; +import { createMockHandler } from 'test-utils/fixtures'; +import { afterEach, describe, expect, test } from 'vitest'; +import { TsdfSection } from './TsdfSection'; + +afterEach(() => cleanup()); + +const TestComponent = () => { + return undefined} signAble={true} />; +}; + +describe('TsdfSection', () => { + test('renders', () => { + renderWithProviders(, { + useFormProps: { + values: { + status: 'NotAssigned', + designatedFacility: createMockHandler({ epaSiteId: 'VATEST123' }), + }, + }, + }); + expect(screen.getByText(/vatest123/i)).toBeInTheDocument(); + }); +}); diff --git a/client/src/components/Manifest/Tsdf/TsdfSection.tsx b/client/src/components/Manifest/Tsdf/TsdfSection.tsx index a911ce580..b0909efc0 100644 --- a/client/src/components/Manifest/Tsdf/TsdfSection.tsx +++ b/client/src/components/Manifest/Tsdf/TsdfSection.tsx @@ -2,25 +2,62 @@ import { ErrorMessage } from '@hookform/error-message'; import { Handler, Manifest } from 'components/Manifest/manifestSchema'; import { QuickSignBtn } from 'components/Manifest/QuickerSign'; import { RcraSiteDetails } from 'components/RcraSite'; -import { HtButton } from 'components/UI'; +import { HtButton, HtSpinner } from 'components/UI'; import { useReadOnly } from 'hooks/manifest'; -import React from 'react'; +import { useHandlerSearchConfig } from 'hooks/manifest/useOpenHandlerSearch/useHandlerSearchConfig'; +import React, { useEffect } from 'react'; import { Alert, Col } from 'react-bootstrap'; import { useFormContext } from 'react-hook-form'; +import { useSearchParams } from 'react-router-dom'; +import { useGetRcrainfoSiteQuery } from 'store'; interface TsdfSectionProps { setupSign: () => void; signAble: boolean; - toggleTsdfFormShow: () => void; } -export function TsdfSection({ signAble, setupSign, toggleTsdfFormShow }: TsdfSectionProps) { +export function TsdfSection({ signAble, setupSign }: TsdfSectionProps) { + const [, setSearchConfigs] = useHandlerSearchConfig(); + const [searchParams, setSearchParams] = useSearchParams(); const { formState: { errors }, ...manifestForm } = useFormContext(); const tsdf: Handler | undefined = manifestForm.watch('designatedFacility'); const [readOnly] = useReadOnly(); + const urlTsdfId = searchParams.get('tsdf'); + const { data, isLoading, error } = useGetRcrainfoSiteQuery(urlTsdfId, { + skip: !urlTsdfId, + }); + + useEffect(() => { + if (data) { + manifestForm.setValue('designatedFacility', data); + } + }, [data]); + + if (isLoading) { + return ; + } + + if (error) { + return ( + <> + + The requested TSDF (EPA ID: {urlTsdfId}) could not be found. + + { + searchParams.delete('tsdf'); + setSearchParams(searchParams); + }} + children={'Clear TSDF'} + variant="outline-danger" + horizontalAlign + > + + ); + } return ( <> @@ -44,9 +81,14 @@ export function TsdfSection({ signAble, setupSign, toggleTsdfFormShow }: TsdfSec )} {tsdf && !readOnly && ( + // Remove TSDF button { manifestForm.setValue('designatedFacility', undefined); + if (urlTsdfId) { + searchParams.delete('tsdf'); + setSearchParams(searchParams); + } }} children={'Remove TSDF'} variant="outline-danger" @@ -55,7 +97,9 @@ export function TsdfSection({ signAble, setupSign, toggleTsdfFormShow }: TsdfSec )} {!tsdf && ( { + setSearchConfigs({ siteType: 'designatedFacility', open: true }); + }} children={'Add TSDF'} variant="outline-primary" horizontalAlign diff --git a/client/src/features/ManifestDetails/ManifestDetails.tsx b/client/src/features/ManifestDetails/ManifestDetails.tsx index 917057c23..5ae3930d1 100644 --- a/client/src/features/ManifestDetails/ManifestDetails.tsx +++ b/client/src/features/ManifestDetails/ManifestDetails.tsx @@ -1,6 +1,7 @@ import { ManifestForm } from 'components/Manifest'; import { HtSpinner } from 'components/UI'; import { useTitle } from 'hooks'; +import { useReadOnly } from 'hooks/manifest'; import React from 'react'; import { useParams } from 'react-router-dom'; import { useGetManifestQuery } from 'store'; @@ -9,6 +10,7 @@ export function ManifestDetails() { const { mtn, action, siteId } = useParams(); useTitle(`${mtn}`); const { data, error, isLoading } = useGetManifestQuery(mtn!, { skip: !mtn }); + useReadOnly(true); const readOnly = action !== 'edit'; diff --git a/client/src/features/NewManifest/NewManifest.tsx b/client/src/features/NewManifest/NewManifest.tsx index bbc3abfbc..487041390 100644 --- a/client/src/features/NewManifest/NewManifest.tsx +++ b/client/src/features/NewManifest/NewManifest.tsx @@ -5,10 +5,11 @@ import { SiteSelect, SiteTypeSelect } from 'components/Manifest/SiteSelect'; import { RcraSite } from 'components/RcraSite'; import { HtCard, HtSpinner } from 'components/UI'; import { useTitle } from 'hooks'; +import { useReadOnly } from 'hooks/manifest'; import React, { useEffect, useMemo, useState } from 'react'; import { Col, Container, Form } from 'react-bootstrap'; import { useForm } from 'react-hook-form'; -import { useParams } from 'react-router-dom'; +import { useParams, useSearchParams } from 'react-router-dom'; import { useGetProfileQuery } from 'store'; /** @@ -22,6 +23,10 @@ export function NewManifest() { useTitle('New Manifest'); const { control } = useForm(); const { siteId } = useParams(); + useReadOnly(false); + const [searchParams, setSearchParams] = useSearchParams(); + + const urlSiteType = searchParams.get('st'); const updateSiteSelection = (site: RcraSite) => { setManifestingSite(site); @@ -29,6 +34,9 @@ export function NewManifest() { if (site.siteType === 'Generator') { setSelectedSiteType(site.siteType); } + if (urlSiteType) { + setSelectedSiteType(urlSiteType as RcraSiteType); + } }; const selectBySiteId = useMemo(() => { diff --git a/client/src/hooks/manifest/useOpenHandlerSearch/useHandlerSearchConfig.tsx b/client/src/hooks/manifest/useOpenHandlerSearch/useHandlerSearchConfig.tsx new file mode 100644 index 000000000..d4a1aa556 --- /dev/null +++ b/client/src/hooks/manifest/useOpenHandlerSearch/useHandlerSearchConfig.tsx @@ -0,0 +1,31 @@ +import { useEffect, useState } from 'react'; +import { useAppDispatch, useAppSelector } from 'store'; +import { + HandlerSearchConfig, + selectHandlerSearchConfigs, + setHandlerSearchConfigs, +} from 'store/manifestSlice/manifest.slice'; + +/** hook used to control the handler Search Form modal + * @example const [open, setOpen] = useOpenHandlerSearch(); + * */ +export function useHandlerSearchConfig() { + const dispatch = useAppDispatch(); + const [configs, setConfigs] = useState(undefined); + const reduxConfigs = useAppSelector(selectHandlerSearchConfigs); + + useEffect(() => { + dispatch(setHandlerSearchConfigs(configs)); + }, [configs]); + + useEffect(() => { + setConfigs(reduxConfigs); + }, [reduxConfigs]); + + /** Set the Modal as open or closed*/ + const handleConfigChange = (handlerSearchConfig: HandlerSearchConfig | undefined) => { + setConfigs(handlerSearchConfig); + }; + + return [configs, handleConfigChange] as const; +} diff --git a/client/src/hooks/manifest/useOpenHandlerSearch/useOpenHandlerSearch.spec.tsx b/client/src/hooks/manifest/useOpenHandlerSearch/useOpenHandlerSearch.spec.tsx new file mode 100644 index 000000000..32e4028e0 --- /dev/null +++ b/client/src/hooks/manifest/useOpenHandlerSearch/useOpenHandlerSearch.spec.tsx @@ -0,0 +1,48 @@ +import '@testing-library/jest-dom'; +import { cleanup } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { SiteType } from 'components/Manifest/manifestSchema'; +import { useHandlerSearchConfig } from 'hooks/manifest/useOpenHandlerSearch/useHandlerSearchConfig'; +import React from 'react'; +import { renderWithProviders, screen } from 'test-utils'; +import { afterEach, describe, expect, it } from 'vitest'; + +const TestChildComponent = () => { + const [configs] = useHandlerSearchConfig(); + return ( + <> +

child open: {configs?.open ? 'yes' : 'no'}

+

child siteType: {configs?.siteType ?? 'undefined'}

+ + ); +}; + +const TestComponent = ({ siteType }: { siteType?: SiteType; open?: boolean }) => { + const [configs, setConfigs] = useHandlerSearchConfig(); + return ( + <> +

open: {configs?.open ? 'yes' : 'no'}

+ + + + ); +}; + +afterEach(() => cleanup()); + +describe('useHandlerSearchConfig', () => { + it('handler search model is closed by default', () => { + renderWithProviders(); + expect(screen.getByText(/^open: no/i)).toBeInTheDocument(); + }); + it('shares open state with other components', async () => { + renderWithProviders(); + expect(screen.getByText(/^open: no/i)).toBeInTheDocument(); + expect(screen.getByText(/^child open: no/i)).toBeInTheDocument(); + await userEvent.click(screen.getByText(/^search$/i)); + expect(screen.getByText(/^open: yes/i)).toBeInTheDocument(); + expect(screen.getByText(/^child open: yes/i)).toBeInTheDocument(); + }); +}); diff --git a/client/src/hooks/manifest/useReadOnly/useReadOnly.tsx b/client/src/hooks/manifest/useReadOnly/useReadOnly.tsx index becea0ce1..6cec6b25f 100644 --- a/client/src/hooks/manifest/useReadOnly/useReadOnly.tsx +++ b/client/src/hooks/manifest/useReadOnly/useReadOnly.tsx @@ -9,10 +9,10 @@ export function useReadOnly(initialValue?: boolean) { const dispatch = useAppDispatch(); const reduxReadOnly = useAppSelector(selectManifestReadOnly); const defaultReadOnly = initialValue !== undefined ? initialValue : reduxReadOnly; - const [readOnly, setReadonly] = useState(defaultReadOnly); + const [readOnly, setReadonly] = useState(defaultReadOnly); useEffect(() => { - dispatch(setManifestReadOnly(readOnly)); + if (readOnly !== undefined) dispatch(setManifestReadOnly(readOnly)); }, [readOnly]); useEffect(() => { @@ -23,7 +23,7 @@ export function useReadOnly(initialValue?: boolean) { }, [initialValue]); useEffect(() => { - setReadonly(reduxReadOnly); + if (reduxReadOnly !== undefined) setReadonly(reduxReadOnly); }, [reduxReadOnly]); /** Set the manifest readOnly*/ diff --git a/client/src/store/htApi.slice.ts b/client/src/store/htApi.slice.ts index 094e6445e..d3fcd0252 100644 --- a/client/src/store/htApi.slice.ts +++ b/client/src/store/htApi.slice.ts @@ -48,10 +48,8 @@ export const htApiBaseQuery = return { data: response.data }; } catch (axiosError) { let err = axiosError as AxiosError; - console.error(err); return { error: { - ...err, statusText: err.response?.statusText, data: err.response?.data || err.message, } as HtApiError, @@ -95,6 +93,12 @@ export const haztrakApi = createApi({ params: { epaId: data.siteId, siteType: data.siteType }, }), }), + getRcrainfoSite: build.query({ + query: (epaSiteId) => ({ + url: `rcra/handler/${epaSiteId}`, + method: 'get', + }), + }), getTaskStatus: build.query({ query: (taskId) => ({ url: `task/${taskId}`, method: 'get' }), }), diff --git a/client/src/store/index.ts b/client/src/store/index.ts index aab921280..eb765683a 100644 --- a/client/src/store/index.ts +++ b/client/src/store/index.ts @@ -24,6 +24,7 @@ export const { useSignEManifestMutation, useUpdateManifestMutation, useGetManifestQuery, + useGetRcrainfoSiteQuery, } = haztrakApi; export const { diff --git a/client/src/store/manifestSlice/manifest.slice.spec.tsx b/client/src/store/manifestSlice/manifest.slice.spec.tsx index 5d564e252..99ce3884d 100644 --- a/client/src/store/manifestSlice/manifest.slice.spec.tsx +++ b/client/src/store/manifestSlice/manifest.slice.spec.tsx @@ -3,6 +3,7 @@ import { useAppSelector } from 'store'; import reducer, { ManifestSlice, selectManifestReadOnly, + setHandlerSearchConfigs, setManifestReadOnly, setManifestStatus, } from 'store/manifestSlice/manifest.slice'; @@ -36,4 +37,15 @@ describe('Manifest slice', () => { const editableState: ManifestSlice = reducer(undefined, setManifestReadOnly(false)); expect(editableState.readOnly).toBe(false); }); + test('openHandlerSearch sets open to true and site type to argument', () => { + const state: ManifestSlice = reducer( + undefined, + setHandlerSearchConfigs({ + siteType: 'generator', + open: true, + }) + ); + expect(state.handlerSearch?.open).toBe(true); + expect(state.handlerSearch?.siteType).toBe('generator'); + }); }); diff --git a/client/src/store/manifestSlice/manifest.slice.ts b/client/src/store/manifestSlice/manifest.slice.ts index fdcec74e2..7dacf27ce 100644 --- a/client/src/store/manifestSlice/manifest.slice.ts +++ b/client/src/store/manifestSlice/manifest.slice.ts @@ -1,13 +1,19 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { ManifestStatus } from 'components/Manifest/manifestSchema'; +import { ManifestStatus, SiteType } from 'components/Manifest/manifestSchema'; + +export interface HandlerSearchConfig { + siteType: SiteType; + open: boolean; +} export interface ManifestSlice { status?: ManifestStatus; - readOnly: boolean; + readOnly?: boolean; + handlerSearch?: HandlerSearchConfig; } const initialState: ManifestSlice = { - readOnly: true, + readOnly: undefined, status: undefined, }; @@ -18,6 +24,7 @@ export const manifestSlice = createSlice({ selectManifestStatus: (state) => state.status, selectManifestReadOnly: (state) => state.readOnly, selectManifestEditable: (state) => !state.readOnly, + selectHandlerSearchConfigs: (state) => state.handlerSearch, }, reducers: { setManifestStatus: (state, action: PayloadAction) => { @@ -26,10 +33,14 @@ export const manifestSlice = createSlice({ setManifestReadOnly: (state, action: PayloadAction) => { state.readOnly = action.payload; }, + setHandlerSearchConfigs: (state, action: PayloadAction) => { + state.handlerSearch = action.payload; + }, }, }); export default manifestSlice.reducer; -export const { setManifestStatus, setManifestReadOnly } = manifestSlice.actions; -export const { selectManifestStatus, selectManifestReadOnly, selectManifestEditable } = +export const { setManifestStatus, setManifestReadOnly, setHandlerSearchConfigs } = + manifestSlice.actions; +export const { selectManifestStatus, selectManifestReadOnly, selectHandlerSearchConfigs } = manifestSlice.selectors; diff --git a/server/apps/site/urls.py b/server/apps/site/urls.py index ad44d5fa3..e5ac6d1f9 100644 --- a/server/apps/site/urls.py +++ b/server/apps/site/urls.py @@ -15,7 +15,7 @@ include( [ path("handler/search", SearchHandlerView.as_view()), - path("handler/", GetRcraSiteView.as_view()), + path("handler/", GetRcraSiteView.as_view()), ] ), ), diff --git a/server/apps/site/views.py b/server/apps/site/views.py index 07fe508c4..4b759e181 100644 --- a/server/apps/site/views.py +++ b/server/apps/site/views.py @@ -67,12 +67,12 @@ def get(self, request, *args, **kwargs): description="Retrieve details on a rcra_site stored in the Haztrak database", ) class GetRcraSiteView(RetrieveAPIView): - """ - Retrieve details on a RCRAInfo Site known to haztrak - """ + """Retrieve details on a RCRAInfo Site known to haztrak by their EPA ID number""" queryset = RcraSite.objects.all() serializer_class = RcraSiteSerializer + lookup_field = "epa_id__iexact" + lookup_url_kwarg = "epa_id" permission_classes = [permissions.IsAuthenticated] @method_decorator(cache_page(60 * 15))