Skip to content

Commit dc418ec

Browse files
authored
[Feature] Search by Doctor MSP number for Renewal Application (#392)
* added a doctor typeahead * added the graphql for the typeahead * updated thre graphql schemas to correctky handle the request * generated the graphql types * sending the request correctly to the graphql server * sending the request correctly to the graphql server * changed the search criterion to contains * added the resolver to the lib graphql * kept only msp number in the git commit * changed the search box placeholder text * return more doctor information * added the form filling code * added spacing and explanations * patched the problem with the reinitialize form * patching the linting errors
1 parent 611d84b commit dc418ec

File tree

8 files changed

+235
-3
lines changed

8 files changed

+235
-3
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// DoctorTypeahead.tsx
2+
import { useState } from 'react';
3+
import { Text } from '@chakra-ui/react';
4+
import { useQuery } from '@tools/hooks/graphql'; // your Apollo wrapper hook
5+
import Typeahead from '@components/Typeahead'; // generic typeahead component
6+
import { formatFullName } from '@lib/utils/format'; // utility to join names
7+
import {
8+
DoctorResult,
9+
SearchDoctorsRequest,
10+
SearchDoctorsResponse,
11+
SEARCH_DOCTORS,
12+
} from '@tools/admin/requests/doctor-typeahead';
13+
14+
type Props = {
15+
/** Callback when a physician is selected; passes the physician’s data */
16+
onSelect: (doctor: DoctorResult) => void;
17+
};
18+
19+
export default function DoctorTypeahead({ onSelect }: Props) {
20+
const [searchString, setSearchString] = useState('');
21+
22+
// Run the query using the input as the filter
23+
const { data, loading, error } = useQuery<SearchDoctorsResponse, SearchDoctorsRequest>(
24+
SEARCH_DOCTORS,
25+
{
26+
variables: {
27+
filter: {
28+
mspNumber: searchString,
29+
},
30+
},
31+
}
32+
);
33+
34+
if (error) {
35+
console.error('Error fetching physicians ', error);
36+
}
37+
38+
/** Called when a physician is selected in the typeahead */
39+
const handleSelect = (doctor: DoctorResult | undefined) => {
40+
if (doctor) {
41+
onSelect(doctor);
42+
}
43+
};
44+
45+
return (
46+
<Typeahead
47+
isLoading={loading}
48+
onSearch={setSearchString}
49+
renderMenuItemChildren={({ firstName, lastName, phone }: DoctorResult) => {
50+
return (
51+
<>
52+
<Text textStyle="body-regular" mt="8px" mb="4px" ml="4px">
53+
{formatFullName(firstName, undefined, lastName)}
54+
</Text>
55+
<Text textStyle="caption" textColor="text.secondary" mb="8px" ml="4px">
56+
Phone {phone}
57+
</Text>
58+
</>
59+
);
60+
}}
61+
labelKey={(option: DoctorResult) => `${option.firstName} ${option.lastName}`}
62+
results={data?.physicians?.result || []}
63+
onSelect={handleSelect}
64+
placeholder="Search by doctor's MSP number"
65+
/>
66+
);
67+
}

lib/graphql/resolvers.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ import {
8989
applicationProcessingWalletCardResolver,
9090
} from '@lib/application-processing/field-resolvers';
9191
import { guardianPoaFormS3ObjectUrlResolver } from '@lib/guardian/field-resolvers';
92-
import { comparePhysicians } from '@lib/physicians/resolvers';
92+
import { physicians, comparePhysicians } from '@lib/physicians/resolvers';
9393

9494
/**
9595
* Resolver return type - accounts for extra fields
@@ -150,6 +150,7 @@ const resolvers = {
150150
generateAccountantReport: authorize(generateAccountantReport, [`ACCOUNTING`]),
151151

152152
// Physicians
153+
physicians: authorize(physicians, ['SECRETARY']),
153154
comparePhysicians: authorize(comparePhysicians, ['SECRETARY']),
154155
},
155156
Mutation: {

lib/graphql/schema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export default gql`
2828
generateAccountantReport(input: GenerateAccountantReportInput!): GenerateAccountantReportResult
2929
3030
# Physicians
31+
physicians(filter: PhysiciansFilter): PhysiciansResult
3132
comparePhysicians(input: ComparePhysiciansInput!): ComparePhysiciansResult
3233
}
3334

lib/graphql/types.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -996,6 +996,19 @@ export type PhysicianStatus =
996996
| 'ACTIVE'
997997
| 'INACTIVE';
998998

999+
export type PhysiciansFilter = {
1000+
order: Maybe<Array<Array<Scalars['String']>>>;
1001+
mspNumber: Maybe<Scalars['String']>;
1002+
limit: Maybe<Scalars['Int']>;
1003+
offset: Maybe<Scalars['Int']>;
1004+
};
1005+
1006+
export type PhysiciansResult = {
1007+
__typename?: 'PhysiciansResult';
1008+
result: Array<Physician>;
1009+
totalCount: Scalars['Int'];
1010+
};
1011+
9991012
export type Province =
10001013
| 'BC'
10011014
| 'AB'
@@ -1022,6 +1035,7 @@ export type Query = {
10221035
generateApplicationsReport: Maybe<GenerateApplicationsReportResult>;
10231036
generatePermitHoldersReport: Maybe<GeneratePermitHoldersReportResult>;
10241037
generateAccountantReport: Maybe<GenerateAccountantReportResult>;
1038+
physicians: Maybe<PhysiciansResult>;
10251039
comparePhysicians: Maybe<ComparePhysiciansResult>;
10261040
};
10271041

@@ -1071,6 +1085,11 @@ export type QueryGenerateAccountantReportArgs = {
10711085
};
10721086

10731087

1088+
export type QueryPhysiciansArgs = {
1089+
filter: Maybe<PhysiciansFilter>;
1090+
};
1091+
1092+
10741093
export type QueryComparePhysiciansArgs = {
10751094
input: ComparePhysiciansInput;
10761095
};

lib/physicians/resolvers.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,62 @@ import {
33
ComparePhysiciansInput,
44
ComparePhysiciansResult,
55
QueryComparePhysiciansArgs,
6+
PhysiciansFilter,
7+
PhysiciansResult,
68
} from '@lib/graphql/types';
79
import { stripPhoneNumber, stripPostalCode } from '@lib/utils/format';
810
import { Physician } from '@prisma/client';
11+
import { Prisma } from '@prisma/client';
12+
13+
/**
14+
* Query and filter physicians from the database.
15+
* Supports filtering by first name, last name, and MSP number.
16+
* Supports pagination and sorting.
17+
* @returns All physicians that match the filter(s).
18+
*/
19+
export const physicians: Resolver<{ filter: PhysiciansFilter }, PhysiciansResult> = async (
20+
_parent,
21+
{ filter },
22+
{ prisma }
23+
) => {
24+
// Create default filter
25+
let where: Prisma.PhysicianWhereInput = {};
26+
27+
if (filter) {
28+
const { mspNumber } = filter;
29+
30+
// Update filter based on input
31+
where = {
32+
...(mspNumber && { mspNumber: { contains: mspNumber } }),
33+
};
34+
}
35+
36+
// Map the input sorting format into key-value pairs that can be used by Prisma
37+
const sortingOrder: Record<string, Prisma.SortOrder> = {};
38+
39+
if (filter?.order) {
40+
filter.order.forEach(([field, order]) => (sortingOrder[field] = order as Prisma.SortOrder));
41+
}
42+
43+
const take = filter?.limit || undefined;
44+
const skip = filter?.offset || undefined;
45+
46+
const totalCount = await prisma.physician.count({
47+
where,
48+
});
49+
50+
const physicians = await prisma.physician.findMany({
51+
where,
52+
skip,
53+
take,
54+
// orderBy: sortingOrder,
55+
});
56+
57+
return {
58+
result: physicians,
59+
totalCount,
60+
};
61+
};
962

1063
/**
1164
* Compare physician data passed in from UI to existing physician data in DB

lib/physicians/schema.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,19 @@ export default gql`
3838
postalCode: String!
3939
}
4040
41+
# Query many doctors
42+
input PhysiciansFilter {
43+
order: [[String!]!]
44+
mspNumber: String
45+
limit: Int
46+
offset: Int
47+
}
48+
49+
type PhysiciansResult {
50+
result: [Physician!]!
51+
totalCount: Int!
52+
}
53+
4154
type ComparePhysiciansResult {
4255
match: Boolean!
4356
status: PhysicianMatchStatus

pages/admin/request/create-renewal.tsx

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { getSession } from 'next-auth/client';
1212
import { GetServerSideProps } from 'next';
1313
import CancelCreateRequestModal from '@components/admin/requests/create/CancelModal';
1414
import PermitHolderTypeahead from '@components/admin/permit-holders/Typeahead';
15+
import DoctorTypeahead from '@components/admin/requests/doctor-information/DoctorTypeahead';
1516
import { useLazyQuery, useMutation } from '@tools/hooks/graphql';
1617
import {
1718
CreateRenewalApplicationRequest,
@@ -281,11 +282,12 @@ export default function CreateRenewal() {
281282
additionalInformation: INITIAL_ADDITIONAL_QUESTIONS,
282283
paymentInformation: INITIAL_PAYMENT_DETAILS,
283284
}}
285+
// enableReinitialize={true}
284286
validationSchema={renewalRequestFormSchema}
285287
onSubmit={handleSubmit}
286288
validateOnMount
287289
>
288-
{({ values, isValid }) => (
290+
{({ values, isValid, ...formik }) => (
289291
<Form noValidate>
290292
<GridItem paddingTop="32px">
291293
<Box
@@ -321,7 +323,28 @@ export default function CreateRenewal() {
321323
<Text textStyle="display-small-semibold" paddingBottom="20px">
322324
{`Doctor's Information`}
323325
</Text>
324-
<DoctorInformationForm />
326+
<Text textStyle="body-regular" color="text.secondary" paddingBottom="16px">
327+
Search for a doctor by MSP number or manually enter the doctor&apos;s
328+
information below
329+
</Text>
330+
<DoctorTypeahead
331+
onSelect={doctor => {
332+
const doctorData = {
333+
firstName: doctor.firstName,
334+
lastName: doctor.lastName,
335+
mspNumber: doctor.mspNumber,
336+
phone: doctor.phone,
337+
addressLine1: doctor.addressLine1,
338+
addressLine2: doctor.addressLine2 || '',
339+
city: doctor.city,
340+
postalCode: doctor.postalCode,
341+
};
342+
formik.setFieldValue('doctorInformation', doctorData);
343+
}}
344+
/>
345+
<Box paddingTop="20px">
346+
<DoctorInformationForm />
347+
</Box>
325348
</Box>
326349
</GridItem>
327350
{/* Additional Quesitons Form */}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// doctorsTypeahead.ts
2+
import { gql } from '@apollo/client';
3+
import { Physician } from '@lib/graphql/types';
4+
5+
/** A simplified Physician type used for the typeahead result */
6+
export type DoctorResult = Pick<
7+
Physician,
8+
| 'mspNumber'
9+
| 'firstName'
10+
| 'lastName'
11+
| 'phone'
12+
| 'addressLine1'
13+
| 'addressLine2'
14+
| 'city'
15+
| 'province'
16+
| 'country'
17+
| 'postalCode'
18+
>;
19+
20+
/** GraphQL query to search for physicians */
21+
export const SEARCH_DOCTORS = gql`
22+
query SearchPhysicians($filter: PhysiciansFilter!) {
23+
physicians(filter: $filter) {
24+
result {
25+
mspNumber
26+
firstName
27+
lastName
28+
phone
29+
addressLine1
30+
addressLine2
31+
city
32+
province
33+
country
34+
postalCode
35+
status
36+
}
37+
}
38+
}
39+
`;
40+
41+
/** Query request variables */
42+
export type SearchDoctorsRequest = {
43+
filter: {
44+
mspNumber?: string;
45+
limit?: number;
46+
offset?: number;
47+
};
48+
};
49+
50+
/** Query response type */
51+
export type SearchDoctorsResponse = {
52+
physicians: {
53+
result: Array<DoctorResult>;
54+
};
55+
};

0 commit comments

Comments
 (0)