Skip to content

Commit

Permalink
Add user site to new manifests (#464)
Browse files Browse the repository at this point in the history
* SiteTypeSelect and SiteSelect component

* serialize site details for RcraSites in a user's RcraProfile on backend

* restructure RcraProfile redux Slice to hold RcraSite details

* logic for using a site from the user RcraProfile slice as a site on the manifest.

* selectSiteById redux selector and tests

* getUserSites redux selector, test, and implementation in SiteSelect component

* replace simple Manifest TS interface with zod schema type, add additionalInfoSchema to manifestSchema

* Type SiteType (replaces HandlerType for clarity) and RcraSiteType which follows the RCRAInfo naming convention

* add dispatch to get user RcraProfile in App.tsx so this available to all virtual DOM nodes as soon as the user logs in
  • Loading branch information
dpgraham4401 authored May 5, 2023
1 parent 71a8331 commit 26a5217
Show file tree
Hide file tree
Showing 33 changed files with 644 additions and 307 deletions.
15 changes: 13 additions & 2 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,26 @@ import { Manifest } from 'features/manifest';
import { Notifications } from 'features/notifications';
import { Profile } from 'features/profile';
import { Sites } from 'features/haztrakSite';
import React, { ReactElement } from 'react';
import React, { ReactElement, useEffect } from 'react';
import { Button, Container } from 'react-bootstrap';
import { Route, Routes, useNavigate } from 'react-router-dom';
import { RootState, useAppSelector } from 'store';
import { RootState, useAppDispatch, useAppSelector } from 'store';
import './App.scss';
import { getProfile } from 'store/rcraProfileSlice';
import { RcraProfileState } from 'store/rcraProfileSlice/rcraProfile.slice';

function App(): ReactElement {
const { user } = useAppSelector((state: RootState) => state.user);
const profile = useAppSelector<RcraProfileState>((state) => state.rcraProfile);
const navigate = useNavigate();
const dispatch = useAppDispatch();
console.log('user', user);

useEffect(() => {
if (user) {
dispatch(getProfile());
}
}, [profile.user]);

return (
<div className="App">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { HtForm } from 'components/Ht';
import React from 'react';
import { Button, Col, Form, Row } from 'react-bootstrap';
import { useFieldArray, useFormContext } from 'react-hook-form';
import { Manifest } from 'types/manifest';
import { Manifest } from 'components/Manifest';
import { WasteLine } from 'components/Manifest/WasteLine';

interface AdditionalFormProps {
Expand Down
12 changes: 6 additions & 6 deletions client/src/components/AdditionalInfo/additionalInfoSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@ import { z } from 'zod';
/**
* Comments which can be 'attached' to a handler EPA ID number
*/
const additionalInfoCommentSchema = z.object({
export const additionalInfoCommentSchema = z.object({
label: z.string().optional(),
description: z.string().optional(),
handlerId: z.string().optional(),
});

const additionalInfoSchema = z.object({
originalManifestTrackingNumbers: z.string().array(),
newManifestDestination: z.enum(['Tsdf', 'OriginalGenerator']),
consentNumber: z.string(),
comments: additionalInfoCommentSchema.array(),
export const additionalInfoSchema = z.object({
originalManifestTrackingNumbers: z.string().array().optional(),
newManifestDestination: z.enum(['Tsdf', 'OriginalGenerator']).optional(),
consentNumber: z.string().optional(),
comments: additionalInfoCommentSchema.array().optional(),
});

export type AdditionalInfo = z.infer<typeof additionalInfoSchema>;
Expand Down
4 changes: 2 additions & 2 deletions client/src/components/Manifest/Address/AddressForm.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import '@testing-library/jest-dom';
import { HandlerType } from 'components/Manifest/manifestSchema';
import { siteType } from 'components/Manifest/manifestSchema';
import React from 'react';
import { cleanup, renderWithProviders, screen } from 'test-utils';
import { AddressForm } from './AddressForm';
Expand All @@ -11,7 +11,7 @@ afterEach(() => {
describe('AddressForm', () => {
test('renders with basic information inputs', () => {
renderWithProviders(
<AddressForm addressType={'siteAddress'} handlerType={HandlerType.enum.generator} />
<AddressForm addressType={'siteAddress'} handlerType={siteType.enum.generator} />
);
expect(screen.getByText(/Street Name/i)).toBeInTheDocument();
expect(screen.getByText(/Street Number/i)).toBeInTheDocument();
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/Manifest/Contact/ContactForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { PhoneForm } from 'components/Manifest/Contact/PhoneForm';
import React from 'react';
import { useFormContext } from 'react-hook-form';
import { Col, Form, Row } from 'react-bootstrap';
import { Manifest } from 'types/manifest';
import { Manifest } from 'components/Manifest';
import { HtForm } from 'components/Ht';

interface ContactFormProps {
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/Manifest/Contact/PhoneForm.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useState } from 'react';
import { useFormContext } from 'react-hook-form';
import { Col, Form, Row } from 'react-bootstrap';
import { Manifest } from 'types/manifest';
import { Manifest } from 'components/Manifest';
import { HtForm } from 'components/Ht';

interface ContactFormProps {
Expand Down
4 changes: 2 additions & 2 deletions client/src/components/Manifest/Handler/AddHandler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import { HandlerSearchForm } from 'components/Manifest/Handler/index';
import { RcraSite } from 'components/RcraSite';
import React from 'react';
import { Col, Row } from 'react-bootstrap';
import { Manifest } from '../manifestSchema';
import { Manifest, SiteType } from '../manifestSchema';
import { UseFieldArrayAppend } from 'react-hook-form';

interface Props {
handleClose: () => void;
show: boolean | undefined;
handlerType: 'generator' | 'designatedFacility' | 'transporter';
handlerType: SiteType;
currentTransporters?: Array<RcraSite>;
appendTransporter?: UseFieldArrayAppend<Manifest, 'transporters'>;
}
Expand Down
12 changes: 5 additions & 7 deletions client/src/components/Manifest/Handler/HandlerForm.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import '@testing-library/jest-dom';
import { fireEvent } from '@testing-library/react';
import { HandlerType } from 'components/Manifest/manifestSchema';
import { siteType } from 'components/Manifest/manifestSchema';
import React from 'react';
import { cleanup, renderWithProviders, screen } from 'test-utils';
import { HandlerForm } from './HandlerForm';
Expand All @@ -11,18 +11,16 @@ afterEach(() => {

describe('HandlerForm', () => {
test('renders with basic information inputs', () => {
renderWithProviders(<HandlerForm handlerType={HandlerType.enum.generator} />);
renderWithProviders(<HandlerForm handlerType={siteType.enum.generator} />);
expect(screen.getByText(/Site Name/i)).toBeInTheDocument();
expect(
screen.getByText(`${HandlerType.enum.generator} ID`, { exact: false })
).toBeInTheDocument();
expect(screen.getByText(`${siteType.enum.generator} ID`, { exact: false })).toBeInTheDocument();
});
test('initially includes 1 AddressForm for site address', async () => {
renderWithProviders(<HandlerForm handlerType={HandlerType.enum.generator} />);
renderWithProviders(<HandlerForm handlerType={siteType.enum.generator} />);
expect(await screen.findAllByLabelText(/Street Number/i).then((v) => v.length)).toBe(1);
});
test('renders 2 AddressForms for site and mailing when checked', async () => {
renderWithProviders(<HandlerForm handlerType={HandlerType.enum.generator} />);
renderWithProviders(<HandlerForm handlerType={siteType.enum.generator} />);
fireEvent.click(screen.getByLabelText(/Separate Mailing Address/i));
expect(await screen.findAllByText(/Street Number/i).then((v) => v.length)).toBe(2);
});
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/Manifest/Handler/HandlerForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { AddressForm } from 'components/Manifest/Address';
import { ReactElement, useEffect, useState } from 'react';
import { Col, Form, Row } from 'react-bootstrap';
import { useFormContext } from 'react-hook-form';
import { Manifest } from 'types/manifest';
import { Manifest } from 'components/Manifest';

interface HandlerFormProps {
handlerType: 'generator' | 'designatedFacility';
Expand Down
4 changes: 2 additions & 2 deletions client/src/components/Manifest/Handler/HandlerSearchForm.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { HtForm } from 'components/Ht';
import { HandlerTypeEnum, Manifest, Transporter } from 'components/Manifest/manifestSchema';
import { SiteType, Manifest, Transporter } from 'components/Manifest/manifestSchema';
import React, { useState } from 'react';
import { Button } from 'react-bootstrap';
import {
Expand All @@ -15,7 +15,7 @@ import { htApi } from 'services';

interface Props {
handleClose: () => void;
handlerType: HandlerTypeEnum;
handlerType: SiteType;
currentTransporters?: Array<RcraSite>;
appendTransporter?: UseFieldArrayAppend<Manifest, 'transporters'>;
}
Expand Down
21 changes: 14 additions & 7 deletions client/src/components/Manifest/ManifestForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ const defaultValues: Manifest = {

interface ManifestFormProps {
readOnly?: boolean;
manifestData?: Manifest;
siteId?: string;
manifestData?: Partial<Manifest>;
manifestingSiteID?: string;
mtn?: string;
}

Expand All @@ -37,13 +37,20 @@ interface ManifestFormProps {
*/
export function ManifestForm({
readOnly,
manifestData = defaultValues,
siteId,
manifestData,
manifestingSiteID,
mtn,
}: ManifestFormProps) {
// methods and top level state related to the manifest to be rendered
let values: Manifest = defaultValues;
if (manifestData) {
values = {
...defaultValues,
...manifestData,
};
}
const manifestMethods = useForm<Manifest>({
values: manifestData,
values: values,
resolver: zodResolver(manifestSchema),
});
const {
Expand Down Expand Up @@ -475,7 +482,7 @@ export function ManifestForm({
if (!mtn) {
navigate(-1);
} else {
navigate(`/site/${siteId}/manifest/${mtn}/view`);
navigate(`/site/${manifestingSiteID}/manifest/${mtn}/view`);
}
}}
>
Expand All @@ -484,7 +491,7 @@ export function ManifestForm({
<Button
variant="primary"
disabled={!readOnly}
onClick={() => navigate(`/site/${siteId}/manifest/${mtn}/edit`)}
onClick={() => navigate(`/site/${manifestingSiteID}/manifest/${mtn}/edit`)}
>
Edit
</Button>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { faFileSignature } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { HtForm } from 'components/Ht';
import { Handler } from 'components/Manifest/manifestSchema';
import { Handler, RcraSiteType } from 'components/Manifest/manifestSchema';
import React from 'react';
import { Button, Col, Container, Form, ListGroup, Row } from 'react-bootstrap';
import { SubmitHandler, useForm } from 'react-hook-form';
Expand All @@ -16,7 +16,7 @@ import { Transporter } from 'components/Manifest/Transporter';
interface QuickerSignProps {
mtn: Array<string>;
mtnHandler: Handler | Transporter;
siteType: 'Generator' | 'Transporter' | 'Tsdf';
siteType: RcraSiteType;
handleClose?: () => void;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { HtModal } from 'components/Ht';
import { Handler } from 'components/Manifest/manifestSchema';
import { Handler, RcraSiteType } from 'components/Manifest/manifestSchema';
import React from 'react';
import { QuickerSignForm } from './QuickerSignForm';

Expand All @@ -8,7 +8,7 @@ interface QuickerSignModalProps {
show: boolean;
mtn?: Array<string>;
mtnHandler?: Handler;
siteType: 'Generator' | 'Transporter' | 'Tsdf';
siteType: RcraSiteType;
}

export function QuickerSignModal({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { faFeather } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Handler } from 'components/Manifest/manifestSchema';
import { Handler, RcraSiteType } from 'components/Manifest/manifestSchema';
import React from 'react';
import { ButtonProps } from 'react-bootstrap';
import { RcraApiUserBtn } from 'components/buttons';

interface QuickerSignData {
handler: Handler | undefined;
siteType: 'Generator' | 'Transporter' | 'Tsdf';
siteType: RcraSiteType;
}

interface QuickerSignModalBtnProps extends ButtonProps {
siteType: 'Generator' | 'Transporter' | 'Tsdf';
siteType: RcraSiteType;
mtnHandler?: Handler;
handleClick: (data: QuickerSignData) => void;
iconOnly?: boolean;
Expand Down
40 changes: 40 additions & 0 deletions client/src/components/Manifest/SiteSelect/SiteSelect.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { screen } from '@testing-library/react';
import { SiteSelect } from 'components/Manifest/SiteSelect/SiteSelect';
import React, { useState } from 'react';
import { useForm } from 'react-hook-form';
import { renderWithProviders } from 'test-utils';
import { createMockSite } from 'test-utils/fixtures';
import { createMockPermission } from 'test-utils/fixtures/mockHandler';

function TestComponent() {
const [selected, setSelected] = useState();
const { control } = useForm();
// @ts-ignore
return <SiteSelect selectedSite={selected} setSelectedSite={setSelected} control={control} />;
}

describe('SiteSelect', () => {
test('renders', () => {
const mySite = createMockSite();
renderWithProviders(<TestComponent />, {
preloadedState: {
rcraProfile: {
user: 'username',
phoneNumber: '1231231234',
apiUser: false,
rcraSites: {
VATESTGEN001: {
site: mySite,
permissions: createMockPermission(),
},
VATEST00001: {
site: mySite,
permissions: createMockPermission(),
},
},
},
},
});
expect(screen.queryByTestId('siteSelect')).toBeDefined();
});
});
51 changes: 51 additions & 0 deletions client/src/components/Manifest/SiteSelect/SiteSelect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { HtForm } from 'components/Ht';
import { RcraSite } from 'components/RcraSite';
import React from 'react';
import { Control, Controller } from 'react-hook-form';
import Select from 'react-select';
import { RootState, useAppSelector } from 'store';
import { getUserSites, RcraProfileState } from 'store/rcraProfileSlice/rcraProfile.slice';

interface SiteSelectProps<T> {
control: Control;
selectedSite: T | undefined;
setSelectedSite: (option: T | undefined) => void;
}

export function SiteSelect({
control,
selectedSite,
setSelectedSite,
}: SiteSelectProps<RcraSite | undefined | null>) {
const rcraSite = useAppSelector(getUserSites);
const siteOptions = rcraSite?.map((site) => site.site.handler);
return (
<>
<HtForm.Label htmlFor="site">Site</HtForm.Label>
<Controller
control={control}
name="site"
data-testid="siteSelect"
render={({ field }) => (
<Select
id="site"
{...field} // object {name, onChange(), onBLur(), value, ref}
openMenuOnFocus={false}
onChange={setSelectedSite}
components={{ IndicatorSeparator: () => null }}
options={siteOptions}
noOptionsMessage={() => 'No Sites found'}
value={selectedSite}
getOptionLabel={(option) => `${option.epaSiteId} -- ${option.name}`}
getOptionValue={(option) => option.epaSiteId}
isSearchable
isClearable
classNames={{
control: () => 'form-control p-0 rounded-2',
}}
/>
)}
/>
</>
);
}
23 changes: 23 additions & 0 deletions client/src/components/Manifest/SiteSelect/SiteTypeSelect.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { screen } from '@testing-library/react';
import { SiteSelect } from 'components/Manifest/SiteSelect/SiteSelect';
import { SiteTypeSelect } from 'components/Manifest/SiteSelect/SiteTypeSelect';
import React, { useState } from 'react';
import { useForm } from 'react-hook-form';
import { renderWithProviders } from 'test-utils';
import { createMockSite } from 'test-utils/fixtures';
import { createMockPermission } from 'test-utils/fixtures/mockHandler';

function TestComponent() {
const [mockSiteType, setMockSiteType] = useState();
const { control } = useForm();
// @ts-ignore
return <SiteTypeSelect siteType={mockSiteType} setSiteType={setMockSiteType} control={control} />;
}

describe('SiteTypeSelect', () => {
test('renders', () => {
const mySite = createMockSite();
renderWithProviders(<TestComponent />);
expect(screen.queryByTestId('siteTypeSelect')).toBeDefined();
});
});
Loading

0 comments on commit 26a5217

Please sign in to comment.