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

BREAKING: O3-2827: Appointments: Refactor (Part I) #971

Merged
merged 17 commits into from
Feb 13, 2024
Merged

BREAKING: O3-2827: Appointments: Refactor (Part I) #971

merged 17 commits into from
Feb 13, 2024

Conversation

mogoodrich
Copy link
Member

@mogoodrich mogoodrich commented Feb 7, 2024

https://openmrs.atlassian.net/browse/O3-2827

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

Screenshots

Related Issue

Other

@mogoodrich mogoodrich marked this pull request as draft February 7, 2024 21:38
# Conflicts:
#	packages/esm-appointments-app/src/appointments/forms/forms.resource.ts
#	packages/esm-appointments-app/src/home-appointments/appointments-list.test.tsx
#	packages/esm-appointments-app/src/home/appointments-base-table.component.tsx
#	packages/esm-appointments-app/src/home/home-appointments.resource.ts
#	packages/esm-appointments-app/src/patient-queue/visit-form/visit-form.component.tsx
#	packages/esm-appointments-app/src/root.component.tsx
Copy link
Contributor

github-actions bot commented Feb 8, 2024

Size Change: -290 kB (-9%) ✅

Total Size: 2.95 MB

Filename Size Change
packages/esm-appointments-app/dist/163.js 0 B -48.1 kB (removed) 🏆
packages/esm-appointments-app/dist/595.js 0 B -242 kB (removed) 🏆
packages/esm-appointments-app/dist/main.js 323 kB +28.3 kB (+10%) ⚠️
packages/esm-patient-search-app/dist/379.js 0 B -25.6 kB (removed) 🏆
ℹ️ View Unchanged
Filename Size Change
packages/esm-active-visits-app/dist/130.js 123 kB 0 B
packages/esm-active-visits-app/dist/255.js 2.21 kB 0 B
packages/esm-active-visits-app/dist/271.js 671 B 0 B
packages/esm-active-visits-app/dist/277.js 12.2 kB 0 B
packages/esm-active-visits-app/dist/316.js 42.9 kB 0 B
packages/esm-active-visits-app/dist/319.js 631 B 0 B
packages/esm-active-visits-app/dist/382.js 1.15 kB 0 B
packages/esm-active-visits-app/dist/448.js 47.1 kB 0 B
packages/esm-active-visits-app/dist/460.js 727 B 0 B
packages/esm-active-visits-app/dist/574.js 545 B 0 B
packages/esm-active-visits-app/dist/588.js 6.66 kB 0 B
packages/esm-active-visits-app/dist/635.js 1.15 kB 0 B
packages/esm-active-visits-app/dist/644.js 671 B 0 B
packages/esm-active-visits-app/dist/729.js 3.1 kB 0 B
packages/esm-active-visits-app/dist/757.js 649 B 0 B
packages/esm-active-visits-app/dist/784.js 2.63 kB 0 B
packages/esm-active-visits-app/dist/788.js 551 B 0 B
packages/esm-active-visits-app/dist/807.js 864 B 0 B
packages/esm-active-visits-app/dist/833.js 669 B 0 B
packages/esm-active-visits-app/dist/879.js 2.94 kB 0 B
packages/esm-active-visits-app/dist/main.js 63.8 kB 0 B
packages/esm-active-visits-app/dist/openmrs-esm-active-visits-app.js 3.33 kB 0 B
packages/esm-appointments-app/dist/130.js 123 kB 0 B
packages/esm-appointments-app/dist/152.js 257 B 0 B
packages/esm-appointments-app/dist/255.js 2.22 kB -8 B (0%)
packages/esm-appointments-app/dist/271.js 2.16 kB 0 B
packages/esm-appointments-app/dist/303.js 258 B 0 B
packages/esm-appointments-app/dist/319.js 1.99 kB 0 B
packages/esm-appointments-app/dist/426.js 271 kB 0 B
packages/esm-appointments-app/dist/460.js 2.18 kB 0 B
packages/esm-appointments-app/dist/500.js 2.52 kB 0 B
packages/esm-appointments-app/dist/574.js 1.84 kB +120 B (+7%) 🔍
packages/esm-appointments-app/dist/588.js 6.65 kB 0 B
packages/esm-appointments-app/dist/591.js 16.9 kB 0 B
packages/esm-appointments-app/dist/606.js 48.8 kB 0 B
packages/esm-appointments-app/dist/644.js 2.16 kB 0 B
packages/esm-appointments-app/dist/729.js 3.1 kB 0 B
packages/esm-appointments-app/dist/757.js 1.75 kB 0 B
packages/esm-appointments-app/dist/784.js 2.63 kB 0 B
packages/esm-appointments-app/dist/788.js 1.75 kB 0 B
packages/esm-appointments-app/dist/80.js 0 B -2.52 kB (removed) 🏆
packages/esm-appointments-app/dist/807.js 2.4 kB 0 B
packages/esm-appointments-app/dist/833.js 1.99 kB 0 B
packages/esm-appointments-app/dist/openmrs-esm-appointments-app.js 3.33 kB +45 B (+1%)
packages/esm-patient-list-management-app/dist/130.js 123 kB 0 B
packages/esm-patient-list-management-app/dist/255.js 2.21 kB 0 B
packages/esm-patient-list-management-app/dist/271.js 1.33 kB 0 B
packages/esm-patient-list-management-app/dist/295.js 99.3 kB 0 B
packages/esm-patient-list-management-app/dist/319.js 1.53 kB 0 B
packages/esm-patient-list-management-app/dist/382.js 1.15 kB 0 B
packages/esm-patient-list-management-app/dist/435.js 22.7 kB 0 B
packages/esm-patient-list-management-app/dist/460.js 1.73 kB 0 B
packages/esm-patient-list-management-app/dist/574.js 1.34 kB 0 B
packages/esm-patient-list-management-app/dist/588.js 6.66 kB 0 B
packages/esm-patient-list-management-app/dist/591.js 16.9 kB 0 B
packages/esm-patient-list-management-app/dist/635.js 1.15 kB 0 B
packages/esm-patient-list-management-app/dist/644.js 1.31 kB 0 B
packages/esm-patient-list-management-app/dist/716.js 4.66 kB 0 B
packages/esm-patient-list-management-app/dist/729.js 3.1 kB 0 B
packages/esm-patient-list-management-app/dist/757.js 1.51 kB 0 B
packages/esm-patient-list-management-app/dist/784.js 2.64 kB 0 B
packages/esm-patient-list-management-app/dist/788.js 1.34 kB 0 B
packages/esm-patient-list-management-app/dist/807.js 1.84 kB 0 B
packages/esm-patient-list-management-app/dist/833.js 1.6 kB 0 B
packages/esm-patient-list-management-app/dist/main.js 126 kB 0 B
packages/esm-patient-list-management-app/dist/openmrs-esm-patient-list-management-app.js 3.3 kB 0 B
packages/esm-patient-registration-app/dist/130.js 123 kB 0 B
packages/esm-patient-registration-app/dist/152.js 262 B 0 B
packages/esm-patient-registration-app/dist/209.js 36.4 kB 0 B
packages/esm-patient-registration-app/dist/255.js 2.21 kB 0 B
packages/esm-patient-registration-app/dist/271.js 1.6 kB 0 B
packages/esm-patient-registration-app/dist/303.js 260 B 0 B
packages/esm-patient-registration-app/dist/319.js 1.97 kB 0 B
packages/esm-patient-registration-app/dist/460.js 2.03 kB 0 B
packages/esm-patient-registration-app/dist/537.js 2.34 kB 0 B
packages/esm-patient-registration-app/dist/574.js 1.7 kB 0 B
packages/esm-patient-registration-app/dist/591.js 16.9 kB 0 B
packages/esm-patient-registration-app/dist/62.js 6.86 kB 0 B
packages/esm-patient-registration-app/dist/644.js 1.6 kB 0 B
packages/esm-patient-registration-app/dist/729.js 3.1 kB 0 B
packages/esm-patient-registration-app/dist/730.js 115 kB 0 B
packages/esm-patient-registration-app/dist/735.js 464 B 0 B
packages/esm-patient-registration-app/dist/757.js 1.96 kB 0 B
packages/esm-patient-registration-app/dist/784.js 2.64 kB 0 B
packages/esm-patient-registration-app/dist/788.js 1.63 kB 0 B
packages/esm-patient-registration-app/dist/807.js 2.43 kB 0 B
packages/esm-patient-registration-app/dist/833.js 1.97 kB 0 B
packages/esm-patient-registration-app/dist/879.js 2.94 kB 0 B
packages/esm-patient-registration-app/dist/884.js 6.1 kB 0 B
packages/esm-patient-registration-app/dist/main.js 153 kB 0 B
packages/esm-patient-registration-app/dist/openmrs-esm-patient-registration-app.js 3.34 kB 0 B
packages/esm-patient-search-app/dist/130.js 123 kB 0 B
packages/esm-patient-search-app/dist/152.js 261 B 0 B
packages/esm-patient-search-app/dist/255.js 2.21 kB 0 B
packages/esm-patient-search-app/dist/271.js 936 B 0 B
packages/esm-patient-search-app/dist/303.js 260 B 0 B
packages/esm-patient-search-app/dist/319.js 950 B 0 B
packages/esm-patient-search-app/dist/382.js 1.15 kB 0 B
packages/esm-patient-search-app/dist/460.js 1.07 kB 0 B
packages/esm-patient-search-app/dist/564.js 21.8 kB 0 B
packages/esm-patient-search-app/dist/574.js 794 B 0 B
packages/esm-patient-search-app/dist/588.js 6.66 kB 0 B
packages/esm-patient-search-app/dist/591.js 16.9 kB 0 B
packages/esm-patient-search-app/dist/635.js 1.15 kB 0 B
packages/esm-patient-search-app/dist/644.js 936 B 0 B
packages/esm-patient-search-app/dist/729.js 3.1 kB 0 B
packages/esm-patient-search-app/dist/757.js 950 B 0 B
packages/esm-patient-search-app/dist/784.js 2.63 kB 0 B
packages/esm-patient-search-app/dist/788.js 788 B 0 B
packages/esm-patient-search-app/dist/807.js 1.13 kB 0 B
packages/esm-patient-search-app/dist/833.js 982 B 0 B
packages/esm-patient-search-app/dist/851.js 25.6 kB 0 B
packages/esm-patient-search-app/dist/main.js 50.6 kB +33 B (0%)
packages/esm-patient-search-app/dist/openmrs-esm-patient-search-app.js 3.34 kB +1 B (0%)
packages/esm-service-queues-app/dist/130.js 123 kB 0 B
packages/esm-service-queues-app/dist/152.js 262 B 0 B
packages/esm-service-queues-app/dist/255.js 2.23 kB 0 B
packages/esm-service-queues-app/dist/271.js 3.11 kB 0 B
packages/esm-service-queues-app/dist/303.js 261 B 0 B
packages/esm-service-queues-app/dist/319.js 3.14 kB 0 B
packages/esm-service-queues-app/dist/328.js 3.07 kB 0 B
packages/esm-service-queues-app/dist/389.js 2.42 kB 0 B
packages/esm-service-queues-app/dist/425.js 2.06 kB 0 B
packages/esm-service-queues-app/dist/460.js 3.96 kB 0 B
packages/esm-service-queues-app/dist/574.js 3.13 kB 0 B
packages/esm-service-queues-app/dist/588.js 6.66 kB 0 B
packages/esm-service-queues-app/dist/591.js 16.9 kB 0 B
packages/esm-service-queues-app/dist/613.js 155 kB 0 B
packages/esm-service-queues-app/dist/616.js 2.71 kB 0 B
packages/esm-service-queues-app/dist/644.js 3.06 kB 0 B
packages/esm-service-queues-app/dist/694.js 2.64 kB 0 B
packages/esm-service-queues-app/dist/729.js 3.1 kB 0 B
packages/esm-service-queues-app/dist/757.js 3.13 kB 0 B
packages/esm-service-queues-app/dist/784.js 2.63 kB 0 B
packages/esm-service-queues-app/dist/788.js 3.12 kB 0 B
packages/esm-service-queues-app/dist/807.js 4.36 kB 0 B
packages/esm-service-queues-app/dist/833.js 3.62 kB 0 B
packages/esm-service-queues-app/dist/895.js 52.3 kB 0 B
packages/esm-service-queues-app/dist/981.js 2.93 kB 0 B
packages/esm-service-queues-app/dist/main.js 211 kB 0 B
packages/esm-service-queues-app/dist/openmrs-esm-service-queues-app.js 3.3 kB 0 B

compressed-size-action

@@ -0,0 +1,126 @@
import dayjs from 'dayjs';
Copy link
Member Author

Choose a reason for hiding this comment

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

Brought over from Patient Chart

@@ -0,0 +1,105 @@
@use '@carbon/styles/scss/colors';
Copy link
Member Author

Choose a reason for hiding this comment

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

Brought over from Patient Chart

@@ -0,0 +1,205 @@
import React from 'react';
Copy link
Member Author

Choose a reason for hiding this comment

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

Brought over from Patient Chart

@mogoodrich mogoodrich marked this pull request as ready for review February 8, 2024 00:21
@mogoodrich mogoodrich changed the title O3-2827: Appointments: Refactor (Part I ?) O3-2827: Appointments: Refactor (Part I) (BREAKING) Feb 8, 2024
@mogoodrich
Copy link
Member Author

This admittedly a rather large PR and therefore difficult to review, best to read the explanation I have written up here:
https://openmrs.atlassian.net/browse/O3-2827

@vasharma05 vasharma05 changed the title O3-2827: Appointments: Refactor (Part I) (BREAKING) BREAKING: O3-2827: Appointments: Refactor (Part I) Feb 8, 2024
@mogoodrich
Copy link
Member Author

per comment on the ticket, I will likely reincorporate the distribution calendar into the form before merging.

Copy link
Member

@ibacher ibacher left a comment

Choose a reason for hiding this comment

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

A few things I noticed, but mostly comments for future work. Personal bugbear of mine: the pattern of creating an abort controller inside a function call is basically useless. The right pattern for this is in the React component use our useAbortController() hook and pass that into any requests.

The point of abort controllers is to allow the application to cancel requests. If the abort controller's abort() method is never called, it doesn't do anything. useAbortController() calls the abort() method when the component is removed from the screen, so it's generally useful for aborting unnecessary GET requests.

},
"devDependencies": {
"@babel/core": "^7.11.6",
"@carbon/react": "~1.37.0",
"@openmrs/esm-framework": "next",
"@openmrs/esm-patient-common-lib": "next",
Copy link
Member

Choose a reason for hiding this comment

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

Do we need this dependency? I don't see it used in any of the code marked as coming from the chart.

Copy link
Member Author

Choose a reason for hiding this comment

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

I forget why I needed it, but it was because something wasn't working... potentially something I later removed, I will take it out and see if things still work.

Copy link
Member Author

Choose a reason for hiding this comment

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

It's used in the appointment-form.component @ibacher

import { convertTime12to24 } from '@openmrs/esm-patient-common-lib';

@@ -18,14 +18,18 @@
"extract-translations": "turbo extract-translations -- --config ../../tools/i18next-parser.config.js"
},
"dependencies": {
"@hookform/resolvers": "^3.3.1",
Copy link
Member

Choose a reason for hiding this comment

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

While this isn't terribly important, it would be preferrable, I think, to keep all dependencies in devDependencies. These aren't in any way different from how we handle, e.g., dayjs.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks @ibacher ! I thought I was just keeping this consistent with the patient chart, but it looks like they are all devDependencies there, I will correct.

Copy link
Member Author

Choose a reason for hiding this comment

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

Okay, so looking again, I see that these dependencies are listed as dependencies, not devDependencies, in patient-chart. However, when I moved them all to devDependencies here things worked. Do we have a best practice regarding this (to be honest, I don't really understand why these are all devdepencies, is the assumption thst they are all bundled with ESM framework anyway)? @ibacher @denniskigen @brandones

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah we are somewhat inconsistent on this because we changed the norm a couple times during the first couple years of O3. I don't think there is a technical difference between devDependencies and dependencies for us, with our current tooling and setup, though at least some monorepo tools install devDependencies at the monorepo level and dependencies at the package level—but I think Yarn 4 just treats them the same.

Oddly, my inclination would be the opposite of what @ibacher suggests here—I am inclined to follow the rule that a dependency that gets built/bundled goes in dependencies. But I am quite open to being convinced otherwise 🤷

Comment on lines -73 to -82
const isAfterNoon = new Date().getHours() > 12;

if (scheduleType === 'Pending' && isAfterNoon) {
return (
<Button onClick={handleOpenDefaulterForm} size="sm" kind="tertiary">
{followUpButtonText}
</Button>
);
}

Copy link
Member

Choose a reason for hiding this comment

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

I'm not in a position to say whether this is right or not, but this does seem to indicate a point where we have implementation needs for extensibility that's not currently provided.

}

const AppointmentActions: React.FC<AppointmentActionsProps> = ({ visits, appointment, scheduleType }) => {
const AppointmentActions: React.FC<AppointmentActionsProps> = ({ visits, appointment, mutate }) => {
Copy link
Member

Choose a reason for hiding this comment

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

We seem to be passing mutate() through three or for different levels, just so we can access it as a prop in AppointmentForm. This seems like a perfect place for using a React context.

The PatientSearchContext is a pretty good example of how this can be used.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, it was annoying me that I had to pass this all the way around. I can check out out PatientSearchContext.

Another pattern I was seeing was using the useSWR call at multiple levels just to retrieve the mutate function, but that didn't seem correct, and seemed dependent on making sure you were asking for the same/correct key at multiple levels.

Copy link
Member Author

Choose a reason for hiding this comment

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

In the interest of getting this PR merged without continuing to bloat, I will handle this as a separate ticket here: https://openmrs.atlassian.net/browse/O3-2849

Copy link
Contributor

@brandones brandones Feb 13, 2024

Choose a reason for hiding this comment

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

You can always create a new hook that does your useSWR call. Then you are sure to be making the same one each time. You shouldn't be passing mutates around. If you don't like the aesthetic of using a fetch hook just to obtain the mutate, then you can also write a custom hook that just obtains the correct mutate function. See useMutatePatientOrders for example. Ignore the comment, it's out of date. PR

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks @brandones !

@@ -130,7 +133,9 @@ const AppointmentsTable: React.FC<AppointmentsTableProps> = ({
onClick={() =>
downloadAppointmentsAsExcel(
appointments,
`${tableHeading} Appointments ${formatDate(new Date(appointments[0]?.dateTime), { year: true })}`,
`${tableHeading} Appointments ${formatDate(new Date(appointments[0]?.startDateTime), {
Copy link
Member

Choose a reason for hiding this comment

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

FIXME for latter, but this should all be translatable.

Comment on lines +40 to +55
const pastAppointments = appointments
?.sort((a, b) => (b.startDateTime > a.startDateTime ? 1 : -1))
?.filter(({ status }) => status !== 'Cancelled')
?.filter(({ startDateTime }) =>
dayjs(new Date(startDateTime).toISOString()).isBefore(new Date().setHours(0, 0, 0, 0)),
);

const upcomingAppointments = appointments
?.sort((a, b) => (a.startDateTime > b.startDateTime ? 1 : -1))
?.filter(({ status }) => status !== 'Cancelled')
?.filter(({ startDateTime }) => dayjs(new Date(startDateTime).toISOString()).isAfter(new Date()));

const todaysAppointments = appointments
?.sort((a, b) => (a.startDateTime > b.startDateTime ? 1 : -1))
?.filter(({ status }) => status !== 'Cancelled')
?.filter(({ startDateTime }) => dayjs(new Date(startDateTime).toISOString()).isToday());
Copy link
Member

Choose a reason for hiding this comment

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

This seems refactorable:

const activeAppointments = appointments
  ?.filter(({ status }) => status !== 'Cancelled') ?? [];

// no ? here since `activeAppointments` is guaranteed to be an array
const pastAppointments = activeAppointments
  .sort((a, b) => (b.startDateTime > a.startDateTime ? 1 : -1))
  .filter(({ startDateTime }) =>
      dayjs(new Date(startDateTime).toISOString()).isBefore(new Date().setHours(0, 0, 0, 0)),
    );

Not essential.

Copy link
Member Author

Choose a reason for hiding this comment

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

Will take a look, just copied this code over from patient chart so hesitant to refactor unless I can clearly test.

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh, yes, this makes total sense @ibacher . However :) I'm not sure where this code is currently being used, so out of an abundance of caution I'm not going to refactor something I'm not 100% sure how to test. I suspect this will come over when I refactor the patient appointments to use this, instead of the version in the patient chart.

Comment on lines 104 to 114
export function getAppointmentsByUuid(appointmentUuid: string, abortController: AbortController) {
return openmrsFetch(`/ws/rest/v1/appointments/${appointmentUuid}`, {
signal: abortController.signal,
});
}

export function getAppointmentService(abortController: AbortController, uuid) {
return openmrsFetch(`/ws/rest/v1/appointmentService?uuid=` + uuid, {
signal: abortController.signal,
});
}
Copy link
Member

Choose a reason for hiding this comment

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

I haven't tracked the context in which these are used, but can they be re-written to take advantage of SWR?

Copy link
Member Author

Choose a reason for hiding this comment

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

Code I copied over, I'm not sure where they are used as well... I hope to do a more thorough cleanup of all the resource calls in the future, I'll had a TODO to the code to take a look at that then.

});
}

export const cancelAppointment = async (toStatus: string, appointmentUuid: string) => {
Copy link
Member

Choose a reason for hiding this comment

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

Probably not this PR, but a general comment: If the only point at which you have an await in a function body is in the return, there is no point in marking it async.

(toStatus: string, appointmentUuid: string) => {
 // ... snip ...
 return openmrsFetch(/*... snip ...*/);
}

Is precisely equivalent and takes less typing.

@@ -51,7 +51,7 @@ export function startupApp() {
},
{
path: `${window.spaBase}/patient-list/:forDate/:serviceName`,
title: ([date, serviceName]) => `Patient Lists / ${decodeURI(serviceName)}`,
title: ([serviceName]) => `Patient Lists / ${decodeURI(serviceName)}`,
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't this be:

Suggested change
title: ([serviceName]) => `Patient Lists / ${decodeURI(serviceName)}`,
title: ([_, serviceName]) => `Patient Lists / ${decodeURI(serviceName)}`,

Copy link
Member Author

Choose a reason for hiding this comment

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

Oops, good catch @ibacher I just removed the unused parameter based on the warning without thinking of the ramifications.

@@ -3,3 +3,51 @@ export const basePath = '/appointments';
export const spaBasePath = `${window.spaBase}/home`;
Copy link
Member

Choose a reason for hiding this comment

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

Future refactoring:

  1. This should probably be called spaHomePage
  2. The definition should be ${window.getOpenmrsSpaBase()}home (getOpenmrsSpaBase() is normalised so it always returns a path ending in /. window.spaBase is a direct representation of whatever was fed into the build the process, and thus may or may not contain a trailing slash).

Copy link
Member Author

Choose a reason for hiding this comment

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

Updated, thanks @ibacher !


export function useTodaysAppointments() {
const { t } = useTranslation();
const { currentAppointmentDate } = useAppointmentDate();

const apiUrl = `/ws/rest/v1/appointment/all?forDate=${currentAppointmentDate}`;
const { data, error, isLoading, isValidating, mutate } = useSWR<{ data: Array<Appointment> }, Error>(
const { data, error, isLoading, isValidating, mutate } = useSWR<AppointmentsFetchResponse, Error>(
currentAppointmentDate ? apiUrl : null,
openmrsFetch,
);

const results = useMemo(() => {
Copy link
Contributor

Choose a reason for hiding this comment

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

minor: we can remove the useMemo as it's just creating an object.

const appointments = data?.data?.length ? data.data : null;

const pastAppointments = appointments
?.sort((a, b) => (b.startDateTime > a.startDateTime ? 1 : -1))
Copy link
Contributor

Choose a reason for hiding this comment

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

This is different from the others. is the descending sort intentional?

Copy link
Member Author

Choose a reason for hiding this comment

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

I just copied this code over from the patient chart, but will test and review. I could see it making sense that you'd want descending on some views and ascending in others, maybe I need to look closer into that. For instance, in a simple list of a patient's past appointments you might want the most recent in the past first, but for any daily view you probably want the earliest first.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I think the idea is that you want to sort past appointments in a different order (newest first) than upcoming and today (oldest first). Will leave this for now and we can update as user test and review the functionality..

Copy link
Member Author

@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.

Thanks @ibacher @chibongho for slogging through this long PR.... I'd added comments, will go ahead and make the changes I mention. I also want to add back in a couple features based on Antony's comments on the ticket. will post when things are ready for re-review.

@@ -18,14 +18,18 @@
"extract-translations": "turbo extract-translations -- --config ../../tools/i18next-parser.config.js"
},
"dependencies": {
"@hookform/resolvers": "^3.3.1",
Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks @ibacher ! I thought I was just keeping this consistent with the patient chart, but it looks like they are all devDependencies there, I will correct.

},
"devDependencies": {
"@babel/core": "^7.11.6",
"@carbon/react": "~1.37.0",
"@openmrs/esm-framework": "next",
"@openmrs/esm-patient-common-lib": "next",
Copy link
Member Author

Choose a reason for hiding this comment

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

I forget why I needed it, but it was because something wasn't working... potentially something I later removed, I will take it out and see if things still work.

}

const AppointmentActions: React.FC<AppointmentActionsProps> = ({ visits, appointment, scheduleType }) => {
const AppointmentActions: React.FC<AppointmentActionsProps> = ({ visits, appointment, mutate }) => {
Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, it was annoying me that I had to pass this all the way around. I can check out out PatientSearchContext.

Another pattern I was seeing was using the useSWR call at multiple levels just to retrieve the mutate function, but that didn't seem correct, and seemed dependent on making sure you were asking for the same/correct key at multiple levels.

export const datePickerFormat = 'd/m/Y';
export const weekDays = [
{
id: 'MONDAY',
Copy link
Member Author

Choose a reason for hiding this comment

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

this was stuff I just copied directly from the patient-chart when copying the appointment form over, I can look into simplifying it.

const appointments = data?.data?.length ? data.data : null;

const pastAppointments = appointments
?.sort((a, b) => (b.startDateTime > a.startDateTime ? 1 : -1))
Copy link
Member Author

Choose a reason for hiding this comment

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

I just copied this code over from the patient chart, but will test and review. I could see it making sense that you'd want descending on some views and ascending in others, maybe I need to look closer into that. For instance, in a simple list of a patient's past appointments you might want the most recent in the past first, but for any daily view you probably want the earliest first.

Comment on lines +40 to +55
const pastAppointments = appointments
?.sort((a, b) => (b.startDateTime > a.startDateTime ? 1 : -1))
?.filter(({ status }) => status !== 'Cancelled')
?.filter(({ startDateTime }) =>
dayjs(new Date(startDateTime).toISOString()).isBefore(new Date().setHours(0, 0, 0, 0)),
);

const upcomingAppointments = appointments
?.sort((a, b) => (a.startDateTime > b.startDateTime ? 1 : -1))
?.filter(({ status }) => status !== 'Cancelled')
?.filter(({ startDateTime }) => dayjs(new Date(startDateTime).toISOString()).isAfter(new Date()));

const todaysAppointments = appointments
?.sort((a, b) => (a.startDateTime > b.startDateTime ? 1 : -1))
?.filter(({ status }) => status !== 'Cancelled')
?.filter(({ startDateTime }) => dayjs(new Date(startDateTime).toISOString()).isToday());
Copy link
Member Author

Choose a reason for hiding this comment

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

Will take a look, just copied this code over from patient chart so hesitant to refactor unless I can clearly test.

Comment on lines 104 to 114
export function getAppointmentsByUuid(appointmentUuid: string, abortController: AbortController) {
return openmrsFetch(`/ws/rest/v1/appointments/${appointmentUuid}`, {
signal: abortController.signal,
});
}

export function getAppointmentService(abortController: AbortController, uuid) {
return openmrsFetch(`/ws/rest/v1/appointmentService?uuid=` + uuid, {
signal: abortController.signal,
});
}
Copy link
Member Author

Choose a reason for hiding this comment

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

Code I copied over, I'm not sure where they are used as well... I hope to do a more thorough cleanup of all the resource calls in the future, I'll had a TODO to the code to take a look at that then.

};

export const useCalendarDistribution = (
servieUuid: string,
Copy link
Member

Choose a reason for hiding this comment

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

typo?

Copy link
Member Author

Choose a reason for hiding this comment

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

fixed!

Copy link
Member Author

@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.

Okay, I think I resolved and/or commented on all the PR questions...see my new comments

@@ -18,14 +18,18 @@
"extract-translations": "turbo extract-translations -- --config ../../tools/i18next-parser.config.js"
},
"dependencies": {
"@hookform/resolvers": "^3.3.1",
Copy link
Member Author

Choose a reason for hiding this comment

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

Okay, so looking again, I see that these dependencies are listed as dependencies, not devDependencies, in patient-chart. However, when I moved them all to devDependencies here things worked. Do we have a best practice regarding this (to be honest, I don't really understand why these are all devdepencies, is the assumption thst they are all bundled with ESM framework anyway)? @ibacher @denniskigen @brandones

},
"devDependencies": {
"@babel/core": "^7.11.6",
"@carbon/react": "~1.37.0",
"@openmrs/esm-framework": "next",
"@openmrs/esm-patient-common-lib": "next",
Copy link
Member Author

Choose a reason for hiding this comment

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

It's used in the appointment-form.component @ibacher

import { convertTime12to24 } from '@openmrs/esm-patient-common-lib';

}

const AppointmentActions: React.FC<AppointmentActionsProps> = ({ visits, appointment, scheduleType }) => {
const AppointmentActions: React.FC<AppointmentActionsProps> = ({ visits, appointment, mutate }) => {
Copy link
Member Author

Choose a reason for hiding this comment

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

In the interest of getting this PR merged without continuing to bloat, I will handle this as a separate ticket here: https://openmrs.atlassian.net/browse/O3-2849

export const datePickerFormat = 'd/m/Y';
export const weekDays = [
{
id: 'MONDAY',
Copy link
Member Author

Choose a reason for hiding this comment

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

Thinking through this, I'm inclined to keep the id... IMO it's beneficial for each element to have a defined id, even if it just directly maps to the label.

const appointments = data?.data?.length ? data.data : null;

const pastAppointments = appointments
?.sort((a, b) => (b.startDateTime > a.startDateTime ? 1 : -1))
Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I think the idea is that you want to sort past appointments in a different order (newest first) than upcoming and today (oldest first). Will leave this for now and we can update as user test and review the functionality..

Comment on lines +40 to +55
const pastAppointments = appointments
?.sort((a, b) => (b.startDateTime > a.startDateTime ? 1 : -1))
?.filter(({ status }) => status !== 'Cancelled')
?.filter(({ startDateTime }) =>
dayjs(new Date(startDateTime).toISOString()).isBefore(new Date().setHours(0, 0, 0, 0)),
);

const upcomingAppointments = appointments
?.sort((a, b) => (a.startDateTime > b.startDateTime ? 1 : -1))
?.filter(({ status }) => status !== 'Cancelled')
?.filter(({ startDateTime }) => dayjs(new Date(startDateTime).toISOString()).isAfter(new Date()));

const todaysAppointments = appointments
?.sort((a, b) => (a.startDateTime > b.startDateTime ? 1 : -1))
?.filter(({ status }) => status !== 'Cancelled')
?.filter(({ startDateTime }) => dayjs(new Date(startDateTime).toISOString()).isToday());
Copy link
Member Author

Choose a reason for hiding this comment

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

Oh, yes, this makes total sense @ibacher . However :) I'm not sure where this code is currently being used, so out of an abundance of caution I'm not going to refactor something I'm not 100% sure how to test. I suspect this will come over when I refactor the patient appointments to use this, instead of the version in the patient chart.

@@ -51,7 +51,7 @@ export function startupApp() {
},
{
path: `${window.spaBase}/patient-list/:forDate/:serviceName`,
title: ([date, serviceName]) => `Patient Lists / ${decodeURI(serviceName)}`,
title: ([serviceName]) => `Patient Lists / ${decodeURI(serviceName)}`,
Copy link
Member Author

Choose a reason for hiding this comment

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

Oops, good catch @ibacher I just removed the unused parameter based on the warning without thinking of the ramifications.

@@ -3,3 +3,51 @@ export const basePath = '/appointments';
export const spaBasePath = `${window.spaBase}/home`;
Copy link
Member Author

Choose a reason for hiding this comment

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

Updated, thanks @ibacher !

@mogoodrich
Copy link
Member Author

whew! I think this is ready to go, going to merge this in, thanks everyone!

@mogoodrich mogoodrich merged commit 08b09a1 into main Feb 13, 2024
6 checks passed
@mogoodrich mogoodrich deleted the O3-2827 branch February 13, 2024 13:27
@denniskigen
Copy link
Member

denniskigen commented Mar 8, 2024

Hey, @mogoodrich, @ibacher there's currently a regression involving the Today's appointment widget on the dev3 home page:

CleanShot 2024-03-08 at 4  13 28@2x

From the response, it appears that the Inpatient Ward location doesn't have a display property, which we're expecting to exist here when creating the table rows to render in the AppointmentsBaseTable component.

Is this likely indicative of an unexpected change in the locations (I'd expect locations to have a display property) and should we fix it there, or is it preferable to add optional chaining checks when accessing these properties on the client?

Also, @ibacher, a timely reminder that we should come up with a better error state than this:

CleanShot 2024-03-08 at 4  19 20@2x

@mogoodrich
Copy link
Member Author

thanks @denniskigen ... I missed this this morning, but Mike just pointed it out to me, I will look at next week...

@mogoodrich
Copy link
Member Author

Actually, @denniskigen do you know when this regressed? Was this something that only worked when we were running the custom Palladium build of Bahmni Appointments on dev3 (which we were doing at one point, right). Because a lot of the Bahmni Appointments REST responses are non-standard, and, you are right, it should have a display property, but it doesn't and it looks like it's always been that way: https://github.com/Bahmni/openmrs-module-appointments/blob/master/omod/src/main/java/org/openmrs/module/appointments/web/mapper/AppointmentMapper.java#L266

For now, IMO, we should just build the appointments UI around this non-standard REST response (ie, just use location.name vs location.display)... fyi @mseaton @ibacher

@mseaton
Copy link
Member

mseaton commented Mar 10, 2024

do you know when this regressed

@mogoodrich / @denniskigen - I have a PR out to address this. The reason for this error is that there is a particular appointment coming back that has a null location. So if null locations are possible on appointments, then the UI just needs to account for that in the display, which it wasn't. On a secondary note, this actually highlights a bug in which the location (if not null) would never have been displayed right due to the use of display over name. I've fixed that in the PR too.

@mogoodrich
Copy link
Member Author

Makes sense, @mseaton ... so I suspect this was never working, period, but wasn't also throwing an error until we had an appointment with a null location on the demo server.

@mogoodrich
Copy link
Member Author

To clarify @denniskigen , @mseaton did point out that it was likely a change I had made that broke this, I had forgotten I had touched that code...

@denniskigen
Copy link
Member

Actually, @denniskigen do you know when this regressed? Was this something that only worked when we were running the custom Palladium build of Bahmni Appointments on dev3 (which we were doing at one point, right). Because a lot of the Bahmni Appointments REST responses are non-standard, and, you are right, it should have a display property, but it doesn't and it looks like it's always been that way: Bahmni/openmrs-module-appointments@master/omod/src/main/java/org/openmrs/module/appointments/web/mapper/AppointmentMapper.java#L266

For now, IMO, we should just build the appointments UI around this non-standard REST response (ie, just use location.name vs location.display)... fyi @mseaton @ibacher

It looks like this is happening again on dev3 after it got reset @mogoodrich. Is the location a required field when scheduling an appointment?

CleanShot 2024-05-13 at 11  33 21@2x

@ibacher
Copy link
Member

ibacher commented May 13, 2024

Hmmm... I think the issue is that it's not required (at least in the version we're running), but the frontend assumes it's always there. It's likely this is just how the demo data is populated, which should be fixable,

@mogoodrich
Copy link
Member Author

Thanks @denniskigen ! Yes, location should be null-safe, not sure if this was a regression or a place where we never fixed this... I pushed up what should be a quick fix: 3feebb1

I have to step away now but should be able confirm that it fixed it later tonight.

@mogoodrich
Copy link
Member Author

Looks like it is working again, sorry and thanks for pointing it out @denniskigen

2024-05-13_21-25

padding-top: spacing.$spacing-03;
}

:global(.cds--select-input):focus {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Hello @mogoodrich is this intended from the designs!, as this affects other fields not on the appointment form.
Thanks
cc @brandones @vasharma05

Copy link
Member Author

Choose a reason for hiding this comment

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

It's a very good point @usamaidrsk ... I just copied this scss class over as-is from the patient-chart, but I would think that everything in this class should be namespaces in some way to only to apply to this page... and there's likely stuff that may not be needed at all.

Maybe the best thing to do would be to comment out everything from this class and view the appointments-form from the app and then only add back in things that are necessary for that appointment-form, namespacing as necessary. How does that sound @usamaidrsk ?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Thanks for the response @mogoodrich , I think we can try with the suggestion you've made.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah... the two @usamaidrsk flagged are a bit of a weird case.

.formWrapper :global(.cds--select-input):focus

Would be safe, but as-written, this is overriding the behaviour of all .cds--select-input elements.

margin-top: 0.5rem;
}

:global(.cds--number input[type='number']):focus {
Copy link
Collaborator

@usamaidrsk usamaidrsk Jun 19, 2024

Choose a reason for hiding this comment

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

This too @mogoodrich , point mentioned here

@brandones
Copy link
Contributor

Screenshots are a requirement in PRs that make UI changes. Let's please enforce that requirement @mogoodrich @ibacher @chibongho .

Also not sure why a "refactor" included UI changes at all. A summary would have been helpful.

@mogoodrich
Copy link
Member Author

@brandones summary can be found on the ticket: https://openmrs.atlassian.net/browse/O3-2827

And there were not intended to be design changes, the scss files were just copied from one ESM to another with the assumption that any styling applied in appointments-form.scss would only be applied to the appointments-form, which I was also moving over. However, I looks like I should not have made that assumption, and it wasn't correct...

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.

8 participants