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) Retrieve scheduled appointments in clinical forms workspace #471

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

jnsereko
Copy link
Contributor

@jnsereko jnsereko commented Feb 27, 2025

Requirements

  • This PR has a title that briefly describes the work done including the ticket number. If there is a ticket, make sure your PR title includes a conventional commit label. See existing PR titles for inspiration.
  • My work conforms to the OpenMRS 3.0 Styleguide and design documentation.
  • My work includes tests or is validated by existing tests.

Summary

This PR adds ability to

  • preview scheduled appointments in the appointments workspace back to the clinical form
  • adds the current encounter to the appointment created with the clinical form
  • previews recent appointments associated to a specific encounter.

Requirements

cc-ing @vasharma05 @pirupius @ibacher @denniskigen

Screenshots

Screen.Recording.2025-02-27.at.16.10.27.mov

Related Issue

[should be created]

Other

@pirupius
Copy link
Member

@jnsereko please update the description and add some screenshots or video to provide more context for the reviewers

@pirupius
Copy link
Member

pirupius commented Feb 27, 2025

The CI seems run endlessly so i've cancelled it

appointmentKind: string;
appointmentNumber: string;
comments: string;
endDateTime: Date | number;
Copy link
Member

Choose a reason for hiding this comment

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

It looks like this property is a Date on the backend. Why are we typing it as Date | number?

Copy link
Member

Choose a reason for hiding this comment

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

Actually, it returns the number, neither the ISO string, nor a Date object

src/api/index.ts Outdated
Comment on lines 27 to 32
const filteredAppointments = appointments.filter((appointment) => {
return !appointment.fulfillingEncounters.includes(encounterUuid);
});
return filteredAppointments.map((appointment) => {
return updateAppointment(url, appointment, encounterUuid, abortController);
});
Copy link
Member

Choose a reason for hiding this comment

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

nit: This code would be more concise and easier to read without the explicit return statements

src/api/index.ts Outdated
appointmentKind: appointment.appointmentKind,
status: appointment.status,
startDateTime: appointment.startDateTime,
endDateTime: appointment.endDateTime.toString(),
Copy link
Member

Choose a reason for hiding this comment

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

Why do we need toString() here? If it's a timestamp, isn't it preferable to convert it to a date string using something like toIsoString or a dayjs utility function instead?

src/api/index.ts Outdated
uuid: appointment.uuid
};

return openmrsFetch(`${url}`, {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
return openmrsFetch(`${url}`, {
return openmrsFetch(url, {

src/api/index.ts Outdated
Comment on lines 69 to 78
export const getPatientAppointment = (appointmentUuid: string) => {
return openmrsFetch(
`${restBaseUrl}/appointments/${appointmentUuid}`,
).then(({ data }) => {
if (data) {
return data;
}
return null;
});
};
Copy link
Member

Choose a reason for hiding this comment

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

Would it be useful to have some kind of error handling here? Something along the lines of:

Suggested change
export const getPatientAppointment = (appointmentUuid: string) => {
return openmrsFetch(
`${restBaseUrl}/appointments/${appointmentUuid}`,
).then(({ data }) => {
if (data) {
return data;
}
return null;
});
};
export const getPatientAppointment = async (appointmentUuid: string) => {
try {
const response = await openmrsFetch(`${restBaseUrl}/appointments/${appointmentUuid}`);
return response.data || null;
} catch (error) {
console.error('Error fetching appointment:', error);
throw error; // Re-throw to allow caller to handle the error
}
};

Comment on lines 1 to 5
import { openmrsFetch, restBaseUrl, useOpenmrsSWR } from '@openmrs/esm-framework';
import dayjs from 'dayjs';
import useSWR, { mutate, SWRResponse } from 'swr';
import { type AppointmentsResponse } from '../types';
import { useCallback, useMemo, useState } from 'react';
Copy link
Member

Choose a reason for hiding this comment

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

It looks like we're not using openmrsFetch and the SWR imports here. Can we remove them?

Copy link
Member

Choose a reason for hiding this comment

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

Yes @denniskigen, working on it!
Thanks for the catch!

@@ -202,6 +203,26 @@ export class EncounterFormProcessor extends FormProcessor {
critical: true,
});
}
// handle appointments
try {
const {appointments: myAppointments} = context
Copy link
Member

Choose a reason for hiding this comment

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

Looks like we're not using this code

Comment on lines 209 to 216
const appointmentsResponse = await Promise.all(addFulfillingEncounters(abortController, appointments, savedEncounter.uuid));
if (appointmentsResponse?.length) {
showSnackbar({
title: translateFn('appointmentsSaved', 'Appointment(s) saved successfully'),
kind: 'success',
isLowContrast: true,
});
}
Copy link
Member

Choose a reason for hiding this comment

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

Should we modify this so that we only proceed if there are appointments?

if (appointments && appointments.length > 0) {

   // ...
 }

const appointmentsResponse = await Promise.all(addFulfillingEncounters(abortController, appointments, savedEncounter.uuid));
if (appointmentsResponse?.length) {
showSnackbar({
title: translateFn('appointmentsSaved', 'Appointment(s) saved successfully'),
Copy link
Member

Choose a reason for hiding this comment

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

Does translateFrom handle pluralization? If not, I think we could just default it to plural:

Suggested change
title: translateFn('appointmentsSaved', 'Appointment(s) saved successfully'),
title: translateFn('appointmentsSaved', 'Appointments saved successfully'),

} catch (error) {
const errorMessages = Array.isArray(error) ? error.map((err) => err.message) : [error.message];
return Promise.reject({
title: translateFn('errorSavingAppointments', 'Error saving appointment(s)'),
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
title: translateFn('errorSavingAppointments', 'Error saving appointment(s)'),
title: translateFn('errorSavingAppointments', 'Error saving appointments'),

}
};

const AppointmentsTable = ({ appointments }) => {
Copy link
Member

Choose a reason for hiding this comment

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

@jnsereko , never create a component inside another component.

Comment on lines +48 to +60
const updatedAppointment: AppointmentsPayload = {
fulfillingEncounters: updatedFulfillingEncounters,
serviceUuid: appointment.service.uuid,
locationUuid: appointment.location.uuid,
patientUuid: appointment.patient.uuid,
dateAppointmentScheduled: appointment.startDateTime,
appointmentKind: appointment.appointmentKind,
status: appointment.status,
startDateTime: appointment.startDateTime,
endDateTime: toOmrsIsoString(appointment.endDateTime),
providers: [{ uuid: appointment.providers[0]?.uuid }],
comments: appointment.comments,
uuid: appointment.uuid,
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 this weird that for updating 1 value of the appointment, we need to pass the whole object?

Can this be worked on?
CC: @denniskigen @jnsereko @ibacher @mogoodrich

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can this be worked on?

Agree with you @vasharma05. A nice improvement to do in the backend

Copy link
Member

Choose a reason for hiding this comment

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

@jnsereko , can you create a Backend Ticket for this?

Copy link
Member

Choose a reason for hiding this comment

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

Agree... we should likely have a REST endpoint just for adding a fulfilling encounter to an appointment.

@@ -20,7 +30,11 @@ const WorkspaceLauncher: React.FC<FormFieldInputProps> = ({ field }) => {
isLowContrast: true,
});
}
launchWorkspace();
if (field.meta?.handleAppointmentCreation) {
Copy link
Member

Choose a reason for hiding this comment

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

@pirupius @jnsereko, instead of having the check by the workspace name, since workspace names can be changed, I added a check by adding new prop handleAppointmentCreation in the field meta

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@pirupius @jnsereko, instead of having the check by the workspace name, since workspace names can be changed, I added a check by adding new prop handleAppointmentCreation in the field meta

Should it really be optional though @vasharma05

Copy link
Member

Choose a reason for hiding this comment

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

@pirupius , did you have a look here?

appointmentKind: string;
appointmentNumber: string;
comments: string;
endDateTime: Date | number;
Copy link
Member

Choose a reason for hiding this comment

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

Actually, it returns the number, neither the ISO string, nor a Date object

@pirupius pirupius changed the title Retrieve appointment scheduled in workspace in forms (feat) Retrieve appointment scheduled from workspace in forms Feb 28, 2025
@denniskigen
Copy link
Member

Whilst we’re still editing, maybe “(feat) Retrieve scheduled appointments in clinical forms workspace” reads better?

@pirupius pirupius changed the title (feat) Retrieve appointment scheduled from workspace in forms (feat) Retrieve scheduled appointments in clinical forms workspace Feb 28, 2025
Copy link
Member

@mogoodrich mogoodrich left a comment

Choose a reason for hiding this comment

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

So I added one comment but didn't review fully yet. This functionality looks really good! But, unfortunately, if I'm following this correctly I think there's a fundamental problem with it. It looks like this sets the "fulfillingEncounter" is set to the encounter when the appointment was scheduled? Is this what is happening? The "fulfillingEncounters" are meant to be set to any encounters that fulfill the encounter... ie, in this case, it should be the encounter that gets created on March 8th 2025 if/when the patient returns that day. It's not used to store the encounter when the appointment was created. See: https://bahmni.atlassian.net/browse/BAH-3239

Am I understanding this correctly?

Comment on lines +48 to +60
const updatedAppointment: AppointmentsPayload = {
fulfillingEncounters: updatedFulfillingEncounters,
serviceUuid: appointment.service.uuid,
locationUuid: appointment.location.uuid,
patientUuid: appointment.patient.uuid,
dateAppointmentScheduled: appointment.startDateTime,
appointmentKind: appointment.appointmentKind,
status: appointment.status,
startDateTime: appointment.startDateTime,
endDateTime: toOmrsIsoString(appointment.endDateTime),
providers: [{ uuid: appointment.providers[0]?.uuid }],
comments: appointment.comments,
uuid: appointment.uuid,
Copy link
Member

Choose a reason for hiding this comment

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

Agree... we should likely have a REST endpoint just for adding a fulfilling encounter to an appointment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants