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

(feat) O3-3677: Implement ability to add patient to a queue from lab app #1258

Merged
merged 23 commits into from
Sep 15, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
91e79f4
(feat) Enhance Service Queue Management for sending back patient to …
its-kios09 Jul 29, 2024
6a5e5bb
(feat) retaining previous queue details from the queue
its-kios09 Jul 30, 2024
6551b5a
(chore) removed the overflow button and renamed the title
its-kios09 Jul 30, 2024
4237402
(refactor) added defualt value to be urgent
its-kios09 Jul 31, 2024
8549ac3
(refactor) passed wait and urgent conceptUuid as configurable
its-kios09 Jul 31, 2024
3a31c7c
(chore) renamed and lab defualt should be blank
its-kios09 Aug 1, 2024
9da6f5f
Merge branch 'main' into itskios-09/lab-modal-send
its-kios09 Aug 1, 2024
4e5ffc4
(chore) transferred all the service-queue into one place and made the…
its-kios09 Aug 2, 2024
b7d2086
(refactor) translation json
its-kios09 Aug 2, 2024
a92f267
(refactor) renaming of defualts values of configuration schemas
its-kios09 Aug 2, 2024
c7bc383
(refactor) renaming of the defualt view on config-schema.ts
its-kios09 Aug 2, 2024
f4f3e2d
(refactor) rename the filenames for send-back-patient-toqueue
its-kios09 Aug 3, 2024
d265571
(refactor) renaming of the button
its-kios09 Aug 5, 2024
0d118f0
(refactor) translation.json
its-kios09 Aug 5, 2024
3c936fb
(refactor) reused the transition component and pass the patient uuid
its-kios09 Aug 5, 2024
f25e9d6
(refactor) translation json
its-kios09 Aug 5, 2024
748b2bf
(refactor) The reduce method is used to iterate through the results a…
its-kios09 Aug 7, 2024
dad80e1
(refactor) translation.json
its-kios09 Aug 7, 2024
c4dc507
(chore)removed ResultReviewConceptUuid
its-kios09 Aug 7, 2024
91bf7a0
(refactor) resolve issues addressed from the PR
its-kios09 Aug 8, 2024
2eaad49
Merge branch 'main' into itskios-09/lab-modal-send
denniskigen Aug 8, 2024
a16a5a5
(refactor) updated the hook
its-kios09 Aug 21, 2024
a583c97
Merge branch 'main' into itskios-09/lab-modal-send
brandones Aug 22, 2024
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
7 changes: 7 additions & 0 deletions packages/esm-service-queues-app/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,13 @@ export const addNewQueueServiceWorkspace = getAsyncLifecycle(
moduleName,
},
);
export const sendBackPatientQueueEntryModal = getAsyncLifecycle(
() => import('./send-patient-back-toqueue/send-back-patient-toqueue.component'),
{
featureName: 'send back patient to queue',
moduleName,
},
);

// t('addNewQueueServiceRoom', 'Add new queue service room')
export const addNewQueueServiceRoomWorkspace = getAsyncLifecycle(
Expand Down
4 changes: 4 additions & 0 deletions packages/esm-service-queues-app/src/routes.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@
"name": "transition-queue-entry-modal",
"component": "transitionQueueEntryModal"
},
{
"name": "send-back-patient-to-queue-entry-modal",
"component": "sendBackPatientQueueEntryModal"
},
{
"name": "edit-queue-entry-modal",
"component": "editQueueEntryModal"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
import React, { useMemo } from 'react';
import {
Button,
ModalBody,
ModalFooter,
ModalHeader,
Form,
ContentSwitcher,
Switch,
Select,
SelectItem,
InlineNotification,
RadioButton,
RadioButtonGroup,
InlineLoading,
Tile,
TextInput,
} from '@carbon/react';
import { useTranslation } from 'react-i18next';
import { navigate, showSnackbar, useConfig } from '@openmrs/esm-framework';
import { useQueueLocations } from '../patient-search/hooks/useQueueLocations';
import styles from './send-back-patient-toqueue.scss';
import { useQueues } from '../hooks/useQueues';
import { useForm, Controller } from 'react-hook-form';
import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';
import { type ConfigObject } from '../config-schema';
import { useMutateQueueEntries } from '../hooks/useQueueEntries';
import { useQueueEntry } from './send-back-patient-toqueue.resource';
import { updateQueueEntry } from '../active-visits/active-visits-table.resource';

interface SendBackPatientProps {
patientUuid: string;
closeModal: () => void;
}

const SendBackPatientToQueue: React.FC<SendBackPatientProps> = ({ closeModal, patientUuid }) => {
const { t } = useTranslation();
const { concepts } = useConfig<ConfigObject>();
const { data, error, isLoading } = useQueueEntry(patientUuid);
const allowedPriorities = data?.queue?.allowedPriorities ?? [];
const allowedStatuses = data?.queue?.allowedStatuses ?? [];
const uppercaseText = (text: string) => text.toUpperCase();

const schema = useMemo(
() =>
z.object({
location: z.string({ required_error: t('queueLocationRequired', 'Queue location is required') }),
service: z.string({ required_error: t('serviceIsRequired', 'Service is required') }),
status: z.string({ required_error: t('statusIsRequired', 'Status is required') }),
priority: z.string({ required_error: t('priorityIsRequired', 'Priority is required') }),
}),
[t],
);

type ChangeStatusForm = z.infer<typeof schema>;

const {
control,
handleSubmit,
formState: { isSubmitting, errors },
getValues,
} = useForm<ChangeStatusForm>({
resolver: zodResolver(schema),
});

const queueServiceLocation = data?.locationWaitingFor?.display ?? '';
const { queues } = useQueues(queueServiceLocation);
const { queueLocations } = useQueueLocations();
const { mutateQueueEntries } = useMutateQueueEntries();

const onSubmit = async (updateQueueData: ChangeStatusForm) => {
const { status, service } = updateQueueData;
const defaultPriority = concepts.defaultPriorityConceptUuid;
const queuePriority = updateQueueData.priority === '' ? defaultPriority : updateQueueData.priority;
const emergencyPriorityConceptUuid = concepts.emergencyPriorityConceptUuid;
const sortWeight = updateQueueData.priority === emergencyPriorityConceptUuid ? 1.0 : 0.0;
const endDate = new Date();

try {
const response = await updateQueueEntry(
its-kios09 marked this conversation as resolved.
Show resolved Hide resolved
data?.visit.uuid,
data?.queue.uuid,
service,
data?.uuid,
patientUuid,
queuePriority,
status,
endDate,
sortWeight,
);

if (response.status === 201) {
showSnackbar({
isLowContrast: true,
title: t('sendPatientBack', 'Back to queue'),
kind: 'success',
subtitle: t('sendBackPatientBackSuccess', 'Patient sent back to queue Successfully'),
});
closeModal();
mutateQueueEntries();
navigate({ to: `${window.spaBase}/home/service-queues` });
}
} catch (error) {
showSnackbar({
title: t('queueEntryStatusUpdateFailed', 'Error updating queue entry status'),
kind: 'error',
subtitle: error?.message,
});
}
};

const onError = (errors: any) => console.error(errors);

if (!data) {
return (
<Tile>
<ModalHeader closeModal={closeModal} title={t('sendPatientToAQueue', 'Add to a queue')} />
<ModalBody>
<div className={styles.modalBody}>
<h5>{t('patientNotInQueue', 'The patient is not in the queue')}</h5>
</div>
</ModalBody>
</Tile>
);
}

return (
<div>
<Form onSubmit={handleSubmit(onSubmit, onError)}>
its-kios09 marked this conversation as resolved.
Show resolved Hide resolved
<ModalHeader closeModal={closeModal} title={t('sendPatientToAQueue', 'Add to a queue')} />
<ModalBody>
<div className={styles.modalBody}>
<h5>
{uppercaseText(data.patient.person.display)} &nbsp; · &nbsp;{data.patient.person.gender} &nbsp; · &nbsp;
{data.patient.person.age}&nbsp;
{t('years', 'Years')}
</h5>
</div>
<TextInput
readOnly
value={data.queueComingFrom?.display}
labelText={t('queueFrom', "Patient's previous queue")}
/>
<section>
<Controller
name="location"
control={control}
defaultValue={data?.queueComingFrom?.location?.uuid}
render={({ field: { onChange, value } }) => (
<Select
labelText={t('selectQueueLocation', 'Select a queue location')}
id="location"
invalid={!!errors.location}
invalidText={errors.location?.message}
value={value}
onChange={(event) => onChange(event.target.value)}>
{!getValues()?.location && (
<SelectItem text={t('selectQueueLocation', 'Select a queue location')} value="" />
)}
{queueLocations?.length > 0 &&
queueLocations.map((location) => (
<SelectItem key={location.id} text={location.name} value={location.id}>
{location.name}
</SelectItem>
))}
</Select>
)}
/>
</section>

<section className={styles.section}>
<div className={styles.sectionTitle}>{t('queueService', 'Queue service')}</div>
<Controller
name="service"
defaultValue={data?.queueComingFrom?.uuid}
control={control}
render={({ field: { onChange, value } }) => (
<Select
labelText={t('selectService', 'Select a service')}
id="service"
invalid={!!errors.service}
invalidText={errors.service?.message}
value={value}
onChange={(event) => onChange(event.target.value)}>
{!getValues()?.service && <SelectItem text={t('selectService', 'Select a service')} value="" />}
{queues?.length > 0 &&
queues.map((service) => (
<SelectItem key={service.uuid} text={service.display} value={service.uuid}>
{service.display}
</SelectItem>
))}
</Select>
)}
/>
</section>

<section className={styles.section}>
<div className={styles.sectionTitle}>{t('queueStatus', 'Queue status')}</div>
{!allowedStatuses?.length ? (
<InlineNotification
className={styles.inlineNotification}
kind={'error'}
lowContrast
subtitle={t('configureStatus', 'Please configure status to continue.')}
title={t('noStatusConfigured', 'No status configured')}
/>
) : (
<Controller
name="status"
control={control}
defaultValue={data?.queue?.allowedStatuses[2].uuid}
its-kios09 marked this conversation as resolved.
Show resolved Hide resolved
render={({ field: { value, onChange } }) => (
<RadioButtonGroup
className={styles.radioButtonWrapper}
name="status"
invalid={!!errors.status}
invalidText={errors.status?.message}
defaultSelected={value}
onChange={(uuid) => onChange(uuid)}>
{allowedStatuses?.length > 0 &&
allowedStatuses.map(({ uuid, display }) => (
<RadioButton key={uuid} labelText={display} value={uuid} />
))}
</RadioButtonGroup>
)}
/>
)}
</section>

<section className={styles.section}>
<div className={styles.sectionTitle}>{t('queuePriority', 'Queue priority')}</div>
<Controller
control={control}
name="priority"
defaultValue={data?.queue.allowedPriorities[2]?.uuid}
its-kios09 marked this conversation as resolved.
Show resolved Hide resolved
render={({ field: { onChange } }) => (
<>
<ContentSwitcher
size="sm"
selectedIndex={allowedPriorities.findIndex((p) => p.uuid === getValues().priority)}
onChange={(event) => onChange(event.name as any)}>
{allowedPriorities?.length > 0 ? (
allowedPriorities.map(({ uuid, display }) => (
<Switch name={uuid} text={display} key={uuid} value={uuid} />
))
) : (
<Switch
name={t('noPriorityFound', 'No priority found')}
text={t('noPriorityFound', 'No priority found')}
value={null}
/>
)}
</ContentSwitcher>
{errors.priority && <div className={styles.error}>{errors.priority.message}</div>}
</>
)}
/>
</section>
</ModalBody>
<ModalFooter>
<Button kind="secondary" onClick={closeModal}>
{t('cancel', 'Cancel')}
</Button>
<Button disabled={isSubmitting} type="submit">
<>
{isSubmitting ? (
<div className={styles.inline}>
<InlineLoading
status="active"
iconDescription={t('submitting', 'Submitting')}
description={t('submitting', 'Submitting')}
/>
</div>
) : (
t('returnPatientToQueue', 'Return patient to queue')
)}
</>
</Button>
</ModalFooter>
</Form>
</div>
);
};

export default SendBackPatientToQueue;
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { openmrsFetch, type OpenmrsResource, type Patient, type Visit } from '@openmrs/esm-framework';
its-kios09 marked this conversation as resolved.
Show resolved Hide resolved
import useSWR from 'swr';
import useSWRImmutable from 'swr/immutable';

interface Location {
uuid: string;
display?: string;
name?: string;
}

interface Concept extends OpenmrsResource {
setMembers?: Array<Concept>;
}
interface Provider extends OpenmrsResource {}

interface Queue {
uuid: string;
display: string;
name: string;
description: string;
location: Location;
service: Concept;
allowedPriorities: Array<Concept>;
allowedStatuses: Array<Concept>;
}

export interface QueueEntryResponse {
uuid: string;
display: string;
queue: Queue;
status: Concept;
patient: Patient;
visit: Visit;
priority: Concept;
priorityComment: string | null;
sortWeight: number;
startedAt: string;
endedAt: string;
locationWaitingFor: Location;
queueComingFrom: Queue;
providerWaitingFor: Provider;
previousQueueEntry: QueueEntryResponse;
}

export function useQueueEntry(patientUuid: string) {
const customRepresentation =
'custom:(uuid,display,queue:(uuid,display,name,description,location:(uuid,display,links),service:(uuid,display,links),priorityConceptSet,statusConceptSet,allowedPriorities:(uuid,display,links),allowedStatuses:(uuid,display,links),links),status,patient:(uuid,display,person,identifiers:(uuid,display,identifier,identifierType)),visit:(uuid,display,startDatetime,encounters:(uuid,display,diagnoses,encounterDatetime,encounterType,obs,encounterProviders,voided),attributes:(uuid,display,value,attributeType)),priority,priorityComment,sortWeight,startedAt,endedAt,locationWaitingFor,queueComingFrom,providerWaitingFor,previousQueueEntry)';

const encodedRepresentation = encodeURIComponent(customRepresentation);
const url = `/ws/rest/v1/queue-entry?v=${encodedRepresentation}&patient=${patientUuid}&isEnded=false`;

const fetcher = async (url: string) => {
const response = await openmrsFetch(url);
const data = await response.json();
return data;
};

const { data, error, isLoading, mutate } = useSWR<{ results: QueueEntryResponse[] }>(url, fetcher);

const queueEntry = data?.results[0] || null;

return { data: queueEntry, error, isLoading, mutate };
}
Loading