From ca85782055a904bbd2f2a0813ecf7643564f7a13 Mon Sep 17 00:00:00 2001 From: David Graham Date: Mon, 8 Jan 2024 09:09:00 -0500 Subject: [PATCH 1/4] move All manifest sections into separate components. Including a TsdfSection, GeneratorSection, WasteLineSection, TransporterSection In addition: add pyupgrade pre-commit hook and upgrade hooks --- .pre-commit-config.yaml | 12 +- .../src/components/Manifest/Actions/index.ts | 4 + .../components/Manifest/GeneralInfo/index.ts | 1 + .../GeneratorForm.spec.tsx | 2 +- .../{Handler => Generator}/GeneratorForm.tsx | 0 .../Manifest/Generator/GeneratorSection.tsx | 93 +++++++++ .../components/Manifest/Generator/index.ts | 1 + .../src/components/Manifest/Handler/index.ts | 5 +- .../src/components/Manifest/ManifestForm.tsx | 178 +++--------------- .../Transporter/TransporterSection.tsx | 63 +++++++ .../{Handler => Tsdf}/TsdfSection.tsx | 4 +- client/src/components/Manifest/Tsdf/index.ts | 1 + .../Manifest/WasteLine/WasteLineForm.tsx | 2 +- .../Manifest/WasteLine/WasteLineSection.tsx | 48 +++++ .../src/features/NewManifest/NewManifest.tsx | 10 +- 15 files changed, 255 insertions(+), 169 deletions(-) create mode 100644 client/src/components/Manifest/Actions/index.ts create mode 100644 client/src/components/Manifest/GeneralInfo/index.ts rename client/src/components/Manifest/{Handler => Generator}/GeneratorForm.spec.tsx (93%) rename client/src/components/Manifest/{Handler => Generator}/GeneratorForm.tsx (100%) create mode 100644 client/src/components/Manifest/Generator/GeneratorSection.tsx create mode 100644 client/src/components/Manifest/Generator/index.ts create mode 100644 client/src/components/Manifest/Transporter/TransporterSection.tsx rename client/src/components/Manifest/{Handler => Tsdf}/TsdfSection.tsx (92%) create mode 100644 client/src/components/Manifest/Tsdf/index.ts create mode 100644 client/src/components/Manifest/WasteLine/WasteLineSection.tsx diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 41c722d1b..dcafdc791 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,7 +11,7 @@ ci: repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: 'v3.2.0' + rev: 'v4.5.0' hooks: - id: trailing-whitespace - id: check-json @@ -24,7 +24,7 @@ repos: \.vscode\/.*| )$ - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: 'v0.1.3' + rev: 'v0.1.11' hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] @@ -38,13 +38,17 @@ repos: - python pass_filenames: false - repo: https://github.com/pre-commit/mirrors-eslint - rev: 'v8.34.0' + rev: 'v8.56.0' hooks: - id: eslint files: \.[jt]sx?$ types: [file] - repo: https://github.com/pre-commit/mirrors-prettier - rev: 'v3.0.0-alpha.3' + rev: 'v3.1.0' hooks: - id: prettier exclude: infra/haztrak/ + - repo: https://github.com/asottile/pyupgrade + rev: v3.15.0 + hooks: + - id: pyupgrade diff --git a/client/src/components/Manifest/Actions/index.ts b/client/src/components/Manifest/Actions/index.ts new file mode 100644 index 000000000..51002577e --- /dev/null +++ b/client/src/components/Manifest/Actions/index.ts @@ -0,0 +1,4 @@ +export { ManifestCancelBtn } from './ManifestCancelBtn'; +export { ManifestSaveBtn } from './ManifestSaveBtn'; +export { ManifestEditBtn } from './ManifestEditBtn'; +export { ManifestFABs } from './ManifestFABs'; diff --git a/client/src/components/Manifest/GeneralInfo/index.ts b/client/src/components/Manifest/GeneralInfo/index.ts new file mode 100644 index 000000000..519c947a4 --- /dev/null +++ b/client/src/components/Manifest/GeneralInfo/index.ts @@ -0,0 +1 @@ +export { GeneralInfoForm } from './GeneralInfoForm'; diff --git a/client/src/components/Manifest/Handler/GeneratorForm.spec.tsx b/client/src/components/Manifest/Generator/GeneratorForm.spec.tsx similarity index 93% rename from client/src/components/Manifest/Handler/GeneratorForm.spec.tsx rename to client/src/components/Manifest/Generator/GeneratorForm.spec.tsx index 99da49ee5..1bfa33bdf 100644 --- a/client/src/components/Manifest/Handler/GeneratorForm.spec.tsx +++ b/client/src/components/Manifest/Generator/GeneratorForm.spec.tsx @@ -1,10 +1,10 @@ import '@testing-library/jest-dom'; import { fireEvent } from '@testing-library/react'; -import { GeneratorForm } from 'components/Manifest/Handler/GeneratorForm'; import { siteType } from 'components/Manifest/manifestSchema'; import React from 'react'; import { cleanup, renderWithProviders, screen } from 'test-utils'; import { afterEach, describe, expect, test } from 'vitest'; +import { GeneratorForm } from './GeneratorForm'; afterEach(() => { cleanup(); diff --git a/client/src/components/Manifest/Handler/GeneratorForm.tsx b/client/src/components/Manifest/Generator/GeneratorForm.tsx similarity index 100% rename from client/src/components/Manifest/Handler/GeneratorForm.tsx rename to client/src/components/Manifest/Generator/GeneratorForm.tsx diff --git a/client/src/components/Manifest/Generator/GeneratorSection.tsx b/client/src/components/Manifest/Generator/GeneratorSection.tsx new file mode 100644 index 000000000..1f773ea47 --- /dev/null +++ b/client/src/components/Manifest/Generator/GeneratorSection.tsx @@ -0,0 +1,93 @@ +import { ErrorMessage } from '@hookform/error-message'; +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 { useReadOnly } from 'hooks/manifest'; +import React, { useState } from 'react'; +import { Alert, Button, Col, Stack } from 'react-bootstrap'; +import { useFormContext } from 'react-hook-form'; +import { GeneratorForm } from './GeneratorForm'; + +interface GeneratorSectionProps { + setupSign: () => void; + toggleShowAddGenerator: () => void; + signAble: boolean; +} + +export function GeneratorSection({ + setupSign, + signAble, + toggleShowAddGenerator, +}: GeneratorSectionProps) { + const [readOnly] = useReadOnly(); + const manifestForm = useFormContext(); + const errors = manifestForm.formState.errors; + const generator: Handler | undefined = manifestForm.getValues('generator'); + const [showGeneratorForm, setShowGeneratorForm] = useState(false); + const toggleShowGeneratorForm = () => setShowGeneratorForm(!showGeneratorForm); + return ( + <> + {readOnly ? ( + <> + +

Emergency Contact Information

+ +
+ + + +
+ + ) : generator && !showGeneratorForm ? ( + <> + + +
+ +
+ + ) : showGeneratorForm ? ( + <> + +

Emergency Contact Information

+ + + ) : ( + <> + + + + Enter Generator Information + + + + )} + { + if (!message) return null; + return ( + + {message} + + ); + }} + /> + + ); +} diff --git a/client/src/components/Manifest/Generator/index.ts b/client/src/components/Manifest/Generator/index.ts new file mode 100644 index 000000000..2330618ea --- /dev/null +++ b/client/src/components/Manifest/Generator/index.ts @@ -0,0 +1 @@ +export { GeneratorSection } from './GeneratorSection'; diff --git a/client/src/components/Manifest/Handler/index.ts b/client/src/components/Manifest/Handler/index.ts index 1f38f8859..331667f04 100644 --- a/client/src/components/Manifest/Handler/index.ts +++ b/client/src/components/Manifest/Handler/index.ts @@ -1,7 +1,6 @@ import { Handler, handlerSchema, Signer } from 'components/Manifest/manifestSchema'; -import { GeneratorForm } from 'components/Manifest/Handler/GeneratorForm'; -import { HandlerSearchForm } from './Search/HandlerSearchForm'; import { AddHandler } from './AddHandler'; +import { HandlerSearchForm } from './Search/HandlerSearchForm'; -export { GeneratorForm, HandlerSearchForm, AddHandler, handlerSchema }; +export { HandlerSearchForm, AddHandler, handlerSchema }; export type { Handler, Signer }; diff --git a/client/src/components/Manifest/ManifestForm.tsx b/client/src/components/Manifest/ManifestForm.tsx index d6f107b6d..b5885ac0c 100644 --- a/client/src/components/Manifest/ManifestForm.tsx +++ b/client/src/components/Manifest/ManifestForm.tsx @@ -1,21 +1,23 @@ -import { ErrorMessage } from '@hookform/error-message'; import { zodResolver } from '@hookform/resolvers/zod'; -import { ManifestCancelBtn } from 'components/Manifest/Actions/ManifestCancelBtn'; -import { ManifestEditBtn } from 'components/Manifest/Actions/ManifestEditBtn'; -import { ManifestFABs } from 'components/Manifest/Actions/ManifestFABs'; -import { ManifestSaveBtn } from 'components/Manifest/Actions/ManifestSaveBtn'; +import { + ManifestCancelBtn, + ManifestEditBtn, + ManifestFABs, + ManifestSaveBtn, +} from 'components/Manifest/Actions'; import { AdditionalInfoForm } from 'components/Manifest/AdditionalInfo'; -import { GeneralInfoForm } from 'components/Manifest/GeneralInfo/GeneralInfoForm'; -import { TsdfSection } from 'components/Manifest/Handler/TsdfSection'; +import { GeneralInfoForm } from 'components/Manifest/GeneralInfo'; +import { GeneratorSection } from 'components/Manifest/Generator'; +import { TransporterSection } from 'components/Manifest/Transporter/TransporterSection'; +import { TsdfSection } from 'components/Manifest/Tsdf'; import { UpdateRcra } from 'components/Manifest/UpdateRcra/UpdateRcra'; import { WasteLine } from 'components/Manifest/WasteLine/wasteLineSchema'; -import { RcraSiteDetails } from 'components/RcraSite'; -import { HtButton, HtCard, HtForm } from 'components/UI'; -import { useManifestStatus } from 'hooks/manifest'; -import { useReadOnly } from 'hooks/manifest/useReadOnly/useReadOnly'; -import { useUserSiteIds } from 'hooks/useUserSiteIds/useUserSiteIds'; +import { WasteLineSection } from 'components/Manifest/WasteLine/WasteLineSection'; +import { HtCard, HtForm } from 'components/UI'; +import { useUserSiteIds } from 'hooks'; +import { useManifestStatus, useReadOnly } from 'hooks/manifest'; import React, { createContext, useEffect, useState } from 'react'; -import { Alert, Button, Col, Container, Stack } from 'react-bootstrap'; +import { Container, Stack } from 'react-bootstrap'; import { FormProvider, SubmitHandler, useFieldArray, useForm } from 'react-hook-form'; import { useNavigate } from 'react-router-dom'; import { toast } from 'react-toastify'; @@ -25,12 +27,10 @@ import { useSaveEManifestMutation, useUpdateManifestMutation, } from 'store'; -import { ContactForm, PhoneForm } from './Contact'; -import { AddHandler, GeneratorForm, Handler } from './Handler'; +import { AddHandler } from './Handler'; import { Manifest, manifestSchema, SiteType } from './manifestSchema'; -import { QuickerSignData, QuickerSignModal, QuickSignBtn } from './QuickerSign'; -import { Transporter, TransporterTable } from './Transporter'; -import { EditWasteModal, WasteLineTable } from './WasteLine'; +import { QuickerSignData, QuickerSignModal } from './QuickerSign'; +import { EditWasteModal } from './WasteLine'; const defaultValues: Manifest = { transporters: [], @@ -96,7 +96,7 @@ export function ManifestForm({ }; } useManifestStatus(values.status); - const [readOnly] = useReadOnly(propReadOnly); + useReadOnly(propReadOnly); // State related to inter-system communications with EPA's RCRAInfo system const [showSpinner, setShowSpinner] = useState(false); @@ -116,9 +116,6 @@ export function ManifestForm({ values: values, resolver: zodResolver(manifestSchema), }); - const { - formState: { errors }, - } = manifestForm; useEffect(() => { if (createData) { @@ -171,28 +168,15 @@ export function ManifestForm({ }; // Generator component state and methods - const generator: Handler | undefined = manifestForm.getValues('generator'); const [showGeneratorSearch, setShowGeneratorSearch] = useState(false); - const [showGeneratorForm, setShowGeneratorForm] = useState(false); const toggleShowAddGenerator = () => setShowGeneratorSearch(!showGeneratorSearch); - const toggleShowGeneratorForm = () => setShowGeneratorForm(!showGeneratorForm); const [generatorStateCode, setGeneratorStateCode] = useState( manifestData?.generator?.siteAddress.state.code ); - // Transporter component state and methods - const [showAddTransporterForm, setShowAddTransporterForm] = useState(false); - const toggleTranSearchShow = () => setShowAddTransporterForm(!showAddTransporterForm); - const transporters: Array = manifestForm.getValues('transporters'); - const transporterForm = useFieldArray({ - control: manifestForm.control, - name: 'transporters', - }); - // DesignatedFacility (TSDF) component state and methods const [tsdfFormShow, setTsdfFormShow] = useState(false); const toggleTsdfFormShow = () => setTsdfFormShow(!tsdfFormShow); - const tsdf: Handler | undefined = manifestForm.watch('designatedFacility'); const [tsdfStateCode, setTsdfStateCode] = useState( manifestData?.designatedFacility?.siteAddress.state.code ); @@ -274,131 +258,26 @@ export function ManifestForm({ - {readOnly ? ( - <> - -

Emergency Contact Information

- -
- - - -
- - ) : generator && !showGeneratorForm ? ( - <> - - -
- -
- - ) : showGeneratorForm ? ( - <> - -

Emergency Contact Information

- - - ) : ( - <> - - - - Enter Generator Information - - - - )} - { - if (!message) return null; - return ( - - {message} - - ); - }} +
- - {readOnly ? ( - <> - ) : ( - - )} - ( - - {message} - - )} - /> + - - {readOnly ? ( - <> - ) : ( - - )} - ( - - {message} - - )} - /> + - void; +} + +export function TransporterSection({ setupSign }: TransporterSectionProps) { + const [readOnly] = useReadOnly(); + const manifestForm = useFormContext(); + const { errors } = manifestForm.formState; + const transporterForm = useFieldArray({ + control: manifestForm.control, + name: 'transporters', + }); + const transporters: Array = manifestForm.getValues('transporters'); + + const [showAddTransporterForm, setShowAddTransporterForm] = useState(false); + const toggleTranSearchShow = () => setShowAddTransporterForm(!showAddTransporterForm); + + return ( + <> + + {readOnly ? ( + <> + ) : ( + + )} + ( + + {message} + + )} + /> + + + ); +} diff --git a/client/src/components/Manifest/Handler/TsdfSection.tsx b/client/src/components/Manifest/Tsdf/TsdfSection.tsx similarity index 92% rename from client/src/components/Manifest/Handler/TsdfSection.tsx rename to client/src/components/Manifest/Tsdf/TsdfSection.tsx index 9c814b9ce..a911ce580 100644 --- a/client/src/components/Manifest/Handler/TsdfSection.tsx +++ b/client/src/components/Manifest/Tsdf/TsdfSection.tsx @@ -9,17 +9,17 @@ import { Alert, Col } from 'react-bootstrap'; import { useFormContext } from 'react-hook-form'; interface TsdfSectionProps { - tsdf?: Handler; setupSign: () => void; signAble: boolean; toggleTsdfFormShow: () => void; } -export function TsdfSection({ tsdf, signAble, setupSign, toggleTsdfFormShow }: TsdfSectionProps) { +export function TsdfSection({ signAble, setupSign, toggleTsdfFormShow }: TsdfSectionProps) { const { formState: { errors }, ...manifestForm } = useFormContext(); + const tsdf: Handler | undefined = manifestForm.watch('designatedFacility'); const [readOnly] = useReadOnly(); return ( diff --git a/client/src/components/Manifest/Tsdf/index.ts b/client/src/components/Manifest/Tsdf/index.ts new file mode 100644 index 000000000..2b5e6977a --- /dev/null +++ b/client/src/components/Manifest/Tsdf/index.ts @@ -0,0 +1 @@ +export { TsdfSection } from './TsdfSection'; diff --git a/client/src/components/Manifest/WasteLine/WasteLineForm.tsx b/client/src/components/Manifest/WasteLine/WasteLineForm.tsx index a96829453..0ae515503 100644 --- a/client/src/components/Manifest/WasteLine/WasteLineForm.tsx +++ b/client/src/components/Manifest/WasteLine/WasteLineForm.tsx @@ -32,7 +32,7 @@ export function WasteLineForm({ handleClose, wasteForm, waste, lineNumber }: Was waste?.epaWaste === undefined ? true : waste.epaWaste ); - // @ts-ignore + // @ts-ignore - we do not want a default container type or unit of measure const wasteLineDefaultValues: Partial = waste ? waste : { diff --git a/client/src/components/Manifest/WasteLine/WasteLineSection.tsx b/client/src/components/Manifest/WasteLine/WasteLineSection.tsx new file mode 100644 index 000000000..473febd05 --- /dev/null +++ b/client/src/components/Manifest/WasteLine/WasteLineSection.tsx @@ -0,0 +1,48 @@ +import { ErrorMessage } from '@hookform/error-message'; +import { Manifest } from 'components/Manifest/manifestSchema'; +import { WasteLine } from 'components/Manifest/WasteLine/wasteLineSchema'; +import { WasteLineTable } from 'components/Manifest/WasteLine/WasteLineTable'; +import { HtButton } from 'components/UI'; +import { useReadOnly } from 'hooks/manifest'; +import React from 'react'; +import { Alert } from 'react-bootstrap'; +import { useFieldArray, useFormContext } from 'react-hook-form'; + +interface WasteLineSectionProps { + toggleWlFormShow: () => void; +} + +export function WasteLineSection({ toggleWlFormShow }: WasteLineSectionProps) { + const manifestForm = useFormContext(); + const { errors } = manifestForm.formState; + const allWastes: Array = manifestForm.getValues('wastes'); + const wasteForm = useFieldArray({ + control: manifestForm.control, + name: 'wastes', + }); + const [readOnly] = useReadOnly(); + return ( + <> + + {readOnly ? ( + <> + ) : ( + + )} + ( + + {message} + + )} + /> + + ); +} diff --git a/client/src/features/NewManifest/NewManifest.tsx b/client/src/features/NewManifest/NewManifest.tsx index a67de559d..bbc3abfbc 100644 --- a/client/src/features/NewManifest/NewManifest.tsx +++ b/client/src/features/NewManifest/NewManifest.tsx @@ -101,10 +101,10 @@ export function NewManifest() { selectedSiteType === 'Generator' ? { generator: manifestingSite } : selectedSiteType === 'Transporter' - ? { transporters: [{ ...manifestingSite, order: 1 }] } - : selectedSiteType === 'Tsdf' - ? { designatedFacility: manifestingSite } - : {}; + ? { transporters: [{ ...manifestingSite, order: 1 }] } + : selectedSiteType === 'Tsdf' + ? { designatedFacility: manifestingSite } + : {}; - return ; + return ; } From d500f35c3f2c62fd8ba979f62c0e26817a3c75b4 Mon Sep 17 00:00:00 2001 From: David Graham Date: Mon, 8 Jan 2024 09:09:08 -0500 Subject: [PATCH 2/4] 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)) From b6a5965b1d2f84f8aa5f7e1282f1a2c120659797 Mon Sep 17 00:00:00 2001 From: David Paul Graham Date: Mon, 8 Jan 2024 14:46:52 -0500 Subject: [PATCH 3/4] update UI grid/layout for mobile first viewing for various manifest sections --- .../Manifest/GeneralInfo/GeneralInfoForm.tsx | 10 +++++----- .../Manifest/Transporter/TransporterTable.tsx | 4 ++-- client/src/components/RcraSite/RcraSiteDetails.tsx | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/client/src/components/Manifest/GeneralInfo/GeneralInfoForm.tsx b/client/src/components/Manifest/GeneralInfo/GeneralInfoForm.tsx index ad6cdcccc..5359b6a52 100644 --- a/client/src/components/Manifest/GeneralInfo/GeneralInfoForm.tsx +++ b/client/src/components/Manifest/GeneralInfo/GeneralInfoForm.tsx @@ -26,7 +26,7 @@ export function GeneralInfoForm({ manifestData, isDraft }: GeneralInfoFormProps) const { errors } = manifestForm.formState; return ( <> - + MTN @@ -53,10 +53,10 @@ export function GeneralInfoForm({ manifestData, isDraft }: GeneralInfoFormProps) - + - + {'Created Date '} @@ -75,7 +75,7 @@ export function GeneralInfoForm({ manifestData, isDraft }: GeneralInfoFormProps) - + {'Last Update Date '} @@ -93,7 +93,7 @@ export function GeneralInfoForm({ manifestData, isDraft }: GeneralInfoFormProps) - + {'Shipped Date '} diff --git a/client/src/components/Manifest/Transporter/TransporterTable.tsx b/client/src/components/Manifest/Transporter/TransporterTable.tsx index d459579db..d97652c9b 100644 --- a/client/src/components/Manifest/Transporter/TransporterTable.tsx +++ b/client/src/components/Manifest/Transporter/TransporterTable.tsx @@ -55,10 +55,10 @@ function TransporterTable({ transporters, arrayFieldMethods, setupSign }: Transp return ( - +
{transporter.order}
- {transporter.name} + {transporter.name}
diff --git a/client/src/components/RcraSite/RcraSiteDetails.tsx b/client/src/components/RcraSite/RcraSiteDetails.tsx index d6fd315d9..f227ade43 100644 --- a/client/src/components/RcraSite/RcraSiteDetails.tsx +++ b/client/src/components/RcraSite/RcraSiteDetails.tsx @@ -19,7 +19,7 @@ export function RcraSiteDetails({ handler }: RcraSiteDetailsProps) { return

error

; } return ( -
+

{handler.name}

@@ -32,9 +32,9 @@ export function RcraSiteDetails({ handler }: RcraSiteDetailsProps) { )}
- + -

EPA ID number

+

EPA ID number

{handler.epaSiteId}

@@ -58,7 +58,7 @@ export function RcraSiteDetails({ handler }: RcraSiteDetailsProps) {

- + From 208dd6613ce2d3ba4589db7a1bb8eb3ff89ce64a Mon Sep 17 00:00:00 2001 From: David Paul Graham Date: Tue, 9 Jan 2024 17:40:40 -0500 Subject: [PATCH 4/4] Place front end logic for saving manifest to server and e-Manifest in a custom hook, useSaveManifest. This hook wraps around our RTK query endpoints/hooks. Depending on the manifest saved, it initiates the appropriate action to POST/PUT to the haztrak server or saving to e-Manifest --- .../Manifest/Actions/ManifestEditBtn.tsx | 16 ++-- .../GeneralInfo/ManifestStatusSelect.tsx | 60 ++++++++------ .../GeneralInfo/ManifestTypeSelect.tsx | 68 ++++++++++------ .../src/components/Manifest/ManifestForm.tsx | 71 +++++------------ .../src/features/NewManifest/NewManifest.tsx | 8 +- client/src/hooks/manifest/index.ts | 1 + .../useSaveManifest/useSaveManifest.spec.tsx | 69 ++++++++++++++++ .../useSaveManifest/useSaveManifest.tsx | 79 +++++++++++++++++++ client/src/test-utils/mock/htApiMocks.ts | 4 - client/src/test-utils/mock/index.ts | 1 + client/src/test-utils/mock/manifestMocks.ts | 36 +++++++++ server/apps/site/tests/test_epa_site_views.py | 6 +- 12 files changed, 301 insertions(+), 118 deletions(-) create mode 100644 client/src/hooks/manifest/useSaveManifest/useSaveManifest.spec.tsx create mode 100644 client/src/hooks/manifest/useSaveManifest/useSaveManifest.tsx create mode 100644 client/src/test-utils/mock/manifestMocks.ts diff --git a/client/src/components/Manifest/Actions/ManifestEditBtn.tsx b/client/src/components/Manifest/Actions/ManifestEditBtn.tsx index 2520ee568..6c6096593 100644 --- a/client/src/components/Manifest/Actions/ManifestEditBtn.tsx +++ b/client/src/components/Manifest/Actions/ManifestEditBtn.tsx @@ -12,16 +12,16 @@ interface ManifestEditBtnProps extends ButtonProps {} export function ManifestEditBtn({ children, ...props }: ManifestEditBtnProps) { const navigate = useNavigate(); const { trackingNumber, viewingAsSiteId } = useContext(ManifestContext); - const [readOnly] = useReadOnly(); + const [readOnly, setReadOnly] = useReadOnly(); if (!readOnly) return <>; + + const handleClick = () => { + setReadOnly(false); + navigate(`/site/${viewingAsSiteId}/manifest/${trackingNumber}/edit`); + }; + return ( - navigate(`/site/${viewingAsSiteId}/manifest/${trackingNumber}/edit`)} - > + Edit diff --git a/client/src/components/Manifest/GeneralInfo/ManifestStatusSelect.tsx b/client/src/components/Manifest/GeneralInfo/ManifestStatusSelect.tsx index 829154a3d..e6a0cf2e2 100644 --- a/client/src/components/Manifest/GeneralInfo/ManifestStatusSelect.tsx +++ b/client/src/components/Manifest/GeneralInfo/ManifestStatusSelect.tsx @@ -1,7 +1,7 @@ import { Manifest, ManifestStatus } from 'components/Manifest/manifestSchema'; import { HtForm, InfoIconTooltip } from 'components/UI'; import React from 'react'; -import { useFormContext } from 'react-hook-form'; +import { Controller, useFormContext } from 'react-hook-form'; import { useGetProfileQuery } from 'store'; import { manifest } from 'services'; import Select, { SingleValue } from 'react-select'; @@ -51,30 +51,40 @@ export function ManifestStatusSelect({ readOnly, isDraft }: ManifestStatusFieldP )} - ) => { + if (option) { + setStatus(option.value); + field.onChange(option.value); + } + }} + filterOption={(option) => + // Hide options that are managed by EPA + availableStatuses.includes(option.value as ManifestStatus) || + option.value === 'Scheduled' + } + isOptionDisabled={(option) => + // Disable the 'Scheduled' option if it's not available + option.value === 'Scheduled' && !availableStatuses.includes('Scheduled') + } + classNames={{ + control: () => 'form-select py-0 rounded-3', + }} + components={{ IndicatorSeparator: () => null, DropdownIndicator: () => null }} + /> + )} /> ); diff --git a/client/src/components/Manifest/GeneralInfo/ManifestTypeSelect.tsx b/client/src/components/Manifest/GeneralInfo/ManifestTypeSelect.tsx index 7a466f814..da4e86dd3 100644 --- a/client/src/components/Manifest/GeneralInfo/ManifestTypeSelect.tsx +++ b/client/src/components/Manifest/GeneralInfo/ManifestTypeSelect.tsx @@ -1,18 +1,21 @@ import { Manifest, SubmissionType } from 'components/Manifest/manifestSchema'; import { HtForm } from 'components/UI'; -import React from 'react'; -import Select from 'react-select'; -import { useFormContext } from 'react-hook-form'; +import React, { useState } from 'react'; +import Select, { SingleValue } from 'react-select'; +import { Controller, useFormContext } from 'react-hook-form'; -const submissionTypeOptions: Array<{ value: SubmissionType; label: string }> = [ +interface SubmissionTypeOption { + value: SubmissionType; + label: string; +} + +const submissionTypeOptions: Array = [ { value: 'Hybrid', label: 'Hybrid' }, { value: 'FullElectronic', label: 'Electronic' }, { value: 'DataImage5Copy', label: 'Data + Image' }, { value: 'Image', label: 'Image Only' }, ]; -const DEFAULT_SUBMISSION_TYPE = submissionTypeOptions[0]; - /** uniform hazardous waste manifest type field. */ export function ManifestTypeSelect({ readOnly, @@ -23,31 +26,44 @@ export function ManifestTypeSelect({ }) { const manifestForm = useFormContext(); const generatorCanESign = manifestForm.getValues('generator.canEsign'); + const [submissionType, setSubmissionType] = useState( + manifestForm.getValues('submissionType') || 'Hybrid' + ); + const selectedType = submissionTypeOptions.filter((option) => option.value === submissionType); return ( Type - ) => { + if (option) { + setSubmissionType(option.value); + field.onChange(option.value); + } + }} + filterOption={(option) => + option.label.toLowerCase().includes('electronic') || + option.label.toLowerCase().includes('hybrid') + } + isOptionDisabled={(option) => option.value === 'FullElectronic' && !generatorCanESign} + classNames={{ + control: () => 'form-select py-0 rounded-3', + }} + components={{ IndicatorSeparator: () => null, DropdownIndicator: () => null }} + /> + )} /> ); diff --git a/client/src/components/Manifest/ManifestForm.tsx b/client/src/components/Manifest/ManifestForm.tsx index 498ca1ff2..597c976ab 100644 --- a/client/src/components/Manifest/ManifestForm.tsx +++ b/client/src/components/Manifest/ManifestForm.tsx @@ -15,18 +15,13 @@ import { WasteLine } from 'components/Manifest/WasteLine/wasteLineSchema'; import { WasteLineSection } from 'components/Manifest/WasteLine/WasteLineSection'; import { HtCard, HtForm } from 'components/UI'; import { useUserSiteIds } from 'hooks'; -import { useManifestStatus, useReadOnly } from 'hooks/manifest'; +import { useManifestStatus, useReadOnly, useSaveManifest } from 'hooks/manifest'; import React, { createContext, useEffect, useState } from 'react'; import { Container, Stack } from 'react-bootstrap'; import { FormProvider, SubmitHandler, useFieldArray, useForm } from 'react-hook-form'; import { useNavigate } from 'react-router-dom'; import { toast } from 'react-toastify'; import { manifest } from 'services'; -import { - useCreateManifestMutation, - useSaveEManifestMutation, - useUpdateManifestMutation, -} from 'store'; import { HandlerSearchModal } from './Handler'; import { Manifest, manifestSchema, SiteType } from './manifestSchema'; import { QuickerSignData, QuickerSignModal } from './QuickerSign'; @@ -36,7 +31,7 @@ const defaultValues: Manifest = { transporters: [], wastes: [], status: 'NotAssigned', - submissionType: 'FullElectronic', + submissionType: 'Hybrid', }; export interface ManifestContextType { @@ -100,16 +95,14 @@ export function ManifestForm({ // State related to inter-system communications with EPA's RCRAInfo system const [showSpinner, setShowSpinner] = useState(false); - const toggleShowSpinner = () => setShowSpinner(!showSpinner); const [taskId, setTaskId] = useState(undefined); - const [createManifest, { data: createData, error: createError, isLoading: createIsLoading }] = - useCreateManifestMutation(); - const [ - saveEmanifest, - { data: eManifestResult, error: eManifestError, isLoading: eManifestIsLoading }, - ] = useSaveEManifestMutation(); - const [updateManifest, { data: updateResults, error: updateError, isLoading: updateIsLoading }] = - useUpdateManifestMutation(); + const { + isLoading: saveManifestIsLoading, + saveManifest, + data: saveManifestResult, + error: saveManifestError, + taskId: saveEmanifestTaskId, + } = useSaveManifest(); // React-Hook-Form component state and methods const manifestForm = useForm({ @@ -118,53 +111,31 @@ export function ManifestForm({ }); useEffect(() => { - if (createData) { - if ('manifestTrackingNumber' in createData) { - navigate(`/manifest/${createData.manifestTrackingNumber}/view`); + if (saveManifestResult) { + if ('manifestTrackingNumber' in saveManifestResult) { + navigate( + `/site/${manifestingSiteID}/manifest/${saveManifestResult.manifestTrackingNumber}/view` + ); } } - if (createIsLoading) { + if (saveManifestIsLoading) { setShowSpinner(true); } - if (createError) { + if (saveManifestError) { toast.error('Error creating manifest'); setShowSpinner(false); } - }, [createData, createIsLoading, createError]); + }, [saveManifestResult, saveManifestIsLoading, saveManifestError]); useEffect(() => { - if (updateResults) { - if ('manifestTrackingNumber' in updateResults) { - navigate(`/manifest/${updateResults.manifestTrackingNumber}/view`); - } - } - if (updateIsLoading) { + if (saveEmanifestTaskId) { + setTaskId(saveEmanifestTaskId); setShowSpinner(true); } - if (updateError) { - console.error(updateError); - toast.error('Error Updating manifest'); - setShowSpinner(false); - } - }, [updateResults, updateError, updateIsLoading]); - - useEffect(() => { - if (eManifestResult) { - setTaskId(eManifestResult.taskId); - toggleShowSpinner(); - } - }, [eManifestResult, eManifestIsLoading, eManifestError]); + }, [saveEmanifestTaskId]); const onSubmit: SubmitHandler = (data: Manifest) => { - if (data.status === 'NotAssigned') { - if (data.manifestTrackingNumber?.endsWith('DFT')) { - updateManifest({ mtn: data.manifestTrackingNumber, manifest: data }); - } else { - createManifest(data); - } - } else { - saveEmanifest(data); - } + saveManifest(data); }; // Generator component state and methods diff --git a/client/src/features/NewManifest/NewManifest.tsx b/client/src/features/NewManifest/NewManifest.tsx index 487041390..9b23201a4 100644 --- a/client/src/features/NewManifest/NewManifest.tsx +++ b/client/src/features/NewManifest/NewManifest.tsx @@ -114,5 +114,11 @@ export function NewManifest() { ? { designatedFacility: manifestingSite } : {}; - return ; + return ( + + ); } diff --git a/client/src/hooks/manifest/index.ts b/client/src/hooks/manifest/index.ts index 0532da567..38f4fb3c9 100644 --- a/client/src/hooks/manifest/index.ts +++ b/client/src/hooks/manifest/index.ts @@ -1,2 +1,3 @@ export { useManifestStatus } from './useManifestStatus/useManifestStatus'; export { useReadOnly } from './useReadOnly/useReadOnly'; +export { useSaveManifest } from './useSaveManifest/useSaveManifest'; diff --git a/client/src/hooks/manifest/useSaveManifest/useSaveManifest.spec.tsx b/client/src/hooks/manifest/useSaveManifest/useSaveManifest.spec.tsx new file mode 100644 index 000000000..414bb76ef --- /dev/null +++ b/client/src/hooks/manifest/useSaveManifest/useSaveManifest.spec.tsx @@ -0,0 +1,69 @@ +import '@testing-library/jest-dom'; +import { cleanup, waitFor } from '@testing-library/react'; +import React from 'react'; +import { renderWithProviders, screen } from 'test-utils'; +import { afterAll, afterEach, beforeAll, describe, expect, it } from 'vitest'; +import { useSaveManifest } from './useSaveManifest'; +import { Manifest } from 'components/Manifest'; +import userEvent from '@testing-library/user-event'; +import { createMockManifest } from 'test-utils/fixtures'; +import { setupServer } from 'msw/node'; +import { manifestMocks } from 'test-utils/mock'; + +const TestComponent = ({ manifest }: { manifest?: Manifest }) => { + const { data, isLoading, error, taskId, saveManifest } = useSaveManifest(); + return ( + <> +

loading: {isLoading ? 'yes' : 'no'}

+

error: {error ? 'yes' : 'no'}

+

task: {taskId ? taskId : 'empty'}

+

data: {data ? 'defined' : 'undefined'}

+

mtn: {data?.manifestTrackingNumber ?? 'undefined'}

+ + + ); +}; + +const server = setupServer(...manifestMocks); +beforeAll(() => server.listen()); +afterAll(() => server.close()); +afterEach(() => cleanup()); + +describe('useSaveManifest', () => { + it('isLoading is false, error is null, and taskId is null by default ', () => { + renderWithProviders(); + expect(screen.getByText(/^loading: no/i)).toBeInTheDocument(); + expect(screen.getByText(/^error: no/i)).toBeInTheDocument(); + expect(screen.getByText(/^task: empty/i)).toBeInTheDocument(); + }); + it('request are made to create a draft manifest', async () => { + renderWithProviders( + + ); + await userEvent.click(screen.getByText(/^save$/i)); + await waitFor(() => expect(screen.getByText(/^loading: no/i)).toBeInTheDocument()); + expect(screen.getByText(/^data: defined/i)).toBeInTheDocument(); + expect(screen.getByText(/^mtn: [0-9]{9}DFT/i)).toBeInTheDocument(); + }); + it('request to update a draft manifest if MTN already exists', async () => { + const existingMTN = '123456789DFT'; + renderWithProviders( + + ); + expect(screen.getByText(/^loading: no/i)).toBeInTheDocument(); + await userEvent.click(screen.getByText(/^save$/i)); + await waitFor(() => expect(screen.getByText(/^loading: no/i)).toBeInTheDocument()); + expect(screen.getByText(/^data: defined/i)).toBeInTheDocument(); + expect(screen.getByText(`mtn: ${existingMTN}`)).toBeInTheDocument(); + }); +}); diff --git a/client/src/hooks/manifest/useSaveManifest/useSaveManifest.tsx b/client/src/hooks/manifest/useSaveManifest/useSaveManifest.tsx new file mode 100644 index 000000000..0dd78b444 --- /dev/null +++ b/client/src/hooks/manifest/useSaveManifest/useSaveManifest.tsx @@ -0,0 +1,79 @@ +import { useEffect, useState } from 'react'; +import { Manifest } from 'components/Manifest'; +import { + useCreateManifestMutation, + useSaveEManifestMutation, + useUpdateManifestMutation, +} from 'store'; + +/** + * encapsulates the logic for making requests to the back end to save a manifest (create, update, or save to e-Manifest) + * */ +export function useSaveManifest() { + const [data, setData] = useState(); + const [isLoading, setIsLoading] = useState(false); + const [taskId, setTaskId] = useState(); + const [error, setError] = useState(null); + + const [createManifest, { data: createData, error: createError, isLoading: createIsLoading }] = + useCreateManifestMutation(); + + const [updateManifest, { data: updateResults, error: updateError, isLoading: updateIsLoading }] = + useUpdateManifestMutation(); + + const [ + saveEmanifest, + { data: eManifestResult, error: eManifestError, isLoading: eManifestIsLoading }, + ] = useSaveEManifestMutation(); + + useEffect(() => { + if (eManifestResult) { + setTaskId(eManifestResult.taskId); + } + }, [eManifestResult]); + + // create or update draft manifest results + useEffect(() => { + if (createData) setData(createData); + if (updateResults) setData(updateResults); + setIsLoading(false); + }, [createData, updateResults]); + + // create or update draft manifest errors, and e-Manifest errors + useEffect(() => { + if (createError) setError(createError); + if (updateError) setError(updateError); + if (eManifestError) setError(eManifestError); + }, [createError, updateError, eManifestError]); + + useEffect(() => { + setIsLoading(updateIsLoading); + }, [updateIsLoading]); + + useEffect(() => { + setIsLoading(eManifestIsLoading); + }, [eManifestIsLoading]); + + useEffect(() => { + setIsLoading(createIsLoading); + }, [createIsLoading]); + + const saveManifest = (manifest?: Manifest) => { + setIsLoading(true); + if (!manifest) { + setIsLoading(false); + return; + } + if (manifest.status === 'NotAssigned') { + if (manifest.manifestTrackingNumber?.endsWith('DFT')) { + updateManifest({ mtn: manifest.manifestTrackingNumber, manifest }); + } else { + createManifest(manifest); + } + } else { + saveEmanifest(manifest); + } + }; + + return { data, isLoading, saveManifest, taskId, error } as const; +} diff --git a/client/src/test-utils/mock/htApiMocks.ts b/client/src/test-utils/mock/htApiMocks.ts index a17dcfe9b..8992c6696 100644 --- a/client/src/test-utils/mock/htApiMocks.ts +++ b/client/src/test-utils/mock/htApiMocks.ts @@ -15,10 +15,6 @@ export const htApiMocks = [ http.get(`${API_BASE_URL}/api/site/${mockEpaId}`, (info) => { return HttpResponse.json(mockSites[0], { status: 200 }); }), - /** mock Manifest*/ - http.get(`${API_BASE_URL}/api/rcra/manifest/${mockMTN}`, (info) => { - return HttpResponse.json(createMockManifest(), { status: 200 }); - }), /** list of manifests ('My Manifests' feature and a site's manifests)*/ http.get(`${API_BASE_URL}/api/rcra/mtn*`, (info) => { const mockManifestArray = [ diff --git a/client/src/test-utils/mock/index.ts b/client/src/test-utils/mock/index.ts index 95cad9572..8987a73a6 100644 --- a/client/src/test-utils/mock/index.ts +++ b/client/src/test-utils/mock/index.ts @@ -1,3 +1,4 @@ export { userApiMocks } from './userApiMocks'; export { htApiMocks } from './htApiMocks'; export { wasteApiMocks } from './wasteApiMocks'; +export { manifestMocks } from './manifestMocks'; diff --git a/client/src/test-utils/mock/manifestMocks.ts b/client/src/test-utils/mock/manifestMocks.ts new file mode 100644 index 000000000..04bbd1d7c --- /dev/null +++ b/client/src/test-utils/mock/manifestMocks.ts @@ -0,0 +1,36 @@ +import { http, HttpResponse } from 'msw'; +import { createMockManifest } from '../fixtures'; +import { Manifest } from 'components/Manifest'; + +export const API_BASE_URL = import.meta.env.VITE_HT_API_URL; +const mockMTN = createMockManifest().manifestTrackingNumber; + +export const manifestMocks = [ + /** mock GET Manifest*/ + http.get(`${API_BASE_URL}/api/rcra/manifest/${mockMTN}`, (info) => { + return HttpResponse.json(createMockManifest(), { status: 200 }); + }), + /** Mock create local Manifests*/ + http.post(`${API_BASE_URL}/api/rcra/manifest`, async (info) => { + let bodyManifest = (await info.request.json()) as Manifest; + if (!bodyManifest.manifestTrackingNumber) + bodyManifest.manifestTrackingNumber = `${Math.floor(Math.random() * 1000000000)}DFT`; + return HttpResponse.json(bodyManifest, { status: 200 }); + }), + /** Mock update local Manifests*/ + http.put(`${API_BASE_URL}/api/rcra/manifest/:mtn`, async (info) => { + const { mtn } = info.params; + let bodyManifest = (await info.request.json()) as Manifest; + if (bodyManifest.manifestTrackingNumber !== mtn) + return HttpResponse.json(null, { status: 400 }); + return HttpResponse.json(bodyManifest, { status: 200 }); + }), + /** list of manifests ('My Manifests' feature and a site's manifests)*/ + http.get(`${API_BASE_URL}/api/rcra/mtn*`, (info) => { + const mockManifestArray = [ + createMockManifest(), + createMockManifest({ manifestTrackingNumber: '987654321ELC', status: 'Pending' }), + ]; + return HttpResponse.json(mockManifestArray, { status: 200 }); + }), +]; diff --git a/server/apps/site/tests/test_epa_site_views.py b/server/apps/site/tests/test_epa_site_views.py index d26e26118..d8affe8d4 100644 --- a/server/apps/site/tests/test_epa_site_views.py +++ b/server/apps/site/tests/test_epa_site_views.py @@ -8,9 +8,7 @@ class TestEpaSiteView: - """ - Tests the for the endpoints related to the handlers - """ + """Handler endpoints test suite""" URL = "/api/rcra/handler" @@ -23,7 +21,7 @@ def generator(self, rcra_site_factory): return rcra_site_factory() def test_endpoint_returns_json_with_rcra_site(self, client, generator): - response: Response = client.get(f"{self.URL}/{generator.pk}") + response: Response = client.get(f"{self.URL}/{generator.epa_id}") assert response.headers["Content-Type"] == "application/json" assert response.status_code == status.HTTP_200_OK assert response.data["epaSiteId"] == generator.epa_id