From 4c79a599f41c39b7c51d768f32a3d0879d5934e3 Mon Sep 17 00:00:00 2001 From: AMEIJER1 <141779443+AMEIJER1@users.noreply.github.com> Date: Tue, 10 Dec 2024 18:49:33 -0800 Subject: [PATCH] Add Tooltips to Form Fields (#1427) * example tooltip next to focal species form field on survey form * permits and survey participation * tooltipping ctd * tooltip ctd1 * more tooltipping * tooltip testing * helpbutton for typography stack * help button stack for box items * fixing help button formatting * tooltipping on edit survey page * make fix * make fix * make fix * wip: update text * move tooltips into autocompletes and add helptext * update survey form tooltips * Fix app tests --------- Co-authored-by: Macgregor Aubertin-Young Co-authored-by: Macgregor Aubertin-Young <108430771+mauberti-bc@users.noreply.github.com> Co-authored-by: Nick Phura --- .../components/buttons/HelpButtonStack.tsx | 19 +++ .../components/buttons/HelpButtonTooltip.tsx | 35 ++---- .../components/fields/AutocompleteField.tsx | 4 + app/src/components/fields/CustomTextField.tsx | 34 +++++- .../fields/MultiAutocompleteField.tsx | 11 ++ .../MultiAutocompleteFieldVariableSize.tsx | 11 ++ .../components/fields/StartEndDateFields.tsx | 13 +- .../fields/SystemUserAutocompleteField.tsx | 49 ++++++-- .../components/SpeciesAutocompleteField.tsx | 11 ++ .../components/ProjectDetailsForm.tsx | 1 + .../components/ProjectObjectivesForm.test.tsx | 10 +- .../components/ProjectObjectivesForm.tsx | 1 + .../components/ProjectUserForm.test.tsx | 4 +- .../projects/components/ProjectUserForm.tsx | 103 ++-------------- .../components/AnimalFormContainer.tsx | 3 +- .../ecological-units/EcologicalUnitsForm.tsx | 41 ++++--- .../AnimalGeneralInformationForm.tsx | 87 ++++++++------ .../components/agreements/AgreementsForm.tsx | 14 ++- .../agreements/ProprietaryDataForm.tsx | 9 +- .../GeneralInformationForm.tsx | 2 + .../locations/SurveyAreaMapControl.tsx | 11 +- .../participants/SurveyUserForm.test.tsx | 4 +- .../participants/SurveyUserForm.tsx | 112 +++--------------- .../SamplingStrategyForm.tsx | 13 +- .../SurveySiteSelectionForm.tsx | 1 + .../species/components/FocalSpeciesForm.tsx | 1 + .../features/surveys/edit/EditSurveyForm.tsx | 17 ++- .../methods/SamplingMethodFormContainer.tsx | 7 +- .../site-groupings/SamplingBlockForm.tsx | 7 +- .../site-groupings/SamplingStratumForm.tsx | 7 +- 30 files changed, 317 insertions(+), 325 deletions(-) create mode 100644 app/src/components/buttons/HelpButtonStack.tsx diff --git a/app/src/components/buttons/HelpButtonStack.tsx b/app/src/components/buttons/HelpButtonStack.tsx new file mode 100644 index 0000000000..9e7bd7f895 --- /dev/null +++ b/app/src/components/buttons/HelpButtonStack.tsx @@ -0,0 +1,19 @@ +import Stack, { StackProps } from '@mui/material/Stack'; +import HelpButtonTooltip from 'components/buttons/HelpButtonTooltip'; +import { PropsWithChildren } from 'react'; + +interface IHelpButtonStackProps extends StackProps { + helpText: string; +} + +const HelpButtonStack = (props: PropsWithChildren) => { + const { helpText, children, ...stackProps } = props; + return ( + + {children} + + + ); +}; + +export default HelpButtonStack; diff --git a/app/src/components/buttons/HelpButtonTooltip.tsx b/app/src/components/buttons/HelpButtonTooltip.tsx index a04b5d66d3..332779ed66 100644 --- a/app/src/components/buttons/HelpButtonTooltip.tsx +++ b/app/src/components/buttons/HelpButtonTooltip.tsx @@ -4,11 +4,10 @@ import Box from '@mui/material/Box'; import IconButton from '@mui/material/IconButton'; import Tooltip from '@mui/material/Tooltip'; import Zoom from '@mui/material/Zoom'; -import { ReactNode } from 'react'; +import { useState } from 'react'; interface HelpButtonTooltipProps { content: string; - children?: ReactNode; iconSx?: object; } @@ -19,31 +18,23 @@ interface HelpButtonTooltipProps { * @param {HelpButtonTooltipProps} * @return {*} */ -//TODO: Update positioning of the tooltip to be more dynamic (Add Animal form) -const HelpButtonTooltip = ({ content, children, iconSx }: HelpButtonTooltipProps) => { +const HelpButtonTooltip = ({ content, iconSx }: HelpButtonTooltipProps) => { + const [renderTooltip, setRenderTooltip] = useState(false); + return ( - {children} + {/* Tooltip should always be there, but only show when hovering */} + {/* IconButton is always displayed */} setRenderTooltip(true)} + onMouseLeave={() => setRenderTooltip(false)} sx={{ - position: 'absolute', - top: '8px', - right: '8px', color: '#38598A', ...iconSx }}> diff --git a/app/src/components/fields/AutocompleteField.tsx b/app/src/components/fields/AutocompleteField.tsx index 5e39736b3b..2324834ffc 100644 --- a/app/src/components/fields/AutocompleteField.tsx +++ b/app/src/components/fields/AutocompleteField.tsx @@ -4,6 +4,7 @@ import CircularProgress from '@mui/material/CircularProgress'; import grey from '@mui/material/colors/grey'; import TextField, { TextFieldProps } from '@mui/material/TextField'; import Typography from '@mui/material/Typography'; +import HelpButtonTooltip from 'components/buttons/HelpButtonTooltip'; import { useFormikContext } from 'formik'; import get from 'lodash-es/get'; import { SyntheticEvent } from 'react'; @@ -27,6 +28,7 @@ export interface IAutocompleteField { showValue?: boolean; disableClearable?: boolean; optionFilter?: 'value' | 'label'; // used to filter existing/ set data for the AutocompleteField, defaults to value in getExistingValue function + helpText?: string; getOptionDisabled?: (option: IAutocompleteFieldOption) => boolean; onChange?: (event: SyntheticEvent, option: IAutocompleteFieldOption | null) => void; renderOption?: (params: React.HTMLAttributes, option: IAutocompleteFieldOption) => React.ReactNode; @@ -64,6 +66,7 @@ const AutocompleteField = (props: IAutocompleteField< blurOnSelect handleHomeEndKeys id={props.id} + fullWidth data-testid={props.id} value={getExistingValue(get(values, props.name))} options={props.options} @@ -137,6 +140,7 @@ const AutocompleteField = (props: IAutocompleteField< endAdornment: ( <> {props.loading ? : null} + {props.helpText && } {params.InputProps.endAdornment} ) diff --git a/app/src/components/fields/CustomTextField.tsx b/app/src/components/fields/CustomTextField.tsx index 4f50ed4ae3..04c83c142a 100644 --- a/app/src/components/fields/CustomTextField.tsx +++ b/app/src/components/fields/CustomTextField.tsx @@ -1,4 +1,6 @@ +import InputAdornment from '@mui/material/InputAdornment'; import TextField from '@mui/material/TextField'; +import HelpButtonTooltip from 'components/buttons/HelpButtonTooltip'; import { useFormikContext } from 'formik'; import get from 'lodash-es/get'; export interface ICustomTextField { @@ -30,6 +32,13 @@ export interface ICustomTextField { * @memberof ICustomTextField */ maxLength?: number; + /** + * Optional help text to be displayed in a tooltip + * + * @type {string} + * @memberof ICustomTextField + */ + helpText?: string; /* * TODO: Needed fix: Add correct hardcoded type * Note: TextFieldProps causes build compile issue @@ -41,7 +50,7 @@ export interface ICustomTextField { const CustomTextField = (props: React.PropsWithChildren) => { const { touched, errors, values, handleChange, handleBlur } = useFormikContext(); - const { name, label, other, placeholder } = props; + const { name, label, other, placeholder, helpText } = props; return ( ) => { label={label} id={name} placeholder={placeholder} - inputProps={{ 'data-testid': name, maxLength: props.maxLength || undefined }} // targets the internal input rather than the react component + inputProps={{ + 'data-testid': name, + maxLength: props.maxLength || undefined + }} + InputProps={{ + endAdornment: helpText && ( + + + + ) + }} onChange={handleChange} onBlur={handleBlur} variant="outlined" @@ -57,6 +76,17 @@ const CustomTextField = (props: React.PropsWithChildren) => { fullWidth={true} error={get(touched, name) && Boolean(get(errors, name))} helperText={get(touched, name) && get(errors, name)} + sx={{ + '& .MuiInputAdornment-root': { + mr: 0, + height: '100%', + alignSelf: 'flex-start', + position: 'absolute', + top: 12, + right: 12 + }, + ...other?.sx + }} {...other} /> ); diff --git a/app/src/components/fields/MultiAutocompleteField.tsx b/app/src/components/fields/MultiAutocompleteField.tsx index 486d349a76..e26c903fc2 100644 --- a/app/src/components/fields/MultiAutocompleteField.tsx +++ b/app/src/components/fields/MultiAutocompleteField.tsx @@ -10,6 +10,7 @@ import Checkbox from '@mui/material/Checkbox'; import Chip from '@mui/material/Chip'; import ListItemText from '@mui/material/ListItemText'; import TextField from '@mui/material/TextField'; +import HelpButtonTooltip from 'components/buttons/HelpButtonTooltip'; import { useFormikContext } from 'formik'; import get from 'lodash-es/get'; import { useEffect, useState } from 'react'; @@ -27,6 +28,7 @@ export interface IMultiAutocompleteField { selectedOptions?: IMultiAutocompleteFieldOption[]; required?: boolean; filterLimit?: number; + helpText?: string; chipVisible?: boolean; onChange?: ( _event: React.ChangeEvent, @@ -157,6 +159,15 @@ const MultiAutocompleteField: React.FC = (props) => { label={props.label} variant="outlined" fullWidth + InputProps={{ + ...params.InputProps, + endAdornment: ( + <> + {props.helpText && } + {params.InputProps.endAdornment} + + ) + }} placeholder="Type to start searching" error={get(touched, props.id) && Boolean(get(errors, props.id))} helperText={get(touched, props.id) && get(errors, props.id)} diff --git a/app/src/components/fields/MultiAutocompleteFieldVariableSize.tsx b/app/src/components/fields/MultiAutocompleteFieldVariableSize.tsx index 12ce881209..450d93fe6e 100644 --- a/app/src/components/fields/MultiAutocompleteFieldVariableSize.tsx +++ b/app/src/components/fields/MultiAutocompleteFieldVariableSize.tsx @@ -6,6 +6,7 @@ import Checkbox from '@mui/material/Checkbox'; import ListSubheader from '@mui/material/ListSubheader'; import TextField from '@mui/material/TextField'; import { FilterOptionsState } from '@mui/material/useAutocomplete'; +import HelpButtonTooltip from 'components/buttons/HelpButtonTooltip'; import { useFormikContext } from 'formik'; import { DebouncedFunc } from 'lodash-es'; import get from 'lodash-es/get'; @@ -44,6 +45,7 @@ export type IMultiAutocompleteField = { label: string; required?: boolean; filterLimit?: number; + helpText?: string; } & (ApiSearchTypeParam | defaultTypeParam); function renderRow(props: ListChildComponentProps) { @@ -290,6 +292,15 @@ const MultiAutocompleteFieldVariableSize: React.FC = (p label={props.label} variant="outlined" fullWidth + InputProps={{ + ...params.InputProps, + endAdornment: ( + <> + {props.helpText && } + {params.InputProps.endAdornment} + + ) + }} placeholder="Type to start searching" error={get(touched, props.id) && Boolean(get(errors, props.id))} helperText={get(touched, props.id) && get(errors, props.id)} diff --git a/app/src/components/fields/StartEndDateFields.tsx b/app/src/components/fields/StartEndDateFields.tsx index 6efcaad1af..aac15a6f02 100644 --- a/app/src/components/fields/StartEndDateFields.tsx +++ b/app/src/components/fields/StartEndDateFields.tsx @@ -51,7 +51,14 @@ const StartEndDateFields: React.FC = (props) => { return ( - + = (props) => { inputProps: { 'data-testid': 'start_date' }, + InputLabelProps: { shrink: true }, fullWidth: true - } + }, + popper: { placement: 'bottom-end' } }} label="Start Date" format={DATE_FORMAT.ShortDateFormat} diff --git a/app/src/components/fields/SystemUserAutocompleteField.tsx b/app/src/components/fields/SystemUserAutocompleteField.tsx index be2e133998..ac02339761 100644 --- a/app/src/components/fields/SystemUserAutocompleteField.tsx +++ b/app/src/components/fields/SystemUserAutocompleteField.tsx @@ -5,6 +5,7 @@ import Box from '@mui/material/Box'; import CircularProgress from '@mui/material/CircularProgress'; import grey from '@mui/material/colors/grey'; import TextField from '@mui/material/TextField'; +import HelpButtonTooltip from 'components/buttons/HelpButtonTooltip'; import UserCard from 'components/user/UserCard'; import { useBiohubApi } from 'hooks/useBioHubApi'; import useIsMounted from 'hooks/useIsMounted'; @@ -27,6 +28,13 @@ interface ISystemUserAutocompleteFieldProps { * @memberof ISystemUserAutocompleteFieldProps */ label: string; + /** + * Users to filter from the options because they have already been selected + * + * @type {number[]} + * @memberof ISystemUserAutocompleteFieldProps + */ + selectedUsers?: number[]; /** * Callback fired on option selection. * @@ -61,6 +69,13 @@ interface ISystemUserAutocompleteFieldProps { * @memberof ISystemUserAutocompleteFieldProps */ clearOnSelect?: boolean; + /** + * Optional help text to be displayed in a tooltip + * + * @type {string} + * @memberof ISystemUserAutocompleteFieldProps + */ + helpText?: string; /** * Whether to show start adornment magnifying glass or not * Defaults to false @@ -85,7 +100,18 @@ interface ISystemUserAutocompleteFieldProps { * @return {*} */ export const SystemUserAutocompleteField = (props: ISystemUserAutocompleteFieldProps) => { - const { formikFieldName, disabled, label, showStartAdornment, placeholder, onSelect, onClear, clearOnSelect } = props; + const { + formikFieldName, + disabled, + label, + showStartAdornment, + placeholder, + onSelect, + onClear, + clearOnSelect, + helpText, + selectedUsers + } = props; const biohubApi = useBiohubApi(); const isMounted = useIsMounted(); @@ -115,15 +141,18 @@ export const SystemUserAutocompleteField = (props: ISystemUserAutocompleteFieldP disabled={disabled} data-testid={formikFieldName} filterSelectedOptions - noOptionsText={inputValue.length > 2 ? 'No matching options' : 'Enter at least 3 letters'} + noOptionsText={inputValue && inputValue.length > 2 ? 'No matching options' : 'Enter at least 3 letters'} options={options} - getOptionLabel={(option) => option.display_name} - isOptionEqualToValue={(option, value) => { - return option.system_user_id === value.system_user_id; + getOptionLabel={(option) => option.display_name || ''} + value={null} // Always set value to null to prevent a selected value from showing + isOptionEqualToValue={(option, value) => option.system_user_id === value.system_user_id} + filterOptions={(options) => { + if (selectedUsers) { + return options.filter((item) => !selectedUsers.includes(item.system_user_id)); + } + return options; }} - filterOptions={(item) => item} - inputValue={inputValue} - // Text field value changed + inputValue={inputValue || ''} // Control the text field value separately onInputChange={(_, value, reason) => { if (clearOnSelect && reason === 'reset') { setInputValue(''); @@ -154,11 +183,10 @@ export const SystemUserAutocompleteField = (props: ISystemUserAutocompleteFieldP if (!isMounted()) { return; } - setOptions(() => newOptions); + setOptions(newOptions); setIsLoading(false); }); }} - // Option selected from dropdown onChange={(_, option) => { if (!option) { onClear?.(); @@ -211,6 +239,7 @@ export const SystemUserAutocompleteField = (props: ISystemUserAutocompleteFieldP endAdornment: ( <> {isLoading ? : null} + {helpText && } {params.InputProps.endAdornment} ) diff --git a/app/src/components/species/components/SpeciesAutocompleteField.tsx b/app/src/components/species/components/SpeciesAutocompleteField.tsx index e0d553bc8f..7789b7b81f 100644 --- a/app/src/components/species/components/SpeciesAutocompleteField.tsx +++ b/app/src/components/species/components/SpeciesAutocompleteField.tsx @@ -5,6 +5,7 @@ import Box from '@mui/material/Box'; import CircularProgress from '@mui/material/CircularProgress'; import grey from '@mui/material/colors/grey'; import TextField from '@mui/material/TextField'; +import HelpButtonTooltip from 'components/buttons/HelpButtonTooltip'; import SpeciesCard from 'components/species/components/SpeciesCard'; import { useBiohubApi } from 'hooks/useBioHubApi'; import useIsMounted from 'hooks/useIsMounted'; @@ -67,6 +68,13 @@ export interface ISpeciesAutocompleteFieldProps { * @memberof ISpeciesAutocompleteFieldProps */ required?: boolean; + /** + * Optional help text to be displayed in a tooltip + * + * @type {string} + * @memberof ISystemUserAutocompleteFieldProps + */ + helpText?: string; /** * If field is disabled. * @@ -116,6 +124,7 @@ const SpeciesAutocompleteField = (props: ISpeciesAutocompleteFieldProps) => { error, placeholder, disabled, + helpText, handleSpecies, handleClear, defaultSpecies, @@ -191,6 +200,7 @@ const SpeciesAutocompleteField = (props: ISpeciesAutocompleteFieldProps) => { getOptionLabel={(option) => option.scientificName} filterOptions={(item) => item} inputValue={inputValue} + fullWidth // Text field value changed onInputChange={(_, value, reason) => { if (reason === 'reset') { @@ -296,6 +306,7 @@ const SpeciesAutocompleteField = (props: ISpeciesAutocompleteFieldProps) => { endAdornment: ( <> {inputValue && isLoading ? : null} + {helpText && } {params.InputProps.endAdornment} ) diff --git a/app/src/features/projects/components/ProjectDetailsForm.tsx b/app/src/features/projects/components/ProjectDetailsForm.tsx index 1bc437e569..efcefba18f 100644 --- a/app/src/features/projects/components/ProjectDetailsForm.tsx +++ b/app/src/features/projects/components/ProjectDetailsForm.tsx @@ -39,6 +39,7 @@ const ProjectDetailsForm = () => { { it('renders correctly with default empty values', () => { - const { getByLabelText } = render( + const { getAllByLabelText } = render( { ); - expect(getByLabelText('Objectives', { exact: false })).toBeVisible(); + // Target the 'Objectives' label, rather than the help icon text which includes the word 'objectives' + expect(getAllByLabelText('Objectives', { exact: false })[0]).toBeVisible(); }); it('renders correctly with existing objective/caveat values', () => { @@ -29,7 +30,7 @@ describe('ProjectObjectivesForm', () => { } }; - const { getByLabelText, getByText } = render( + const { getAllByLabelText, getByText } = render( { ); - expect(getByLabelText('Objectives', { exact: false })).toBeVisible(); + // Target the 'Objectives' label, rather than the help icon text which includes the word 'objectives' + expect(getAllByLabelText('Objectives', { exact: false })[0]).toBeVisible(); expect(getByText('a project objective')).toBeVisible(); }); }); diff --git a/app/src/features/projects/components/ProjectObjectivesForm.tsx b/app/src/features/projects/components/ProjectObjectivesForm.tsx index f9765cfd75..c292fd8d37 100644 --- a/app/src/features/projects/components/ProjectObjectivesForm.tsx +++ b/app/src/features/projects/components/ProjectObjectivesForm.tsx @@ -38,6 +38,7 @@ const ProjectObjectivesForm = () => { diff --git a/app/src/features/projects/components/ProjectUserForm.test.tsx b/app/src/features/projects/components/ProjectUserForm.test.tsx index 7fd481a38b..6c2d80d026 100644 --- a/app/src/features/projects/components/ProjectUserForm.test.tsx +++ b/app/src/features/projects/components/ProjectUserForm.test.tsx @@ -100,7 +100,7 @@ describe('ProjectUserForm', () => { ); await waitFor(async () => { - expect(getByTestId('autocomplete-user-role-search')).toBeVisible(); + expect(getByTestId('system_user_id')).toBeVisible(); expect(getByText('Test User', { exact: false })).toBeVisible(); }); }); @@ -140,7 +140,7 @@ describe('ProjectUserForm', () => { ); await waitFor(async () => { - expect(getByTestId('autocomplete-user-role-search')).toBeVisible(); + expect(getByTestId('system_user_id')).toBeVisible(); expect(getByTestId('remove-user-role-button-0')).toBeVisible(); }); }); diff --git a/app/src/features/projects/components/ProjectUserForm.tsx b/app/src/features/projects/components/ProjectUserForm.tsx index 08474c0236..10d7a1211a 100644 --- a/app/src/features/projects/components/ProjectUserForm.tsx +++ b/app/src/features/projects/components/ProjectUserForm.tsx @@ -1,24 +1,15 @@ -import { mdiMagnify } from '@mdi/js'; -import Icon from '@mdi/react'; -import Autocomplete, { createFilterOptions } from '@mui/material/Autocomplete'; import Box from '@mui/material/Box'; import Collapse from '@mui/material/Collapse'; -import grey from '@mui/material/colors/grey'; -import TextField from '@mui/material/TextField'; import Typography from '@mui/material/Typography'; import AlertBar from 'components/alert/AlertBar'; -import UserCard from 'components/user/UserCard'; +import { SystemUserAutocompleteField } from 'components/fields/SystemUserAutocompleteField'; import UserRoleSelector from 'components/user/UserRoleSelector'; import { PROJECT_ROLE } from 'constants/roles'; import { useFormikContext } from 'formik'; -import { useBiohubApi } from 'hooks/useBioHubApi'; -import useDataLoader from 'hooks/useDataLoader'; import { ICode } from 'interfaces/useCodesApi.interface'; import { ICreateProjectRequest, IGetProjectParticipant } from 'interfaces/useProjectApi.interface'; import { ISystemUser } from 'interfaces/useUserApi.interface'; -import { useEffect, useState } from 'react'; import { TransitionGroup } from 'react-transition-group'; -import { alphabetizeObjects } from 'utils/Utils'; import yup from 'utils/YupSchema'; export const ProjectUserRoleYupSchema = yup.object().shape({ @@ -48,19 +39,6 @@ export const ProjectUserRoleFormInitialValues = { const ProjectUserForm = (props: IProjectUserFormProps) => { const { handleSubmit, values, setFieldValue, errors, setErrors } = useFormikContext(); - const biohubApi = useBiohubApi(); - - const searchUserDataLoader = useDataLoader((keyword: string) => biohubApi.user.searchSystemUser(keyword)); - - const [searchText, setSearchText] = useState(''); - - const [sortedUsers, setSortedUsers] = useState([]); - - useEffect(() => { - if (searchUserDataLoader.data) { - setSortedUsers(alphabetizeObjects(searchUserDataLoader.data, 'display_name')); - } - }, [searchUserDataLoader.data]); const handleAddUser = (user: ISystemUser | IGetProjectParticipant) => { setFieldValue(`participants[${values.participants.length}]`, { @@ -161,76 +139,19 @@ const ProjectUserForm = (props: IProjectUserFormProps) => { )} - { - const searchFilter = createFilterOptions({ ignoreCase: true }); - const unselectedOptions = options.filter( - (item) => !values.participants.some((existing) => existing.system_user_id === item.system_user_id) - ); - return searchFilter(unselectedOptions, state); - }} - getOptionLabel={(option) => option.display_name} - inputValue={searchText} - onInputChange={(_, value, reason) => { - if (reason === 'reset') { - setSearchText(''); - } else { - setSearchText(value); - - if (value.length >= 3) { - // Only search if the search text is at least 3 characters long - searchUserDataLoader.refresh(value); - } - } - }} - onChange={(_, option) => { - if (option) { - handleAddUser(option); + participant.system_user_id)} + clearOnSelect + onSelect={(value) => { + if (value) { + handleAddUser(value); } }} - renderInput={(params) => ( - - - - ) - }} - /> - )} - renderOption={(renderProps, renderOption) => { - return ( - - - - - - ); - }} + key="project-user-filter" /> diff --git a/app/src/features/surveys/animals/animal-form/components/AnimalFormContainer.tsx b/app/src/features/surveys/animals/animal-form/components/AnimalFormContainer.tsx index ef3564ae54..c8305208f1 100644 --- a/app/src/features/surveys/animals/animal-form/components/AnimalFormContainer.tsx +++ b/app/src/features/surveys/animals/animal-form/components/AnimalFormContainer.tsx @@ -112,9 +112,10 @@ export const AnimalFormContainer = (props: IAnimalFormProps) => { summary="Enter information to identify the animal" component={} /> + } /> diff --git a/app/src/features/surveys/animals/animal-form/components/ecological-units/EcologicalUnitsForm.tsx b/app/src/features/surveys/animals/animal-form/components/ecological-units/EcologicalUnitsForm.tsx index a732318aae..a250b403d0 100644 --- a/app/src/features/surveys/animals/animal-form/components/ecological-units/EcologicalUnitsForm.tsx +++ b/app/src/features/surveys/animals/animal-form/components/ecological-units/EcologicalUnitsForm.tsx @@ -3,6 +3,7 @@ import { Icon } from '@mdi/react'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import Collapse from '@mui/material/Collapse'; +import HelpButtonStack from 'components/buttons/HelpButtonStack'; import { EcologicalUnitDualSelect } from 'components/species/ecological-units/EcologicalUnitDualSelect'; import { FieldArray, FieldArrayRenderProps, useFormikContext } from 'formik'; import { useCritterbaseApi } from 'hooks/useCritterbaseApi'; @@ -56,25 +57,27 @@ export const EcologicalUnitsForm = () => { ))} - + + + )} /> diff --git a/app/src/features/surveys/animals/animal-form/components/general-information/AnimalGeneralInformationForm.tsx b/app/src/features/surveys/animals/animal-form/components/general-information/AnimalGeneralInformationForm.tsx index e7c6eabf40..f374c3ef7b 100644 --- a/app/src/features/surveys/animals/animal-form/components/general-information/AnimalGeneralInformationForm.tsx +++ b/app/src/features/surveys/animals/animal-form/components/general-information/AnimalGeneralInformationForm.tsx @@ -1,6 +1,7 @@ import Collapse from '@mui/material/Collapse'; import Grid from '@mui/material/Grid'; import Box from '@mui/system/Box'; +import HelpButtonStack from 'components/buttons/HelpButtonStack'; import AutocompleteField, { IAutocompleteFieldOption } from 'components/fields/AutocompleteField'; import CustomTextField from 'components/fields/CustomTextField'; import SpeciesAutocompleteField from 'components/species/components/SpeciesAutocompleteField'; @@ -44,24 +45,26 @@ export const AnimalGeneralInformationForm = (props: IAnimalGeneralInformationFor - { - setFieldValue('species', species); - setFieldValue('ecological_units', []); - if (species) { - measurementsDataLoader.refresh(species.tsn); - return; - } - measurementsDataLoader.clearData(); - }} - clearOnSelect={true} - error={errors.species} - /> + + { + setFieldValue('species', species); + setFieldValue('ecological_units', []); + if (species) { + measurementsDataLoader.refresh(species.tsn); + return; + } + measurementsDataLoader.clearData(); + }} + clearOnSelect={true} + error={errors.species} + /> + {values.species && ( - + + + - + + + - + + + diff --git a/app/src/features/surveys/components/agreements/AgreementsForm.tsx b/app/src/features/surveys/components/agreements/AgreementsForm.tsx index 0016ba55f9..5ddfab154c 100644 --- a/app/src/features/surveys/components/agreements/AgreementsForm.tsx +++ b/app/src/features/surveys/components/agreements/AgreementsForm.tsx @@ -1,10 +1,10 @@ -import Box from '@mui/material/Box'; import Checkbox from '@mui/material/Checkbox'; import FormControl from '@mui/material/FormControl'; import FormControlLabel from '@mui/material/FormControlLabel'; import FormHelperText from '@mui/material/FormHelperText'; import Grid from '@mui/material/Grid'; import Typography from '@mui/material/Typography'; +import HelpButtonStack from 'components/buttons/HelpButtonStack'; import { useFormikContext } from 'formik'; import { StringBoolean } from 'types/misc'; import yup from 'utils/YupSchema'; @@ -60,9 +60,11 @@ const AgreementsForm = () => {
- - Species and Ecosystems Data and Information Security (SEDIS) Procedures - + + + Species and Ecosystems Data and Information Security (SEDIS) Procedures + + { - + Freedom of Information and Protection of Privacy Act (FOIPPA) Requirements - + = (props) => { required={true} component="fieldset" error={touched.proprietor?.disa_required && Boolean(errors.proprietor?.disa_required)}> - - Data and Information Sharing Agreement (DISA) - + + + Data and Information Sharing Agreement (DISA) + + Do you require a data and information sharing agreement? diff --git a/app/src/features/surveys/components/general-information/GeneralInformationForm.tsx b/app/src/features/surveys/components/general-information/GeneralInformationForm.tsx index 5156d7680a..c319d0925c 100644 --- a/app/src/features/surveys/components/general-information/GeneralInformationForm.tsx +++ b/app/src/features/surveys/components/general-information/GeneralInformationForm.tsx @@ -95,6 +95,7 @@ const GeneralInformationForm: React.FC = (props) = = (props) = { - + Study Areas ({values.locations.length}) diff --git a/app/src/features/surveys/components/participants/SurveyUserForm.test.tsx b/app/src/features/surveys/components/participants/SurveyUserForm.test.tsx index 6fcb9776b8..85aa4530c2 100644 --- a/app/src/features/surveys/components/participants/SurveyUserForm.test.tsx +++ b/app/src/features/surveys/components/participants/SurveyUserForm.test.tsx @@ -96,7 +96,7 @@ describe('SurveyUserForm', () => { ); await waitFor(async () => { - expect(getByTestId('autocomplete-user-role-search')).toBeVisible(); + expect(getByTestId('participants')).toBeVisible(); expect(getByText('Test User', { exact: false })).toBeVisible(); }); }); @@ -137,7 +137,7 @@ describe('SurveyUserForm', () => { ); await waitFor(async () => { - expect(getByTestId('autocomplete-user-role-search')).toBeVisible(); + expect(getByTestId('participants')).toBeVisible(); expect(getByTestId('remove-user-role-button-0')).toBeVisible(); }); }); diff --git a/app/src/features/surveys/components/participants/SurveyUserForm.tsx b/app/src/features/surveys/components/participants/SurveyUserForm.tsx index dc02dcd567..ae98ddec4f 100644 --- a/app/src/features/surveys/components/participants/SurveyUserForm.tsx +++ b/app/src/features/surveys/components/participants/SurveyUserForm.tsx @@ -1,23 +1,14 @@ -import { mdiMagnify } from '@mdi/js'; -import Icon from '@mdi/react'; -import Autocomplete, { createFilterOptions } from '@mui/material/Autocomplete'; import Box from '@mui/material/Box'; import Collapse from '@mui/material/Collapse'; -import grey from '@mui/material/colors/grey'; -import TextField from '@mui/material/TextField'; import Typography from '@mui/material/Typography'; import AlertBar from 'components/alert/AlertBar'; -import UserCard from 'components/user/UserCard'; +import { SystemUserAutocompleteField } from 'components/fields/SystemUserAutocompleteField'; import UserRoleSelector from 'components/user/UserRoleSelector'; import { useFormikContext } from 'formik'; -import { useBiohubApi } from 'hooks/useBioHubApi'; -import useDataLoader from 'hooks/useDataLoader'; import { ICode } from 'interfaces/useCodesApi.interface'; import { ICreateSurveyRequest, IGetSurveyParticipant } from 'interfaces/useSurveyApi.interface'; import { ISystemUser } from 'interfaces/useUserApi.interface'; -import { useEffect, useState } from 'react'; import { TransitionGroup } from 'react-transition-group'; -import { alphabetizeObjects } from 'utils/Utils'; import yup from 'utils/YupSchema'; export const SurveyUserJobYupSchema = yup.object().shape({ @@ -39,19 +30,6 @@ export const SurveyUserJobFormInitialValues = { const SurveyUserForm = (props: ISurveyUserFormProps) => { const { handleSubmit, values, setFieldValue, errors, setErrors } = useFormikContext(); - const biohubApi = useBiohubApi(); - - const searchUserDataLoader = useDataLoader((keyword: string) => biohubApi.user.searchSystemUser(keyword)); - - const [searchText, setSearchText] = useState(''); - - const [sortedUsers, setSortedUsers] = useState([]); - - useEffect(() => { - if (searchUserDataLoader.data) { - setSortedUsers(alphabetizeObjects(searchUserDataLoader.data, 'display_name')); - } - }, [searchUserDataLoader.data]); const handleAddUser = (user: ISystemUser | IGetSurveyParticipant) => { setFieldValue(`participants[${values.participants.length}]`, { @@ -119,79 +97,18 @@ const SurveyUserForm = (props: ISurveyUserFormProps) => { )} - - { - const searchFilter = createFilterOptions({ ignoreCase: true }); - const unselectedOptions = options.filter( - (item) => !values.participants.some((existing) => existing.system_user_id === item.system_user_id) - ); - return searchFilter(unselectedOptions, state); - }} - getOptionLabel={(option) => option.display_name} - inputValue={searchText} - onInputChange={(_, value, reason) => { - if (reason === 'reset') { - setSearchText(''); - } else { - setSearchText(value); - - if (value.length >= 3) { - // Only search if the search text is at least 3 characters long - searchUserDataLoader.refresh(value); - } - } - }} - onChange={(_, option) => { - if (option) { - handleAddUser(option); - } - }} - renderInput={(params) => ( - - - - ) - }} - /> - )} - renderOption={(renderProps, renderOption) => { - return ( - - - - - - ); - }} - /> - + participant.system_user_id)} + clearOnSelect + onSelect={(value) => { + if (value) { + handleAddUser(value); + } + }} + /> { selectedRole={getSelectedRole(index)} handleAdd={handleAddUserRole} handleRemove={handleRemoveUser} - key={user.system_user_id} - label={'Select a Job'} + label="Select a Job" /> ); diff --git a/app/src/features/surveys/components/sampling-strategy/SamplingStrategyForm.tsx b/app/src/features/surveys/components/sampling-strategy/SamplingStrategyForm.tsx index d60bab9f44..57c9296adf 100644 --- a/app/src/features/surveys/components/sampling-strategy/SamplingStrategyForm.tsx +++ b/app/src/features/surveys/components/sampling-strategy/SamplingStrategyForm.tsx @@ -1,6 +1,7 @@ import Box from '@mui/material/Box'; import Collapse from '@mui/material/Collapse'; import Typography from '@mui/material/Typography'; +import HelpButtonStack from 'components/buttons/HelpButtonStack'; import { useState } from 'react'; import SurveyBlockSection from './blocks/SurveyBlockForm'; import SurveyStratumForm from './stratums/SurveyStratumForm'; @@ -14,7 +15,9 @@ const SamplingStrategyForm = () => { - Add Stratum + + Add Stratum + { - - Add Blocks (optional) - + Add Blocks (optional) { id="site_selection.strategies" label="Site selection strategy" options={siteStrategies} + helpText="Select how the locations of sampling sites were chosen." selectedOptions={selectedSiteStrategies} required={true} onChange={(_, selectedOptions, reason) => { diff --git a/app/src/features/surveys/components/species/components/FocalSpeciesForm.tsx b/app/src/features/surveys/components/species/components/FocalSpeciesForm.tsx index 69dd036cda..035af446aa 100644 --- a/app/src/features/surveys/components/species/components/FocalSpeciesForm.tsx +++ b/app/src/features/surveys/components/species/components/FocalSpeciesForm.tsx @@ -33,6 +33,7 @@ export const FocalSpeciesForm = () => { { if (values.species.focal_species.some((focalSpecies) => focalSpecies.tsn === species.tsn)) { diff --git a/app/src/features/surveys/edit/EditSurveyForm.tsx b/app/src/features/surveys/edit/EditSurveyForm.tsx index c228734833..8b26b90f7f 100644 --- a/app/src/features/surveys/edit/EditSurveyForm.tsx +++ b/app/src/features/surveys/edit/EditSurveyForm.tsx @@ -4,6 +4,7 @@ import Divider from '@mui/material/Divider'; import Stack from '@mui/material/Stack'; import Typography from '@mui/material/Typography'; import FormikErrorSnackbar from 'components/alert/FormikErrorSnackbar'; +import HelpButtonStack from 'components/buttons/HelpButtonStack'; import HorizontalSplitFormComponent from 'components/fields/HorizontalSplitFormComponent'; import { CodesContext } from 'contexts/codesContext'; import { ProjectContext } from 'contexts/projectContext'; @@ -117,7 +118,9 @@ const EditSurveyForm = < summary="Enter any permits used in this survey" component={ - Were any permits used in this survey? + + Were any permits used in this survey? + } @@ -130,7 +133,9 @@ const EditSurveyForm = < summary="Specify funding sources for this survey" component={ - Do any funding agencies require this survey to be submitted? + + Do any funding agencies require this survey to be submitted? + } @@ -160,7 +165,7 @@ const EditSurveyForm = < } /> @@ -184,7 +189,7 @@ const EditSurveyForm = < } /> @@ -195,7 +200,9 @@ const EditSurveyForm = < summary="Indicate whether any data is proprietary" component={ - Is any data in this survey proprietary? + + Is any data in this survey proprietary? + { diff --git a/app/src/features/surveys/sampling-information/methods/SamplingMethodFormContainer.tsx b/app/src/features/surveys/sampling-information/methods/SamplingMethodFormContainer.tsx index 429da93169..77b6e10b6d 100644 --- a/app/src/features/surveys/sampling-information/methods/SamplingMethodFormContainer.tsx +++ b/app/src/features/surveys/sampling-information/methods/SamplingMethodFormContainer.tsx @@ -18,6 +18,7 @@ import Menu, { MenuProps } from '@mui/material/Menu'; import MenuItem from '@mui/material/MenuItem'; import Stack from '@mui/material/Stack'; import Typography from '@mui/material/Typography'; +import HelpButtonStack from 'components/buttons/HelpButtonStack'; import ColouredRectangleChip from 'components/chips/ColouredRectangleChip'; import { CodesContext } from 'contexts/codesContext'; import { ISurveySampleMethodFormData } from 'features/surveys/sampling-information/methods/components/SamplingMethodForm'; @@ -142,7 +143,11 @@ export const SamplingMethodFormContainer = () => { - Add Sampling Techniques + + Add Sampling Techniques + { return ( <> - Assign to Block + + Assign to Block + { return ( <> - Assign to Stratum + + Assign to Strata +