Skip to content

(feat) Retrieve scheduled appointments in clinical forms workspace#471

Draft
jnsereko wants to merge 7 commits intoopenmrs:mainfrom
jnsereko:LIME2-400
Draft

(feat) Retrieve scheduled appointments in clinical forms workspace#471
jnsereko wants to merge 7 commits intoopenmrs:mainfrom
jnsereko:LIME2-400

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!

}
// 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.

});
}
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?

fyi @jnsereko

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.

@jnsereko
Copy link
Contributor Author

jnsereko commented Mar 5, 2025

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?

fyi @jnsereko

Thank you @mogoodrich. You are understanding this correctly. IMO, we might need another field for the scheduling encounter or something like that. I have created a talk post on this for further discussion.

@ibacher
Copy link
Member

ibacher commented Mar 5, 2025

@jnsereko Please create a Jira ticket explaining why we're putting this change in and what the goals are.

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.

6 participants