Skip to content
New issue

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

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

Already on GitHub? # to your account

(fix) O3-4271 Service Queues - fix form to start service queues for p… #1411

Merged
merged 2 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const testProps = {

const mockOpenmrsFetch = jest.mocked(openmrsFetch);

describe('AppointmensOverview', () => {
describe('AppointmentsOverview', () => {
it('renders an empty state if appointments data is unavailable', async () => {
mockOpenmrsFetch.mockResolvedValueOnce({
data: [],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useMemo, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
InlineLoading,
Expand All @@ -18,9 +18,13 @@ import dayjs from 'dayjs';
import { type Appointment } from '../types';
import { useMutateAppointments } from '../form/appointments-form.resource';

interface VisitFormCallbacks {
onVisitCreatedOrUpdated: (visit: Visit) => Promise<any>;
}

// See VisitFormExtensionState in esm-patient-chart-app
export interface PatientUpcomingAppointmentsProps {
setOnVisitCreatedOrUpdated(onSubmit: (visit: Visit) => Promise<any>);
setVisitFormCallbacks(callbacks: VisitFormCallbacks);
visitFormOpenedFrom: string;
patientChartConfig?: {
showUpcomingAppointments: boolean;
Expand All @@ -36,45 +40,53 @@ export interface PatientUpcomingAppointmentsProps {
*/
const PatientUpcomingAppointmentsCard: React.FC<PatientUpcomingAppointmentsProps> = ({
patientUuid,
setOnVisitCreatedOrUpdated,
setVisitFormCallbacks,
patientChartConfig,
}) => {
const { t } = useTranslation();
const startDate = dayjs(new Date().toISOString()).subtract(6, 'month').toISOString();
const headerTitle = t('upcomingAppointments', 'Upcoming appointments');
const [selectedAppointment, setSelectedAppointment] = useState<Appointment>(null);
const { mutateAppointments } = useMutateAppointments();
const memoMutateAppointments = useMemo(() => mutateAppointments, [mutateAppointments]);

const ac = useMemo<AbortController>(() => new AbortController(), []);
useEffect(() => () => ac.abort(), [ac]);
const { data: appointmentsData, error, isLoading } = usePatientAppointments(patientUuid, startDate, ac);

useEffect(() => {
setOnVisitCreatedOrUpdated(() => {
if (selectedAppointment) {
return changeAppointmentStatus('CheckedIn', selectedAppointment.uuid)
.then(() => {
mutateAppointments();
showSnackbar({
isLowContrast: true,
kind: 'success',
subtitle: t('appointmentMarkedChecked', 'Appointment marked as Checked In'),
title: t('appointmentCheckedIn', 'Appointment Checked In'),
});
})
.catch((error) => {
showSnackbar({
title: t('updateError', 'Error updating upcoming appointment'),
kind: 'error',
isLowContrast: false,
subtitle: error?.message,
const onVisitCreatedOrUpdated = useMemo(
() => ({
onVisitCreatedOrUpdated: () => {
if (selectedAppointment) {
return changeAppointmentStatus('CheckedIn', selectedAppointment.uuid)
.then(() => {
memoMutateAppointments();
showSnackbar({
isLowContrast: true,
kind: 'success',
subtitle: t('appointmentMarkedChecked', 'Appointment marked as Checked In'),
title: t('appointmentCheckedIn', 'Appointment Checked In'),
});
})
.catch((error) => {
showSnackbar({
title: t('updateError', 'Error updating upcoming appointment'),
kind: 'error',
isLowContrast: false,
subtitle: error?.message,
});
});
});
} else {
return Promise.resolve();
}
});
}, [selectedAppointment, mutateAppointments, setOnVisitCreatedOrUpdated, t]);
} else {
return Promise.resolve();
}
},
}),
[selectedAppointment, memoMutateAppointments, t],
);

useEffect(() => {
setVisitFormCallbacks(onVisitCreatedOrUpdated);
}, [onVisitCreatedOrUpdated, setVisitFormCallbacks]);

const todaysAppointments = appointmentsData?.todaysAppointments?.length ? appointmentsData?.todaysAppointments : [];
const futureAppointments = appointmentsData?.upcomingAppointments?.length
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,26 @@ const ExistingVisitForm: React.FC<ExistingVisitFormProps> = ({ visit, closeWorks
const [isSubmitting, setIsSubmitting] = useState(false);

const { mutateQueueEntries } = useMutateQueueEntries();
const [submitQueueEntry, setSubmitQueueEntry] = useState<(visit: Visit, patientUuid: string) => Promise<any>>(null);
const [callback, setCallback] = useState<{
submitQueueEntry: (visit: Visit) => Promise<any>;
}>(null);

const handleSubmit = useCallback(
(event) => {
event.preventDefault();
setIsSubmitting(true);

submitQueueEntry?.(visit, visit.patient.uuid)
callback
.submitQueueEntry?.(visit)
Copy link
Member

Choose a reason for hiding this comment

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

Isn't is possible (if unlikely) for callback still be null here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah... will change.

?.then(() => {
closeWorkspace();
mutateQueueEntries();
})
?.finally(() => {
setIsSubmitting(false);
});
},
[closeWorkspace, submitQueueEntry, visit],
[closeWorkspace, callback, visit, mutateQueueEntries],
);

return visit ? (
Expand All @@ -52,7 +56,7 @@ const ExistingVisitForm: React.FC<ExistingVisitFormProps> = ({ visit, closeWorks
</Row>
)}
<Form className={classNames(styles.form, styles.container)} onSubmit={handleSubmit}>
<QueueFields setOnSubmit={setSubmitQueueEntry} />
<QueueFields setOnSubmit={(onSubmit) => setCallback({ submitQueueEntry: onSubmit })} />
<ButtonSet className={isTablet ? styles.tablet : styles.desktop}>
<Button className={styles.button} kind="secondary" onClick={closeWorkspace}>
{t('discard', 'Discard')}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import React, { useContext, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import classNames from 'classnames';
import {
InlineNotification,
RadioButton,
Expand All @@ -9,16 +6,17 @@ import {
Select,
SelectItem,
SelectSkeleton,
TextInput,
} from '@carbon/react';
import { useConfig, ResponsiveWrapper, useSession, type Visit, showSnackbar } from '@openmrs/esm-framework';
import { ResponsiveWrapper, showSnackbar, useConfig, useSession, type Visit } from '@openmrs/esm-framework';
import React, { useCallback, useContext, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { type ConfigObject } from '../../config-schema';
import { useMutateQueueEntries } from '../../hooks/useQueueEntries';
import { useQueues } from '../../hooks/useQueues';
import { useQueueLocations } from '../hooks/useQueueLocations';
import { AddPatientToQueueContext } from '../create-queue-entry.workspace';
import styles from './queue-fields.scss';
import { useMutateQueueEntries } from '../../hooks/useQueueEntries';
import { useQueueLocations } from '../hooks/useQueueLocations';
import { postQueueEntry } from './queue-fields.resource';
import styles from './queue-fields.scss';

export interface QueueFieldsProps {
setOnSubmit(onSubmit: (visit: Visit) => Promise<any>);
Expand All @@ -42,11 +40,12 @@ const QueueFields: React.FC<QueueFieldsProps> = ({ setOnSubmit }) => {
const [priority, setPriority] = useState(defaultPriorityConceptUuid);
const priorities = queues.find((q) => q.uuid === selectedService)?.allowedPriorities ?? [];
const { mutateQueueEntries } = useMutateQueueEntries();
const memoMutateQueueEntries = useCallback(mutateQueueEntries, [mutateQueueEntries]);

const sortWeight = priority === emergencyPriorityConceptUuid ? 1 : 0;

useEffect(() => {
setOnSubmit?.((visit: Visit) => {
const onSubmit = useCallback(
(visit: Visit) => {
if (selectedQueueLocation && selectedService && priority) {
return postQueueEntry(
visit.uuid,
Expand All @@ -65,7 +64,7 @@ const QueueFields: React.FC<QueueFieldsProps> = ({ setOnSubmit }) => {
title: t('addedPatientToQueue', 'Added patient to queue'),
subtitle: t('queueEntryAddedSuccessfully', 'Queue entry added successfully'),
});
mutateQueueEntries();
memoMutateQueueEntries();
})
.catch((error) => {
showSnackbar({
Expand All @@ -79,18 +78,22 @@ const QueueFields: React.FC<QueueFieldsProps> = ({ setOnSubmit }) => {
} else {
return Promise.resolve();
}
});
}, [
selectedQueueLocation,
selectedService,
priority,
sortWeight,
defaultStatusConceptUuid,
visitQueueNumberAttributeUuid,
mutateQueueEntries,
setOnSubmit,
t,
]);
},
[
selectedQueueLocation,
selectedService,
priority,
sortWeight,
defaultStatusConceptUuid,
visitQueueNumberAttributeUuid,
memoMutateQueueEntries,
t,
],
);

useEffect(() => {
setOnSubmit?.(onSubmit);
}, [onSubmit, setOnSubmit]);

useEffect(() => {
if (currentServiceQueueUuid) {
Expand Down Expand Up @@ -201,16 +204,6 @@ const QueueFields: React.FC<QueueFieldsProps> = ({ setOnSubmit }) => {
) : null}
</section>
) : null}

<section className={classNames(styles.section, styles.sectionHidden)}>
<TextInput
type="number"
id="sortWeight"
name="sortWeight"
labelText={t('sortWeight', 'Sort weight')}
value={sortWeight}
/>
</section>
</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,3 @@
.section {
margin: layout.$spacing-07 layout.$spacing-05 0;
}

.sectionHidden {
display: none;
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,13 @@ describe('QueueFields', () => {
it('renders the form fields and returns the set values', async () => {
const user = userEvent.setup();
let onSubmit: (visit: Visit) => Promise<any> = null;
const setOnSubmit = (callback) => (onSubmit = callback);
const setOnSubmit = (callback) => {
onSubmit = callback;
};
render(<QueueFields setOnSubmit={setOnSubmit} />);

expect(screen.getByLabelText('Select a queue location')).toBeInTheDocument();
expect(screen.getByLabelText('Select a service')).toBeInTheDocument();
expect(screen.getByLabelText('Sort weight')).toBeInTheDocument();

const queueUuid = 'e2ec9cf0-ec38-4d2b-af6c-59c82fa30b90';
const serviceSelect = screen.getByLabelText('Select a service').closest('select');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ import { type Visit } from '@openmrs/esm-framework';
import React from 'react';
import QueueFields from './queue-fields.component';

interface VisitFormCallbacks {
onVisitCreatedOrUpdated: (visit: Visit) => Promise<any>;
}
// See VisitFormExtensionState in esm-patient-chart-app
export interface VisitFormQueueFieldsProps {
setOnVisitCreatedOrUpdated(onSubmit: (visit: Visit) => Promise<any>);
setVisitFormCallbacks: (callbacks: VisitFormCallbacks) => void;
visitFormOpenedFrom: string;
patientChartConfig?: {
showServiceQueueFields: boolean;
Expand All @@ -17,9 +20,9 @@ export interface VisitFormQueueFieldsProps {
* It is used slotted into the patient-chart's start visit form
*/
const VisitFormQueueFields: React.FC<VisitFormQueueFieldsProps> = (props) => {
const { setOnVisitCreatedOrUpdated, visitFormOpenedFrom, patientChartConfig } = props;
const { setVisitFormCallbacks, visitFormOpenedFrom, patientChartConfig } = props;
if (patientChartConfig.showServiceQueueFields || visitFormOpenedFrom == 'service-queues-add-patient') {
return <QueueFields setOnSubmit={setOnVisitCreatedOrUpdated} />;
return <QueueFields setOnSubmit={(onSubmit) => setVisitFormCallbacks({ onVisitCreatedOrUpdated: onSubmit })} />;
} else {
return <></>;
}
Expand Down
1 change: 0 additions & 1 deletion packages/esm-service-queues-app/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,6 @@
"serviceIsRequired": "Service is required",
"serviceQueue": "Service queue",
"serviceQueues": "Service queues",
"sortWeight": "Sort weight",
"sp02": "Sp02",
"startAgeRangeInvalid": "Start age range is not valid",
"status": "Status",
Expand Down
Loading