Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show Descriptions in Autocomplete Options #1497

Open
wants to merge 15 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions api/src/paths/codes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ GET.apiDoc = {
},
name: {
type: 'string'
},
description: {
type: 'string'
Copy link
Collaborator

@mauberti-bc mauberti-bc Feb 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the description can be null in the database, you should include nullable: true in the description object here. Otherwise the request would fail for what the database considers valid data.

(Disregard if the description field of the type database table has a not null constraint.)

}
}
}
Expand Down
18 changes: 12 additions & 6 deletions api/src/repositories/code-repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ const DeviceMakeCode = ICode.extend({ description: z.string() });
const FrequencyUnitCode = ICode.extend({ description: z.string() });
const AlertTypeCode = ICode.extend({ description: z.string() });
const VantageCode = ICode.extend({ description: z.string() });
const TypeCode = ICode.extend({ description: z.string() });
const SiteSelectionStrategyCode = ICode.extend({ description: z.string() });

export const IAllCodeSets = z.object({
management_action_type: CodeSet(),
Expand Down Expand Up @@ -173,13 +175,14 @@ export class CodeRepository extends BaseRepository {
const sqlStatement = SQL`
SELECT
type_id as id,
name
name,
description
FROM
type
WHERE record_end_date is null;
`;

const response = await this.connection.sql(sqlStatement, ICode);
const response = await this.connection.sql(sqlStatement, TypeCode);

return response.rows;
}
Expand Down Expand Up @@ -319,7 +322,8 @@ export class CodeRepository extends BaseRepository {
const sqlStatement = SQL`
SELECT
project_role_id as id,
name
name,
description
FROM project_role
WHERE record_end_date is null
ORDER BY
Expand All @@ -341,7 +345,8 @@ export class CodeRepository extends BaseRepository {
const sqlStatement = SQL`
SELECT
survey_job_id as id,
name
name,
description
FROM survey_job
WHERE record_end_date is null;
`;
Expand All @@ -361,12 +366,13 @@ export class CodeRepository extends BaseRepository {
const sqlStatement = SQL`
SELECT
ss.site_strategy_id as id,
ss.name
ss.name,
ss.description
FROM site_strategy ss
WHERE record_end_date is null;
`;

const response = await this.connection.sql(sqlStatement, ICode);
const response = await this.connection.sql(sqlStatement, SiteSelectionStrategyCode);

return response.rows;
}
Expand Down
6 changes: 3 additions & 3 deletions app/src/components/fields/MultiAutocompleteField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { useEffect, useState } from 'react';
export interface IMultiAutocompleteFieldOption {
value: string | number;
label: string;
subText?: string;
description?: string;
}

export interface IMultiAutocompleteField {
Expand Down Expand Up @@ -133,7 +133,7 @@ const MultiAutocompleteField: React.FC<IMultiAutocompleteField> = (props) => {
filterOptions={createFilterOptions({ limit: props.filterLimit })}
renderOption={(renderProps, renderOption, { selected }) => {
return (
<Box component="li" {...renderProps}>
<Box component="li" {...renderProps} key={renderOption.value}>
<Checkbox
icon={<CheckBoxOutlineBlank fontSize="small" />}
checkedIcon={<CheckBox fontSize="small" />}
Expand All @@ -142,7 +142,7 @@ const MultiAutocompleteField: React.FC<IMultiAutocompleteField> = (props) => {
value={renderOption.value}
color="default"
/>
<ListItemText primary={renderOption.label} secondary={renderOption.subText} />
<ListItemText primary={renderOption.label} secondary={renderOption.description} />
</Box>
);
}}
Expand Down
11 changes: 4 additions & 7 deletions app/src/components/fields/MultiAutocompleteFieldVariableSize.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import CheckBoxOutlineBlank from '@mui/icons-material/CheckBoxOutlineBlank';
import Autocomplete, { AutocompleteInputChangeReason, createFilterOptions } from '@mui/material/Autocomplete';
import Box from '@mui/material/Box';
import Checkbox from '@mui/material/Checkbox';
import ListItemText from '@mui/material/ListItemText';
import ListSubheader from '@mui/material/ListSubheader';
import TextField from '@mui/material/TextField';
import { FilterOptionsState } from '@mui/material/useAutocomplete';
Expand All @@ -12,14 +13,10 @@ import { DebouncedFunc } from 'lodash-es';
import get from 'lodash-es/get';
import React, { useEffect, useState } from 'react';
import { ListChildComponentProps, VariableSizeList } from 'react-window';
import { IMultiAutocompleteFieldOption } from './MultiAutocompleteField';

const LISTBOX_PADDING = 8; // px

export interface IMultiAutocompleteFieldOption {
value: string | number;
label: string;
}

// Params required to make MultiAutocompleteField use API to populate search results
export type ApiSearchTypeParam = {
type: 'api-search';
Expand Down Expand Up @@ -270,7 +267,7 @@ const MultiAutocompleteFieldVariableSize: React.FC<IMultiAutocompleteField> = (p
filterOptions={handleFiltering}
renderOption={(renderProps, renderOption, { selected }) => {
return (
<Box component="li" {...renderProps}>
<Box component="li" {...renderProps} key={renderOption.value}>
<Checkbox
icon={<CheckBoxOutlineBlank fontSize="small" />}
checkedIcon={<CheckBox fontSize="small" />}
Expand All @@ -280,7 +277,7 @@ const MultiAutocompleteFieldVariableSize: React.FC<IMultiAutocompleteField> = (p
value={renderOption.value}
color="default"
/>
{renderOption.label}
<ListItemText primary={renderOption.label} secondary={renderOption.description} />
</Box>
);
}}
Expand Down
21 changes: 10 additions & 11 deletions app/src/components/user/UserRoleSelector.test.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import { PROJECT_ROLE } from 'constants/roles';
import { ICode } from 'interfaces/useCodesApi.interface';
import { IGetAllCodeSetsResponse } from 'interfaces/useCodesApi.interface';
import { fireEvent, render, waitFor } from 'test-helpers/test-utils';
import UserRoleSelector from './UserRoleSelector';
const roles: ICode[] = [

const mockRoles: IGetAllCodeSetsResponse['project_roles'] = [
{
id: 1,
name: PROJECT_ROLE.COLLABORATOR
name: PROJECT_ROLE.COORDINATOR,
description: 'The administrative lead of the project.'
},
{
id: 2,
name: PROJECT_ROLE.COORDINATOR
},
{
id: 3,
name: PROJECT_ROLE.OBSERVER
name: PROJECT_ROLE.COLLABORATOR,
description: 'A participant team member of the project.'
}
];

Expand All @@ -35,7 +34,7 @@ describe('UserRoleSelector', () => {
agency: 'Business',
project_role_names: [PROJECT_ROLE.COORDINATOR]
}}
roles={roles}
roles={mockRoles}
error={undefined}
selectedRole={PROJECT_ROLE.COORDINATOR}
handleAdd={() => {}}
Expand Down Expand Up @@ -68,7 +67,7 @@ describe('UserRoleSelector', () => {
agency: 'Business',
project_role_names: [PROJECT_ROLE.COORDINATOR]
}}
roles={roles}
roles={mockRoles}
error={undefined}
selectedRole={PROJECT_ROLE.COORDINATOR}
handleAdd={() => {}}
Expand Down Expand Up @@ -106,7 +105,7 @@ describe('UserRoleSelector', () => {
agency: 'Business',
project_role_names: []
}}
roles={roles}
roles={mockRoles}
error={undefined}
selectedRole={''}
handleAdd={onAdd}
Expand Down
33 changes: 23 additions & 10 deletions app/src/components/user/UserRoleSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Paper from '@mui/material/Paper';
import Select from '@mui/material/Select';
import Typography from '@mui/material/Typography';
import { PROJECT_ROLE_ICONS } from 'constants/roles';
import { ICode } from 'interfaces/useCodesApi.interface';
import { IGetAllCodeSetsResponse } from 'interfaces/useCodesApi.interface';
import { IGetProjectParticipant } from 'interfaces/useProjectApi.interface';
import { IGetSurveyParticipant } from 'interfaces/useSurveyApi.interface';
import { ISystemUser } from 'interfaces/useUserApi.interface';
Expand All @@ -18,11 +18,12 @@ interface IUserRoleSelectorProps {
index: number;
user: ISystemUser | IGetProjectParticipant | IGetSurveyParticipant;
selectedRole: string;
roles: ICode[];
roles: IGetAllCodeSetsResponse['project_roles'];
error: JSX.Element | undefined;
handleAdd: (role: string, index: number) => void;
handleRemove: (id: number) => void;
label: string;
description?: string;
}

const UserRoleSelector: React.FC<IUserRoleSelectorProps> = (props) => {
Expand Down Expand Up @@ -79,14 +80,26 @@ const UserRoleSelector: React.FC<IUserRoleSelectorProps> = (props) => {
);
}}>
{roles.map((item) => (
<MenuItem key={item.id} value={item.name}>
{item.name}
{PROJECT_ROLE_ICONS[item.name] && (
<>
&nbsp;
<Icon path={PROJECT_ROLE_ICONS[item.name] ?? ''} size={0.75} color={grey[600]} />
</>
)}
<MenuItem
key={item.id}
value={item.name}
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start'
}}>
<Box sx={{ display: 'flex', alignItems: 'center', width: '100%' }}>
{item.name}
{PROJECT_ROLE_ICONS[item.name] && (
<>
&nbsp;
<Icon path={PROJECT_ROLE_ICONS[item.name] ?? ''} size={0.75} color={grey[600]} />
</>
)}
</Box>
<Typography variant="caption" color="text.secondary" sx={{ mt: 0.5 }}>
{item.description}
</Typography>
</MenuItem>
))}
</Select>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IMultiAutocompleteFieldOption } from 'components/fields/MultiAutocompleteFieldVariableSize';
import { IMultiAutocompleteFieldOption } from 'components/fields/MultiAutocompleteField';
import { Formik } from 'formik';
import { fireEvent, render, waitFor, within } from 'test-helpers/test-utils';
import ProjectIUCNForm, {
Expand Down
2 changes: 1 addition & 1 deletion app/src/features/projects/components/ProjectIUCNForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import InputLabel from '@mui/material/InputLabel';
import MenuItem from '@mui/material/MenuItem';
import Select from '@mui/material/Select';
import Typography from '@mui/material/Typography';
import { IMultiAutocompleteFieldOption } from 'components/fields/MultiAutocompleteFieldVariableSize';
import { IMultiAutocompleteFieldOption } from 'components/fields/MultiAutocompleteField';
import { FieldArray, FieldArrayRenderProps, useFormikContext } from 'formik';
import React from 'react';
import yup from 'utils/YupSchema';
Expand Down
14 changes: 6 additions & 8 deletions app/src/features/projects/components/ProjectUserForm.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,22 @@ import { PROJECT_ROLE } from 'constants/roles';
import { AuthStateContext } from 'contexts/authStateContext';
import { Formik } from 'formik';
import { useBiohubApi } from 'hooks/useBioHubApi';
import { ICode } from 'interfaces/useCodesApi.interface';
import { IGetAllCodeSetsResponse } from 'interfaces/useCodesApi.interface';
import { ISystemUser } from 'interfaces/useUserApi.interface';
import { getMockAuthState, SystemAdminAuthState } from 'test-helpers/auth-helpers';
import { render, waitFor } from 'test-helpers/test-utils';
import ProjectUserForm, { ProjectUserRoleYupSchema } from './ProjectUserForm';

const mockRoles: ICode[] = [
const mockRoles: IGetAllCodeSetsResponse['project_roles'] = [
{
id: 1,
name: PROJECT_ROLE.COLLABORATOR
name: 'Project Manager',
description: 'Manages the project'
},
{
id: 2,
name: PROJECT_ROLE.COORDINATOR
},
{
id: 3,
name: PROJECT_ROLE.OBSERVER
name: 'Team Member',
description: 'Regular team member'
}
];

Expand Down
8 changes: 5 additions & 3 deletions app/src/features/projects/components/ProjectUserForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { SystemUserAutocompleteField } from 'components/fields/SystemUserAutocom
import UserRoleSelector from 'components/user/UserRoleSelector';
import { PROJECT_ROLE } from 'constants/roles';
import { useFormikContext } from 'formik';
import { ICode } from 'interfaces/useCodesApi.interface';
import { IGetAllCodeSetsResponse } from 'interfaces/useCodesApi.interface';
import { ICreateProjectRequest, IGetProjectParticipant } from 'interfaces/useProjectApi.interface';
import { ISystemUser } from 'interfaces/useUserApi.interface';
import { TransitionGroup } from 'react-transition-group';
Expand All @@ -30,14 +30,15 @@ export const ProjectUserRoleYupSchema = yup.object().shape({
});

interface IProjectUserFormProps {
roles: ICode[];
roles: IGetAllCodeSetsResponse['project_roles'];
description?: string;
}

export const ProjectUserRoleFormInitialValues = {
participants: []
};

const ProjectUserForm = (props: IProjectUserFormProps) => {
const ProjectUserForm: React.FC<IProjectUserFormProps> = (props) => {
const { handleSubmit, values, setFieldValue, errors, setErrors } = useFormikContext<ICreateProjectRequest>();

const handleAddUser = (user: ISystemUser | IGetProjectParticipant) => {
Expand Down Expand Up @@ -175,6 +176,7 @@ const ProjectUserForm = (props: IProjectUserFormProps) => {
index={index}
user={user}
roles={props.roles}
description={props.description}
error={error}
selectedRole={getSelectedRole(index)}
handleAdd={handleAddUserRole}
Expand Down
4 changes: 3 additions & 1 deletion app/src/features/projects/edit/EditProjectForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@ const EditProjectForm = <InitialValuesType extends IUpdateProjectRequest | ICrea
<HorizontalSplitFormComponent
title="Team Members"
summary="Specify team members and their associated role for this project."
component={<ProjectUserForm roles={codes?.project_roles ?? []} />}
component={
<ProjectUserForm roles={codes?.project_roles ?? []} description={codes?.project_roles?.[0]?.description} />
}
/>

<Divider />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import Box from '@mui/material/Box';
import Grid from '@mui/material/Grid';
import CustomTextField from 'components/fields/CustomTextField';
import MultiAutocompleteField from 'components/fields/MultiAutocompleteField';
import MultiAutocompleteFieldVariableSize, {
IMultiAutocompleteFieldOption
} from 'components/fields/MultiAutocompleteFieldVariableSize';
import MultiAutocompleteField, { IMultiAutocompleteFieldOption } from 'components/fields/MultiAutocompleteField';
import { ISelectWithSubtextFieldOption } from 'components/fields/SelectWithSubtext';
import React from 'react';
import yup from 'utils/YupSchema';
Expand Down Expand Up @@ -48,7 +45,7 @@ const PurposeAndMethodologyForm: React.FC<IPurposeAndMethodologyFormProps> = (pr
<Box component="fieldset">
<Grid container spacing={3}>
<Grid item xs={12}>
<MultiAutocompleteFieldVariableSize
<MultiAutocompleteField
id={'survey_details.survey_types'}
label={'Collected data'}
options={props.type}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import { AuthStateContext } from 'contexts/authStateContext';
import { Formik } from 'formik';
import { useBiohubApi } from 'hooks/useBioHubApi';
import { ICode } from 'interfaces/useCodesApi.interface';
import { IGetAllCodeSetsResponse } from 'interfaces/useCodesApi.interface';
import { ISystemUser } from 'interfaces/useUserApi.interface';
import { getMockAuthState, SystemAdminAuthState } from 'test-helpers/auth-helpers';
import { render, waitFor } from 'test-helpers/test-utils';
import SurveyUserForm, { SurveyUserJobYupSchema } from './SurveyUserForm';

const mockJobs: ICode[] = [
const mockJobs: IGetAllCodeSetsResponse['project_roles'] = [
{
id: 1,
name: 'Pilot'
name: 'Pilot',
description: 'Conducts surveys'
},
{
id: 2,
name: 'Biologist'
name: 'Biologist',
description: 'Observes survey activities'
}
];

Expand Down
Loading