@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.
- package/.turbo/turbo-build.log +8 -8
- package/dist/1431.js +1 -1
- package/dist/1431.js.map +1 -1
- package/dist/1559.js +1 -1
- package/dist/1559.js.map +1 -1
- package/dist/2265.js +1 -0
- package/dist/2265.js.map +1 -0
- package/dist/{5160.js → 4036.js} +1 -1
- package/dist/{5160.js.map → 4036.js.map} +1 -1
- package/dist/449.js +1 -1
- package/dist/449.js.map +1 -1
- package/dist/4515.js +1 -0
- package/dist/4515.js.map +1 -0
- package/dist/4745.js +1 -1
- package/dist/4745.js.map +1 -1
- package/dist/4889.js +1 -1
- package/dist/4889.js.map +1 -1
- package/dist/525.js +1 -1
- package/dist/525.js.map +1 -1
- package/dist/5666.js +1 -1
- package/dist/5666.js.map +1 -1
- package/dist/5755.js +1 -1
- package/dist/5755.js.map +1 -1
- package/dist/592.js +1 -1
- package/dist/592.js.map +1 -1
- package/dist/6467.js +1 -1
- package/dist/6467.js.map +1 -1
- package/dist/6886.js +1 -1
- package/dist/6886.js.map +1 -1
- package/dist/{7565.js → 7294.js} +1 -1
- package/dist/7294.js.map +1 -0
- package/dist/9712.js +1 -1
- package/dist/9712.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-appointments-app.js.buildmanifest.json +121 -120
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/appointments/common-components/appointments-table.component.tsx +4 -6
- package/src/constants.ts +1 -0
- package/src/form/appointments-form.resource.ts +1 -0
- package/src/form/appointments-form.test.tsx +111 -71
- package/src/form/appointments-form.workspace.tsx +437 -436
- package/src/form/exported-appointments-form.workspace.tsx +24 -0
- package/src/helpers/functions.ts +72 -25
- package/src/index.ts +5 -3
- package/src/metrics/metrics-cards/highest-volume-service.extension.tsx +1 -1
- package/src/metrics/metrics-cards/metrics-card.component.tsx +1 -1
- package/src/metrics/metrics-header.component.tsx +9 -24
- package/src/patient-appointments/patient-appointments-action-menu.component.tsx +2 -6
- package/src/patient-appointments/patient-appointments-detailed-summary.extension.tsx +176 -15
- package/src/patient-appointments/{patient-appointments-base.test.tsx → patient-appointments-detailed-summary.test.tsx} +14 -22
- package/src/patient-appointments/patient-appointments-overview.component.tsx +15 -18
- package/src/routes.json +22 -7
- package/dist/3092.js +0 -1
- package/dist/3092.js.map +0 -1
- package/dist/7026.js +0 -1
- package/dist/7026.js.map +0 -1
- package/dist/7565.js.map +0 -1
- package/src/hooks/patient-appointment-context.ts +0 -18
- package/src/patient-appointments/patient-appointments-base.component.tsx +0 -178
- package/src/patient-search/patient-search.component.tsx +0 -33
- package/src/patient-search/patient-search.scss +0 -24
- /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;
|
package/src/helpers/functions.ts
CHANGED
|
@@ -1,41 +1,65 @@
|
|
|
1
1
|
import dayjs, { type Dayjs } from 'dayjs';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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
|
-
|
|
7
|
-
|
|
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((
|
|
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 = (
|
|
15
|
-
|
|
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((
|
|
35
|
+
countMap: Object.entries(el.appointmentCountMap).flatMap(([, countMap]) => countMap),
|
|
18
36
|
}));
|
|
19
37
|
|
|
20
38
|
export const getServiceCountByAppointmentType = (
|
|
21
|
-
appointmentSummary:
|
|
22
|
-
appointmentType:
|
|
23
|
-
) => {
|
|
39
|
+
appointmentSummary: AppointmentSummary[],
|
|
40
|
+
appointmentType: 'allAppointmentsCount' | 'missedAppointmentsCount',
|
|
41
|
+
): number => {
|
|
24
42
|
return appointmentSummary
|
|
25
|
-
.map((el) =>
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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(
|
|
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(
|
|
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
|
|
22
|
+
label={highestServiceLoad ? t(highestServiceLoad.serviceName) : t('serviceName', 'Service name')}
|
|
23
23
|
value={highestServiceLoad?.count ?? '--'}
|
|
24
24
|
/>
|
|
25
25
|
);
|
|
@@ -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 {
|
|
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
|
-
<
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
} from '
|
|
7
|
-
|
|
8
|
-
|
|
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
|
|
13
|
-
*
|
|
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<
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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('
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
"
|
|
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
|
-
"
|
|
143
|
-
|
|
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
|
}
|