@openmrs/esm-appointments-app 9.2.1-pre.7303 → 9.2.1-pre.7315

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/.turbo/turbo-build.log +8 -8
  2. package/dist/1431.js +1 -1
  3. package/dist/1431.js.map +1 -1
  4. package/dist/1559.js +1 -1
  5. package/dist/1559.js.map +1 -1
  6. package/dist/2265.js +1 -0
  7. package/dist/2265.js.map +1 -0
  8. package/dist/{5160.js → 4036.js} +1 -1
  9. package/dist/{5160.js.map → 4036.js.map} +1 -1
  10. package/dist/449.js +1 -1
  11. package/dist/449.js.map +1 -1
  12. package/dist/4515.js +1 -0
  13. package/dist/4515.js.map +1 -0
  14. package/dist/4745.js +1 -1
  15. package/dist/4745.js.map +1 -1
  16. package/dist/4889.js +1 -1
  17. package/dist/4889.js.map +1 -1
  18. package/dist/525.js +1 -1
  19. package/dist/525.js.map +1 -1
  20. package/dist/5666.js +1 -1
  21. package/dist/5666.js.map +1 -1
  22. package/dist/5755.js +1 -1
  23. package/dist/5755.js.map +1 -1
  24. package/dist/592.js +1 -1
  25. package/dist/592.js.map +1 -1
  26. package/dist/6467.js +1 -1
  27. package/dist/6467.js.map +1 -1
  28. package/dist/6886.js +1 -1
  29. package/dist/6886.js.map +1 -1
  30. package/dist/{7565.js → 7294.js} +1 -1
  31. package/dist/7294.js.map +1 -0
  32. package/dist/9712.js +1 -1
  33. package/dist/9712.js.map +1 -1
  34. package/dist/main.js +1 -1
  35. package/dist/main.js.map +1 -1
  36. package/dist/openmrs-esm-appointments-app.js.buildmanifest.json +121 -120
  37. package/dist/routes.json +1 -1
  38. package/package.json +1 -1
  39. package/src/appointments/common-components/appointments-table.component.tsx +4 -6
  40. package/src/constants.ts +1 -0
  41. package/src/form/appointments-form.resource.ts +1 -0
  42. package/src/form/appointments-form.test.tsx +111 -71
  43. package/src/form/appointments-form.workspace.tsx +437 -436
  44. package/src/form/exported-appointments-form.workspace.tsx +24 -0
  45. package/src/helpers/functions.ts +72 -25
  46. package/src/index.ts +5 -3
  47. package/src/metrics/metrics-cards/highest-volume-service.extension.tsx +1 -1
  48. package/src/metrics/metrics-cards/metrics-card.component.tsx +1 -1
  49. package/src/metrics/metrics-header.component.tsx +9 -24
  50. package/src/patient-appointments/patient-appointments-action-menu.component.tsx +2 -6
  51. package/src/patient-appointments/patient-appointments-detailed-summary.extension.tsx +176 -15
  52. package/src/patient-appointments/{patient-appointments-base.test.tsx → patient-appointments-detailed-summary.test.tsx} +14 -22
  53. package/src/patient-appointments/patient-appointments-overview.component.tsx +15 -18
  54. package/src/routes.json +22 -7
  55. package/dist/3092.js +0 -1
  56. package/dist/3092.js.map +0 -1
  57. package/dist/7026.js +0 -1
  58. package/dist/7026.js.map +0 -1
  59. package/dist/7565.js.map +0 -1
  60. package/src/hooks/patient-appointment-context.ts +0 -18
  61. package/src/patient-appointments/patient-appointments-base.component.tsx +0 -178
  62. package/src/patient-search/patient-search.component.tsx +0 -33
  63. package/src/patient-search/patient-search.scss +0 -24
  64. /package/src/patient-appointments/{patient-appointments-base.scss → patient-appointments-detailed-summary.scss} +0 -0
@@ -0,0 +1,24 @@
1
+ import React from 'react';
2
+ import { type Workspace2DefinitionProps } from '@openmrs/esm-framework';
3
+ import type { Appointment, RecurringPattern } from '../types';
4
+ import AppointmentsForm from './appointments-form.workspace';
5
+
6
+ interface ExportedAppointmentsFormProps {
7
+ appointment?: Appointment;
8
+ recurringPattern?: RecurringPattern;
9
+ }
10
+
11
+ interface ExportedAppointmentsFormGroupProps {
12
+ patientUuid: string;
13
+ }
14
+
15
+ /**
16
+ * Workspace used to create or edit an appointment in the patient chart (or app with compatible workspaceGroup)
17
+ */
18
+ const ExportedAppointmentsForm: React.FC<
19
+ Workspace2DefinitionProps<ExportedAppointmentsFormProps, {}, ExportedAppointmentsFormGroupProps>
20
+ > = ({ workspaceProps: { appointment, recurringPattern }, groupProps: { patientUuid }, ...rest }) => {
21
+ return <AppointmentsForm workspaceProps={{ appointment, recurringPattern, patientUuid }} groupProps={{}} {...rest} />;
22
+ };
23
+
24
+ export default ExportedAppointmentsForm;
@@ -1,41 +1,65 @@
1
1
  import dayjs, { type Dayjs } from 'dayjs';
2
- import { formatDate, parseDate } from '@openmrs/esm-framework';
3
- import { type AppointmentSummary, type Appointment } from '../types';
4
- import { configSchema } from '../config-schema';
2
+ import { type TFunction } from 'i18next';
3
+ import { launchWorkspace2, type Workspace2DefinitionProps } from '@openmrs/esm-framework';
4
+ import { type AppointmentSummary, type AppointmentCountMap } from '../types';
5
+ import { appointmentsFormWorkspace } from '../constants';
5
6
 
6
- export const getHighestAppointmentServiceLoad = (appointmentSummary: Array<any> = []) => {
7
- const groupedAppointments = appointmentSummary?.map(({ countMap, serviceName }) => ({
7
+ interface FlattenedAppointmentSummary {
8
+ serviceName: string;
9
+ countMap: AppointmentCountMap[];
10
+ }
11
+
12
+ interface ServiceLoadSummary {
13
+ serviceName: string;
14
+ count: number;
15
+ }
16
+
17
+ export const getHighestAppointmentServiceLoad = (
18
+ appointmentSummary: FlattenedAppointmentSummary[] = [],
19
+ ): ServiceLoadSummary | undefined => {
20
+ const groupedAppointments = appointmentSummary.map(({ countMap, serviceName }) => ({
8
21
  serviceName: serviceName,
9
- count: countMap.reduce((cummulator, currentValue) => cummulator + currentValue.allAppointmentsCount, 0),
22
+ count: countMap.reduce((accumulator, currentValue) => accumulator + currentValue.allAppointmentsCount, 0),
10
23
  }));
24
+ if (groupedAppointments.length === 0) {
25
+ return undefined;
26
+ }
11
27
  return groupedAppointments.find((summary) => summary.count === Math.max(...groupedAppointments.map((x) => x.count)));
12
28
  };
13
29
 
14
- export const flattenAppointmentSummary = (appointmentToTransfrom: Array<any>) =>
15
- appointmentToTransfrom.flatMap((el: any) => ({
30
+ export const flattenAppointmentSummary = (
31
+ appointmentToTransform: AppointmentSummary[],
32
+ ): FlattenedAppointmentSummary[] =>
33
+ appointmentToTransform.flatMap((el) => ({
16
34
  serviceName: el.appointmentService.name,
17
- countMap: Object.entries(el.appointmentCountMap).flatMap((el) => el[1]),
35
+ countMap: Object.entries(el.appointmentCountMap).flatMap(([, countMap]) => countMap),
18
36
  }));
19
37
 
20
38
  export const getServiceCountByAppointmentType = (
21
- appointmentSummary: Array<AppointmentSummary>,
22
- appointmentType: string,
23
- ) => {
39
+ appointmentSummary: AppointmentSummary[],
40
+ appointmentType: 'allAppointmentsCount' | 'missedAppointmentsCount',
41
+ ): number => {
24
42
  return appointmentSummary
25
- .map((el) => Object.entries(el.appointmentCountMap).flatMap((el) => el[1][appointmentType]))
43
+ .map((el) =>
44
+ Object.values(el.appointmentCountMap).map((countMap) => {
45
+ const value = countMap[appointmentType];
46
+ if (typeof value === 'number') {
47
+ return value;
48
+ }
49
+ return 0;
50
+ }),
51
+ )
26
52
  .flat(1)
27
53
  .reduce((count, val) => count + val, 0);
28
54
  };
29
55
 
30
- export const formatAMPM = (date) => {
31
- let hours = date.getHours();
32
- let minutes = date.getMinutes();
33
- let ampm = hours >= 12 ? 'PM' : 'AM';
34
- hours = hours % 12;
35
- hours = hours ? hours : 12; // the hour '0' should be '12'
36
- minutes = minutes < 10 ? '0' + minutes : minutes;
37
- const strTime = hours + ':' + minutes + ' ' + ampm;
38
- return strTime;
56
+ export const formatAMPM = (date: Date): string => {
57
+ const hours24 = date.getHours();
58
+ const minutes = date.getMinutes();
59
+ const ampm = hours24 >= 12 ? 'PM' : 'AM';
60
+ const hours12 = hours24 % 12 || 12; // Convert 0 to 12
61
+ const minutesStr = minutes < 10 ? `0${minutes}` : minutes.toString();
62
+ return `${hours12}:${minutesStr} ${ampm}`;
39
63
  };
40
64
 
41
65
  export const isSameMonth = (cellDate: Dayjs, currentDate: Dayjs) => {
@@ -51,7 +75,7 @@ export const monthDays = (currentDate: Dayjs) => {
51
75
  let days: Dayjs[] = [];
52
76
 
53
77
  for (let i = lastMonth.daysInMonth() - monthStart.day() + 1; i <= lastMonth.daysInMonth(); i++) {
54
- days.push(dayjs().month(lastMonth.month()).date(i));
78
+ days.push(lastMonth.date(i));
55
79
  }
56
80
 
57
81
  for (let i = 1; i <= monthDays; i++) {
@@ -61,12 +85,12 @@ export const monthDays = (currentDate: Dayjs) => {
61
85
  const dayLen = days.length > 30 ? 7 : 14;
62
86
 
63
87
  for (let i = 1; i < dayLen - monthEnd.day(); i++) {
64
- days.push(dayjs().month(nextMonth.month()).date(i));
88
+ days.push(nextMonth.date(i));
65
89
  }
66
90
  return days;
67
91
  };
68
92
 
69
- export const getGender = (gender, t) => {
93
+ export const getGender = (gender: string | undefined, t: TFunction<'translation', undefined>): string => {
70
94
  switch (gender) {
71
95
  case 'M':
72
96
  return t('male', 'Male');
@@ -80,3 +104,26 @@ export const getGender = (gender, t) => {
80
104
  return gender;
81
105
  }
82
106
  };
107
+
108
+ export const launchCreateAppointmentForm = (t: TFunction<'translation', undefined>) => {
109
+ launchWorkspace2(
110
+ 'appointments-patient-search-workspace',
111
+ {
112
+ initialQuery: '',
113
+ workspaceTitle: t('createNewAppointment', 'Create new appointment'),
114
+ onPatientSelected(
115
+ patientUuid: string,
116
+ patient: fhir.Patient,
117
+ launchChildWorkspace: Workspace2DefinitionProps['launchChildWorkspace'],
118
+ closeWorkspace: Workspace2DefinitionProps['closeWorkspace'],
119
+ ) {
120
+ launchChildWorkspace(appointmentsFormWorkspace, {
121
+ patientUuid: patient.id,
122
+ });
123
+ },
124
+ },
125
+ {
126
+ startVisitWorkspaceName: 'appointments-patient-search-start-visit-workspace',
127
+ },
128
+ );
129
+ };
package/src/index.ts CHANGED
@@ -92,8 +92,6 @@ export const metricsCardProvidersBooked = getAsyncLifecycle(
92
92
  options,
93
93
  );
94
94
 
95
- export const searchPatient = getAsyncLifecycle(() => import('./patient-search/patient-search.component'), options);
96
-
97
95
  // t('Appointments', 'Appointments')
98
96
  export const patientAppointmentsSummaryDashboardLink = getAsyncLifecycle(async () => {
99
97
  const commonLib = await import('@openmrs/esm-patient-common-lib');
@@ -120,9 +118,13 @@ export const cancelAppointmentModal = getAsyncLifecycle(
120
118
  options,
121
119
  );
122
120
 
123
- // t('createNewAppointment', 'Create new appointment')
124
121
  export const appointmentsFormWorkspace = getAsyncLifecycle(() => import('./form/appointments-form.workspace'), options);
125
122
 
123
+ export const exportedAppointmentsFormWorkspace = getAsyncLifecycle(
124
+ () => import('./form/exported-appointments-form.workspace'),
125
+ options,
126
+ );
127
+
126
128
  export const endAppointmentModal = getAsyncLifecycle(
127
129
  () => import('./appointments/common-components/end-appointment.modal'),
128
130
  options,
@@ -19,7 +19,7 @@ export default function HighestVolumeServiceExtension() {
19
19
  return (
20
20
  <MetricsCard
21
21
  headerLabel={t('highestServiceVolume', 'Highest volume service: {{time}}', { time: formattedStartDate })}
22
- label={highestServiceLoad?.count !== 0 ? t(highestServiceLoad?.serviceName) : t('serviceName', 'Service name')}
22
+ label={highestServiceLoad ? t(highestServiceLoad.serviceName) : t('serviceName', 'Service name')}
23
23
  value={highestServiceLoad?.count ?? '--'}
24
24
  />
25
25
  );
@@ -9,7 +9,7 @@ dayjs.extend(isSameOrBefore);
9
9
 
10
10
  interface MetricsCardProps {
11
11
  label: string;
12
- value: number;
12
+ value: number | string;
13
13
  headerLabel: string;
14
14
  count?: { pendingAppointments: Array<any>; arrivedAppointments: Array<any> };
15
15
  }
@@ -4,10 +4,11 @@ import isToday from 'dayjs/plugin/isToday';
4
4
  import { useTranslation } from 'react-i18next';
5
5
  import { Calendar, Hospital } from '@carbon/react/icons';
6
6
  import { Button } from '@carbon/react';
7
- import { ExtensionSlot, isDesktop, launchWorkspace, navigate, useLayoutType } from '@openmrs/esm-framework';
7
+ import { isDesktop, navigate, useLayoutType } from '@openmrs/esm-framework';
8
8
  import { spaHomePage } from '../constants';
9
9
  import { useAppointmentsStore } from '../store';
10
10
  import styles from './metrics-header.scss';
11
+ import { launchCreateAppointmentForm } from '../helpers';
11
12
 
12
13
  dayjs.extend(isToday);
13
14
 
@@ -17,16 +18,6 @@ const MetricsHeader: React.FC = () => {
17
18
  const layout = useLayoutType();
18
19
  const responsiveSize = isDesktop(layout) ? 'sm' : 'md';
19
20
 
20
- const launchCreateAppointmentForm = (patientUuid) => {
21
- const props = {
22
- patientUuid: patientUuid,
23
- context: 'creating',
24
- mutate: () => {}, // TODO get this to mutate properly
25
- };
26
-
27
- launchWorkspace('appointments-form-workspace', { ...props });
28
- };
29
-
30
21
  return (
31
22
  <div className={styles.metricsContainer}>
32
23
  <div className={styles.metricsContent}>
@@ -39,19 +30,13 @@ const MetricsHeader: React.FC = () => {
39
30
  }>
40
31
  {t('appointmentsCalendar', 'Appointments calendar')}
41
32
  </Button>
42
- <ExtensionSlot
43
- name="patient-search-button-slot"
44
- state={{
45
- selectPatientAction: launchCreateAppointmentForm,
46
- buttonText: t('createNewAppointment', 'Create new appointment'),
47
- overlayHeader: t('createNewAppointment', 'Create new appointment'),
48
- buttonProps: {
49
- kind: 'primary',
50
- renderIcon: (props) => <Hospital size={32} {...props} />,
51
- size: responsiveSize,
52
- },
53
- }}
54
- />
33
+ <Button
34
+ kind="primary"
35
+ renderIcon={(props) => <Hospital size={32} {...props} />}
36
+ size={responsiveSize}
37
+ onClick={() => launchCreateAppointmentForm(t)}>
38
+ {t('createNewAppointment', 'Create new appointment')}
39
+ </Button>
55
40
  </div>
56
41
  </div>
57
42
  );
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import { useTranslation } from 'react-i18next';
3
3
  import { Layer, OverflowMenu, OverflowMenuItem } from '@carbon/react';
4
- import { launchWorkspace, showModal, useLayoutType } from '@openmrs/esm-framework';
4
+ import { launchWorkspace2, showModal, useLayoutType } from '@openmrs/esm-framework';
5
5
  import type { Appointment } from '../types';
6
6
  import styles from './patient-appointments-action-menu.scss';
7
7
 
@@ -15,11 +15,7 @@ export const PatientAppointmentsActionMenu = ({ appointment, patientUuid }: appo
15
15
  const isTablet = useLayoutType() === 'tablet';
16
16
 
17
17
  const handleLaunchEditAppointmentForm = () => {
18
- launchWorkspace('appointments-form-workspace', {
19
- appointment,
20
- context: 'editing',
21
- workspaceTitle: t('editAppointment', 'Edit appointment'),
22
- });
18
+ launchWorkspace2('appointments-form-workspace', { appointment, patientUuid });
23
19
  };
24
20
 
25
21
  const handleLaunchCancelAppointmentModal = () => {
@@ -1,23 +1,184 @@
1
- import React from 'react';
2
- import PatientAppointmentsBase from './patient-appointments-base.component';
3
- import {
4
- PatientAppointmentContextProvider,
5
- PatientAppointmentContextTypes,
6
- } from '../hooks/patient-appointment-context';
7
-
8
- interface PatientAppointmentsDetailedSummaryProps {
1
+ import React, { useState, useMemo } from 'react';
2
+ import dayjs from 'dayjs';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { Button, ContentSwitcher, DataTableSkeleton, InlineLoading, Layer, Switch, Tile } from '@carbon/react';
5
+ import { Add } from '@carbon/react/icons';
6
+ import { launchWorkspace2, useLayoutType } from '@openmrs/esm-framework';
7
+ import { CardHeader, EmptyDataIllustration, ErrorState } from '@openmrs/esm-patient-common-lib';
8
+ import { type Appointment } from '../types';
9
+ import { usePatientAppointments } from './patient-appointments.resource';
10
+ import PatientAppointmentsTable from './patient-appointments-table.component';
11
+ import styles from './patient-appointments-detailed-summary.scss';
12
+
13
+ interface PatientAppointmentsDetailProps {
9
14
  patientUuid: string;
15
+
16
+ /**
17
+ * Optional callback to launch the appropriate appointments form workspace, depending
18
+ * on which app is using this extension. If not provided, uses the default implementation
19
+ * for patient chart context.
20
+ */
21
+ launchAppointmentForm?(patientUuid: string, appointment?: Appointment): void;
22
+ }
23
+
24
+ enum AppointmentTypes {
25
+ UPCOMING = 0,
26
+ TODAY = 1,
27
+ PAST = 2,
10
28
  }
29
+
11
30
  /**
12
- * This component is wired in as an extension to render the patient appointments view (all appointments for a single patient) within of the context of the patient chart.
13
- * It uses the PatientAppointmentsBase component to render the actual appointments data.
31
+ * This extension displays a detailed summary of appointments for a single patient.
32
+ * Note that this extension can be used both in the patient chart and in the appointments app.
33
+ * Accordingly, the `launchAppointmentForm` callback is passed in to allow the app to define
34
+ * how the appointments form workspace is launched.
14
35
  */
15
- const PatientAppointmentsDetailedSummary: React.FC<PatientAppointmentsDetailedSummaryProps> = ({ patientUuid }) => {
16
- return (
17
- <PatientAppointmentContextProvider value={PatientAppointmentContextTypes.PATIENT_CHART}>
18
- <PatientAppointmentsBase patientUuid={patientUuid} />
19
- </PatientAppointmentContextProvider>
36
+ const PatientAppointmentsDetailedSummary: React.FC<PatientAppointmentsDetailProps> = ({
37
+ patientUuid,
38
+ launchAppointmentForm,
39
+ }) => {
40
+ const { t } = useTranslation();
41
+ const headerTitle = t('appointments', 'Appointments');
42
+ const isTablet = useLayoutType() === 'tablet';
43
+ const [switchedView, setSwitchedView] = useState(false);
44
+
45
+ // Default implementation for patient chart context
46
+ const defaultLaunchAppointmentForm = (patientUuid: string, appointment?: Appointment) => {
47
+ launchWorkspace2('appointments-form-workspace', { patientUuid, appointment });
48
+ };
49
+
50
+ const handleLaunchAppointmentForm = launchAppointmentForm || defaultLaunchAppointmentForm;
51
+
52
+ const [contentSwitcherValue, setContentSwitcherValue] = useState(0);
53
+ const startDate = useMemo(() => dayjs().subtract(6, 'month').toISOString(), []);
54
+ const {
55
+ data: appointmentsData,
56
+ error,
57
+ isLoading,
58
+ isValidating,
59
+ } = usePatientAppointments(
60
+ patientUuid,
61
+ startDate,
62
+ useMemo(() => new AbortController(), []),
20
63
  );
64
+
65
+ if (isLoading) {
66
+ return <DataTableSkeleton role="progressbar" compact={!isTablet} zebra />;
67
+ }
68
+
69
+ if (error) {
70
+ return <ErrorState headerTitle={headerTitle} error={error} />;
71
+ }
72
+
73
+ if (appointmentsData && Object.keys(appointmentsData)?.length) {
74
+ return (
75
+ <div className={styles.widgetCard}>
76
+ <CardHeader title={headerTitle}>
77
+ {isValidating ? (
78
+ <span>
79
+ <InlineLoading />
80
+ </span>
81
+ ) : null}
82
+ <div className={styles.contentSwitcherWrapper}>
83
+ <ContentSwitcher
84
+ size={isTablet ? 'md' : 'sm'}
85
+ onChange={({ index }) => {
86
+ setContentSwitcherValue(index);
87
+ setSwitchedView(true);
88
+ }}>
89
+ <Switch name={'upcoming'} text={t('upcoming', 'Upcoming')} />
90
+ <Switch name={'today'} text={t('today', 'Today')} />
91
+ <Switch name={'past'} text={t('past', 'Past')} />
92
+ </ContentSwitcher>
93
+ <div className={styles.divider}>|</div>
94
+ <Button
95
+ kind="ghost"
96
+ renderIcon={(props) => <Add size={16} {...props} />}
97
+ iconDescription="Add Appointments"
98
+ onClick={() => handleLaunchAppointmentForm(patientUuid)}>
99
+ {t('add', 'Add')}
100
+ </Button>
101
+ </div>
102
+ </CardHeader>
103
+ {(() => {
104
+ if (contentSwitcherValue === AppointmentTypes.UPCOMING) {
105
+ if (appointmentsData.upcomingAppointments?.length) {
106
+ return (
107
+ <PatientAppointmentsTable
108
+ patientAppointments={appointmentsData?.upcomingAppointments}
109
+ switchedView={switchedView}
110
+ setSwitchedView={setSwitchedView}
111
+ patientUuid={patientUuid}
112
+ />
113
+ );
114
+ }
115
+ return (
116
+ <Layer>
117
+ <Tile className={styles.tile}>
118
+ <EmptyDataIllustration />
119
+ <p className={styles.content}>
120
+ {t(
121
+ 'noUpcomingAppointmentsForPatient',
122
+ 'There are no upcoming appointments to display for this patient',
123
+ )}
124
+ </p>
125
+ </Tile>
126
+ </Layer>
127
+ );
128
+ }
129
+
130
+ if (contentSwitcherValue === AppointmentTypes.TODAY) {
131
+ if (appointmentsData.todaysAppointments?.length) {
132
+ return (
133
+ <PatientAppointmentsTable
134
+ patientAppointments={appointmentsData?.todaysAppointments}
135
+ switchedView={switchedView}
136
+ setSwitchedView={setSwitchedView}
137
+ patientUuid={patientUuid}
138
+ />
139
+ );
140
+ }
141
+ return (
142
+ <Layer>
143
+ <Tile className={styles.tile}>
144
+ <EmptyDataIllustration />
145
+ <p className={styles.content}>
146
+ {t(
147
+ 'noCurrentAppointments',
148
+ 'There are no appointments scheduled for today to display for this patient',
149
+ )}
150
+ </p>
151
+ </Tile>
152
+ </Layer>
153
+ );
154
+ }
155
+
156
+ if (contentSwitcherValue === AppointmentTypes.PAST) {
157
+ if (appointmentsData.pastAppointments?.length) {
158
+ return (
159
+ <PatientAppointmentsTable
160
+ patientAppointments={appointmentsData?.pastAppointments}
161
+ switchedView={switchedView}
162
+ setSwitchedView={setSwitchedView}
163
+ patientUuid={patientUuid}
164
+ />
165
+ );
166
+ }
167
+ return (
168
+ <Layer>
169
+ <Tile className={styles.tile}>
170
+ <EmptyDataIllustration />
171
+ <p className={styles.content}>
172
+ {t('noPastAppointments', 'There are no past appointments to display for this patient')}
173
+ </p>
174
+ </Tile>
175
+ </Layer>
176
+ );
177
+ }
178
+ })()}
179
+ </div>
180
+ );
181
+ }
21
182
  };
22
183
 
23
184
  export default PatientAppointmentsDetailedSummary;
@@ -3,32 +3,32 @@ import { screen } from '@testing-library/react';
3
3
  import userEvent from '@testing-library/user-event';
4
4
  import { type FetchResponse, openmrsFetch } from '@openmrs/esm-framework';
5
5
  import { mockAppointmentsData } from '__mocks__';
6
- import { mockPatient, patientChartBasePath, renderWithContext, waitForLoadingToFinish, withSwr } from 'tools';
7
- import { type AppointmentsFetchResponse } from '../types';
8
6
  import {
9
- PatientAppointmentContextProvider,
10
- PatientAppointmentContextTypes,
11
- } from '../hooks/patient-appointment-context';
12
- import AppointmentsBase from './patient-appointments-base.component';
7
+ mockPatient,
8
+ patientChartBasePath,
9
+ renderWithContext,
10
+ renderWithSwr,
11
+ waitForLoadingToFinish,
12
+ withSwr,
13
+ } from 'tools';
14
+ import { type AppointmentsFetchResponse } from '../types';
15
+ import AppointmentsDetailedSummary from './patient-appointments-detailed-summary.extension';
13
16
 
14
17
  const testProps = {
15
18
  basePath: patientChartBasePath,
16
19
  patientUuid: mockPatient.id,
20
+ launchAppointmentForm: jest.fn(),
17
21
  };
18
22
 
19
23
  const mockOpenmrsFetch = jest.mocked(openmrsFetch);
20
24
 
21
- describe('AppointmentsOverview', () => {
25
+ describe('AppointmentsDetailedSummary', () => {
22
26
  it('renders an empty state if appointments data is unavailable', async () => {
23
27
  mockOpenmrsFetch.mockResolvedValueOnce({
24
28
  data: [],
25
29
  } as unknown as FetchResponse<AppointmentsFetchResponse>);
26
30
 
27
- renderWithContext(
28
- withSwr(<AppointmentsBase {...testProps} />),
29
- PatientAppointmentContextProvider,
30
- PatientAppointmentContextTypes.APPOINTMENTS_APP,
31
- );
31
+ renderWithSwr(<AppointmentsDetailedSummary {...testProps} />);
32
32
 
33
33
  await waitForLoadingToFinish();
34
34
 
@@ -48,11 +48,7 @@ describe('AppointmentsOverview', () => {
48
48
 
49
49
  mockOpenmrsFetch.mockRejectedValueOnce(error);
50
50
 
51
- renderWithContext(
52
- withSwr(<AppointmentsBase {...testProps} />),
53
- PatientAppointmentContextProvider,
54
- PatientAppointmentContextTypes.APPOINTMENTS_APP,
55
- );
51
+ renderWithSwr(<AppointmentsDetailedSummary {...testProps} />);
56
52
 
57
53
  await waitForLoadingToFinish();
58
54
 
@@ -71,11 +67,7 @@ describe('AppointmentsOverview', () => {
71
67
  ...mockAppointmentsData,
72
68
  } as unknown as FetchResponse<AppointmentsFetchResponse>);
73
69
 
74
- renderWithContext(
75
- withSwr(<AppointmentsBase {...testProps} />),
76
- PatientAppointmentContextProvider,
77
- PatientAppointmentContextTypes.APPOINTMENTS_APP,
78
- );
70
+ renderWithSwr(<AppointmentsDetailedSummary {...testProps} />);
79
71
 
80
72
  await waitForLoadingToFinish();
81
73
 
@@ -1,12 +1,8 @@
1
1
  import React from 'react';
2
2
  import { DataTableSkeleton } from '@carbon/react';
3
3
  import { useParams } from 'react-router-dom';
4
- import { usePatient, useLayoutType, isDesktop, WorkspaceContainer } from '@openmrs/esm-framework';
5
- import {
6
- PatientAppointmentContextProvider,
7
- PatientAppointmentContextTypes,
8
- } from '../hooks/patient-appointment-context';
9
- import PatientAppointmentsBase from './patient-appointments-base.component';
4
+ import { usePatient, useLayoutType, isDesktop, WorkspaceContainer, launchWorkspace2 } from '@openmrs/esm-framework';
5
+ import PatientAppointmentsDetailedSummary from './patient-appointments-detailed-summary.extension';
10
6
  import PatientAppointmentsHeader from './patient-appointments-header.component';
11
7
  import styles from './patient-appointments-overview.scss';
12
8
 
@@ -22,18 +18,19 @@ const PatientAppointmentsOverview: React.FC = () => {
22
18
  const response = usePatient(params.patientUuid);
23
19
  const layout = useLayoutType();
24
20
 
25
- return (
26
- <PatientAppointmentContextProvider value={PatientAppointmentContextTypes.APPOINTMENTS_APP}>
27
- {response.isLoading ? (
28
- <DataTableSkeleton role="progressbar" compact={isDesktop(layout)} zebra />
29
- ) : (
30
- <div className={styles.patientAppointmentsOverview}>
31
- <PatientAppointmentsHeader patient={response.patient} />
32
- <PatientAppointmentsBase patientUuid={response.patient.id} />
33
- <WorkspaceContainer overlay contextKey={`patient/${params.patientUuid}`} />
34
- </div>
35
- )}
36
- </PatientAppointmentContextProvider>
21
+ return response.isLoading ? (
22
+ <DataTableSkeleton role="progressbar" compact={isDesktop(layout)} zebra />
23
+ ) : (
24
+ <div className={styles.patientAppointmentsOverview}>
25
+ <PatientAppointmentsHeader patient={response.patient} />
26
+ <PatientAppointmentsDetailedSummary
27
+ patientUuid={response.patient.id}
28
+ launchAppointmentForm={(patientUuid, appointment) => {
29
+ launchWorkspace2('appointments-form-workspace', { patientUuid, appointment });
30
+ }}
31
+ />
32
+ <WorkspaceContainer overlay contextKey={`patient/${params.patientUuid}`} />
33
+ </div>
37
34
  );
38
35
  };
39
36
 
package/src/routes.json CHANGED
@@ -97,10 +97,6 @@
97
97
  "component": "patientUpcomingAppointmentsWidget",
98
98
  "slot": "visit-form-top-slot"
99
99
  },
100
- {
101
- "name": "search-patient",
102
- "component": "searchPatient"
103
- },
104
100
  {
105
101
  "name": "home-appointments-tile",
106
102
  "slot": "home-metrics-tiles-slot",
@@ -135,12 +131,31 @@
135
131
  "component": "cancelAppointmentModal"
136
132
  }
137
133
  ],
138
- "workspaces": [
134
+ "workspaceGroups2": [{
135
+ "name": "appointments-group"
136
+ }
137
+ ],
138
+ "workspaceWindows2": [
139
+ {
140
+ "name": "appointments-window",
141
+ "group": "appointments-group"
142
+ }
143
+ ],
144
+ "workspaces2": [
139
145
  {
140
146
  "name": "appointments-form-workspace",
141
147
  "component": "appointmentsFormWorkspace",
142
- "title": "createNewAppointment",
143
- "type": "form"
148
+ "window": "appointments-window"
149
+ },
150
+ {
151
+ "name": "appointments-patient-search-workspace",
152
+ "component": "@openmrs/esm-patient-search-app#patientSearchWorkspace2",
153
+ "window": "appointments-window"
154
+ },
155
+ {
156
+ "name": "appointments-patient-search-start-visit-workspace",
157
+ "component": "@openmrs/esm-patient-chart-app#exportedVisitForm",
158
+ "window": "appointments-window"
144
159
  }
145
160
  ]
146
161
  }