@kenyaemr/esm-appointments-app 8.0.1-pre.95 → 8.0.2
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 +25 -26
- package/dist/130.js +1 -1
- package/dist/130.js.LICENSE.txt +2 -0
- package/dist/130.js.map +1 -1
- package/dist/171.js +1 -0
- package/dist/171.js.map +1 -0
- package/dist/198.js +2 -0
- package/dist/198.js.map +1 -0
- package/dist/2.js +1 -0
- package/dist/2.js.map +1 -0
- package/dist/269.js +1 -0
- package/dist/269.js.map +1 -0
- package/dist/271.js +1 -1
- package/dist/319.js +1 -1
- package/dist/325.js +1 -0
- package/dist/325.js.map +1 -0
- package/dist/372.js +2 -0
- package/dist/372.js.map +1 -0
- package/dist/440.js +2 -0
- package/dist/440.js.LICENSE.txt +15 -0
- package/dist/440.js.map +1 -0
- package/dist/460.js +1 -1
- package/dist/529.js +1 -1
- package/dist/529.js.map +1 -1
- package/dist/574.js +1 -1
- package/dist/581.js +1 -1
- package/dist/644.js +1 -1
- package/dist/711.js +1 -0
- package/dist/711.js.map +1 -0
- package/dist/757.js +1 -1
- package/dist/787.js +1 -0
- package/dist/787.js.map +1 -0
- package/dist/788.js +1 -1
- package/dist/807.js +1 -1
- package/dist/833.js +1 -1
- package/dist/kenyaemr-esm-appointments-app.js +1 -1
- package/dist/kenyaemr-esm-appointments-app.js.buildmanifest.json +203 -182
- package/dist/kenyaemr-esm-appointments-app.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +3 -4
- package/src/appointments/appointment-tabs.component.tsx +1 -2
- package/src/appointments/common-components/appointments-table.component.tsx +2 -2
- package/src/appointments/common-components/appointments-table.test.tsx +3 -3
- package/src/appointments/common-components/end-appointment.modal.tsx +24 -27
- package/src/appointments/common-components/end-appointment.test.tsx +10 -4
- package/src/appointments/unscheduled/unscheduled-appointments.component.tsx +2 -2
- package/src/appointments/unscheduled/unscheduled-appointments.test.tsx +3 -3
- package/src/appointments.component.tsx +1 -1
- package/src/appointments.test.tsx +5 -5
- package/src/calendar/appointments-calendar-view.component.tsx +1 -1
- package/src/calendar/header/calendar-header.component.tsx +7 -5
- package/src/calendar/header/calendar-header.scss +12 -0
- package/src/calendar/monthly/days-of-week.component.tsx +6 -4
- package/src/calendar/monthly/days-of-week.scss +5 -0
- package/src/calendar/monthly/monthly-calendar-view.component.tsx +2 -2
- package/src/calendar/monthly/monthly-header.component.tsx +49 -0
- package/src/calendar/monthly/monthly-view-workload.scss +0 -4
- package/src/calendar/monthly/monthly-workload-view-expanded.component.tsx +6 -1
- package/src/calendar/monthly/monthly-workload-view.component.tsx +3 -3
- package/src/config-schema.ts +6 -0
- package/src/constants.ts +1 -1
- package/src/form/appointments-form.component.tsx +59 -30
- package/src/form/appointments-form.scss +9 -0
- package/src/header/appointments-header.component.tsx +38 -55
- package/src/header/appointments-header.scss +7 -56
- package/src/helpers/excel.ts +31 -15
- package/src/hooks/useClinicalMetrics.ts +7 -5
- package/src/index.ts +15 -13
- package/src/metrics/metrics-header.component.tsx +3 -4
- package/src/routes.json +14 -18
- package/src/types/index.ts +0 -1
- package/translations/am.json +7 -2
- package/translations/ar.json +7 -2
- package/translations/en.json +8 -3
- package/translations/es.json +69 -64
- package/translations/fr.json +71 -66
- package/translations/he.json +7 -2
- package/translations/km.json +7 -2
- package/translations/zh.json +7 -2
- package/translations/zh_CN.json +7 -2
- package/dist/152.js +0 -1
- package/dist/152.js.map +0 -1
- package/dist/23.js +0 -2
- package/dist/23.js.LICENSE.txt +0 -5
- package/dist/23.js.map +0 -1
- package/dist/255.js +0 -2
- package/dist/255.js.map +0 -1
- package/dist/303.js +0 -1
- package/dist/303.js.map +0 -1
- package/dist/646.js +0 -2
- package/dist/646.js.map +0 -1
- package/dist/729.js +0 -1
- package/dist/729.js.map +0 -1
- package/dist/85.js +0 -1
- package/dist/85.js.map +0 -1
- package/dist/89.js +0 -1
- package/dist/89.js.map +0 -1
- package/src/calendar/monthly/monthly-header.module.tsx +0 -40
- package/src/header/appointments-illustration.component.tsx +0 -22
- /package/dist/{646.js.LICENSE.txt → 198.js.LICENSE.txt} +0 -0
- /package/dist/{255.js.LICENSE.txt → 372.js.LICENSE.txt} +0 -0
- /package/src/calendar/monthly/{monthly-header.module.scss → monthly-header.scss} +0 -0
package/dist/routes.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"webservices.rest":"^2.2.0"},"
|
|
1
|
+
{"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"webservices.rest":"^2.2.0"},"extensions":[{"name":"home-appointments","slot":"homepage-widgets-slot","component":"homeAppointments","order":1},{"name":"clinical-appointments-dashboard-link","slot":"homepage-dashboard-slot","component":"appointmentsDashboardLink","meta":{"name":"appointments","slot":"clinical-appointments-dashboard-slot","title":"Appointments"}},{"component":"root","name":"clinical-appointments-dashboard","slot":"clinical-appointments-dashboard-slot"},{"name":"appointments-calendar-dashboard-link","slot":"calendar-dashboard-slot","component":"appointmentsCalendarDashboardLink"},{"name":"todays-appointments-dashboard","slot":"todays-appointment-slot","component":"homeAppointments"},{"name":"expected-appointments-panel","slot":"scheduled-appointments-panels-slot","component":"appointmentsList"},{"name":"checked-in-appointments-panel","slot":"scheduled-appointments-panels-slot","component":"appointmentsList"},{"name":"completed-appointments-panel","slot":"scheduled-appointments-panels-slot","component":"appointmentsList"},{"name":"missed-appointments-panel","slot":"scheduled-appointments-panels-slot","component":"appointmentsList"},{"name":"cancelled-appointments-panel","slot":"scheduled-appointments-panels-slot","component":"appointmentsList"},{"name":"early-appointments-panel","component":"earlyAppointments"},{"name":"appointments-form-workspace","component":"appointmentsFormWorkspace","meta":{"title":{"key":"createNewAppointment","default":"Create new appointment"}}},{"name":"patient-appointments-summary-dashboard","component":"patientAppointmentsSummaryDashboardLink","slot":"patient-chart-dashboard-slot","order":11,"meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-appointments-dashboard-slot","title":"Appointments","path":"Appointments"}},{"name":"patientAppointments-details-widget","component":"patientAppointmentsDetailedSummary","slot":"patient-chart-appointments-dashboard-slot","meta":{"columnSpan":1}},{"name":"patient-upcoming-appointment-widget","component":"patientUpcomingAppointmentsWidget","slot":"upcoming-appointment-slot"},{"name":"edit-appointments-form","component":"appointmentsForm","meta":{"title":{"key":"editAppointment","default":"Edit Appointment"}}},{"name":"search-patient","component":"searchPatient"},{"name":"create-appointment","component":"appointmentsForm","meta":{"title":{"key":"appointmentForm","default":"Appointment Form"}}},{"name":"add-appointment","component":"appointmentsForm","meta":{"title":{"key":"createNewAppointment","default":"Create new appointment"}}},{"name":"home-appointments-tile","slot":"home-metrics-tiles-slot","component":"homeAppointmentsTile"}],"modals":[{"name":"end-appointment-modal","component":"endAppointmentModal"},{"name":"patient-appointment-cancel-confirmation-dialog","component":"patientAppointmentsCancelConfirmationDialog"}],"version":"8.0.2"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kenyaemr/esm-appointments-app",
|
|
3
|
-
"version": "8.0.
|
|
3
|
+
"version": "8.0.2",
|
|
4
4
|
"description": "Appointments front-end module for the OpenMRS SPA",
|
|
5
5
|
"browser": "dist/kenyaemr-esm-appointments-app.js",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"@openmrs/esm-framework": "5.x",
|
|
47
|
-
"@openmrs/esm-patient-common-lib": "
|
|
47
|
+
"@openmrs/esm-patient-common-lib": "8.x",
|
|
48
48
|
"react": "18.x",
|
|
49
49
|
"react-i18next": "11.x",
|
|
50
50
|
"react-router-dom": "6.x",
|
|
@@ -52,6 +52,5 @@
|
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"webpack": "^5.74.0"
|
|
55
|
-
}
|
|
56
|
-
"stableVersion": "8.0.0"
|
|
55
|
+
}
|
|
57
56
|
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
3
|
import { Tab, TabList, Tabs, TabPanel, TabPanels } from '@carbon/react';
|
|
4
|
-
|
|
5
|
-
import { type ConfigObject } from '../config-schema';
|
|
6
4
|
import { useConfig } from '@openmrs/esm-framework';
|
|
5
|
+
import { type ConfigObject } from '../config-schema';
|
|
7
6
|
import ScheduledAppointments from './scheduled/scheduled-appointments.component';
|
|
8
7
|
import UnscheduledAppointments from './unscheduled/unscheduled-appointments.component';
|
|
9
8
|
import styles from './appointment-tabs.scss';
|
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
} from '@openmrs/esm-framework';
|
|
38
38
|
import { Download } from '@carbon/react/icons';
|
|
39
39
|
import { EmptyState } from '../../empty-state/empty-state.component';
|
|
40
|
-
import {
|
|
40
|
+
import { exportAppointmentsToSpreadsheet } from '../../helpers/excel';
|
|
41
41
|
import { useTodaysVisits } from '../../hooks/useTodaysVisits';
|
|
42
42
|
import { type Appointment } from '../../types';
|
|
43
43
|
import { type ConfigObject } from '../../config-schema';
|
|
@@ -154,7 +154,7 @@ const AppointmentsTable: React.FC<AppointmentsTableProps> = ({ appointments, isL
|
|
|
154
154
|
noToday: true,
|
|
155
155
|
})
|
|
156
156
|
: null;
|
|
157
|
-
|
|
157
|
+
exportAppointmentsToSpreadsheet(appointments, `${tableHeading}_appointments_${date}`);
|
|
158
158
|
}}>
|
|
159
159
|
{t('download', 'Download')}
|
|
160
160
|
</Button>
|
|
@@ -4,7 +4,7 @@ import { render, screen } from '@testing-library/react';
|
|
|
4
4
|
import { getDefaultsFromConfigSchema, useConfig, defineConfigSchema } from '@openmrs/esm-framework';
|
|
5
5
|
import { type ConfigObject, configSchema } from '../../config-schema';
|
|
6
6
|
import type { Appointment, AppointmentKind, AppointmentStatus } from '../../types';
|
|
7
|
-
import {
|
|
7
|
+
import { exportAppointmentsToSpreadsheet } from '../../helpers/excel';
|
|
8
8
|
import { getByTextWithMarkup } from 'tools';
|
|
9
9
|
import AppointmentsTable from './appointments-table.component';
|
|
10
10
|
|
|
@@ -62,7 +62,7 @@ const mockAppointments = [
|
|
|
62
62
|
},
|
|
63
63
|
] as unknown as Array<Appointment>;
|
|
64
64
|
|
|
65
|
-
const
|
|
65
|
+
const mockExportAppointmentsToSpreadsheet = jest.mocked(exportAppointmentsToSpreadsheet);
|
|
66
66
|
const mockUseConfig = jest.mocked(useConfig<ConfigObject>);
|
|
67
67
|
|
|
68
68
|
jest.mock('../../helpers/excel');
|
|
@@ -123,7 +123,7 @@ describe('AppointmentsTable', () => {
|
|
|
123
123
|
const downloadButton = screen.getByRole('button', { name: /download/i });
|
|
124
124
|
await user.click(downloadButton);
|
|
125
125
|
expect(downloadButton).toBeInTheDocument();
|
|
126
|
-
expect(
|
|
126
|
+
expect(mockExportAppointmentsToSpreadsheet).toHaveBeenCalledWith(mockAppointments, expect.anything());
|
|
127
127
|
});
|
|
128
128
|
});
|
|
129
129
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useCallback } from 'react';
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
|
-
import {
|
|
3
|
+
import { showSnackbar, updateVisit, useVisit } from '@openmrs/esm-framework';
|
|
4
4
|
import { Button, ModalBody, ModalFooter, ModalHeader } from '@carbon/react';
|
|
5
5
|
import { changeAppointmentStatus } from '../../patient-appointments/patient-appointments.resource';
|
|
6
6
|
import { useMutateAppointments } from '../../form/appointments-form.resource';
|
|
@@ -12,48 +12,43 @@ interface EndAppointmentModalProps {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
const EndAppointmentModal: React.FC<EndAppointmentModalProps> = ({ patientUuid, appointmentUuid, closeModal }) => {
|
|
15
|
-
const { activeVisit, mutate } = useVisit(patientUuid);
|
|
16
15
|
const { t } = useTranslation();
|
|
16
|
+
const { activeVisit, mutate } = useVisit(patientUuid);
|
|
17
17
|
const { mutateAppointments } = useMutateAppointments();
|
|
18
18
|
|
|
19
|
-
const
|
|
20
|
-
|
|
19
|
+
const handleEndAppointment = useCallback(() => {
|
|
20
|
+
changeAppointmentStatus('Completed', appointmentUuid)
|
|
21
21
|
.then(() => {
|
|
22
22
|
mutateAppointments();
|
|
23
23
|
if (activeVisit) {
|
|
24
24
|
const abortController = new AbortController();
|
|
25
|
-
const endVisitPayload = {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
visitType: activeVisit.visitType.uuid,
|
|
29
|
-
stopDatetime: new Date(),
|
|
30
|
-
};
|
|
31
|
-
updateVisit(activeVisit.uuid, endVisitPayload, abortController)
|
|
32
|
-
.toPromise()
|
|
25
|
+
const endVisitPayload = { stopDatetime: new Date() };
|
|
26
|
+
|
|
27
|
+
return updateVisit(activeVisit.uuid, endVisitPayload, abortController)
|
|
33
28
|
.then(() => {
|
|
34
|
-
mutate();
|
|
35
29
|
showSnackbar({
|
|
36
30
|
title: t('appointmentEnded', 'Appointment ended'),
|
|
37
31
|
subtitle: t(
|
|
38
32
|
'appointmentEndedAndVisitClosedSuccessfully',
|
|
39
|
-
'Appointment successfully ended and visit successfully closed
|
|
33
|
+
'Appointment successfully ended and visit successfully closed',
|
|
40
34
|
),
|
|
41
35
|
isLowContrast: true,
|
|
42
36
|
kind: 'success',
|
|
43
37
|
});
|
|
44
|
-
|
|
38
|
+
mutate();
|
|
45
39
|
})
|
|
46
|
-
.catch((
|
|
47
|
-
closeModal();
|
|
40
|
+
.catch((error) => {
|
|
48
41
|
showSnackbar({
|
|
49
|
-
title: t(
|
|
50
|
-
|
|
42
|
+
title: t(
|
|
43
|
+
'appointmentEndedButVisitNotClosedError',
|
|
44
|
+
'Appointment ended successfully, but there was an error closing the visit.',
|
|
45
|
+
),
|
|
46
|
+
subtitle: error?.message,
|
|
51
47
|
kind: 'error',
|
|
52
48
|
isLowContrast: true,
|
|
53
49
|
});
|
|
54
50
|
});
|
|
55
51
|
} else {
|
|
56
|
-
closeModal();
|
|
57
52
|
showSnackbar({
|
|
58
53
|
title: t('appointmentEnded', 'Appointment ended'),
|
|
59
54
|
subtitle: t('appointmentEndedSuccessfully', 'Appointment successfully ended.'),
|
|
@@ -62,16 +57,18 @@ const EndAppointmentModal: React.FC<EndAppointmentModalProps> = ({ patientUuid,
|
|
|
62
57
|
});
|
|
63
58
|
}
|
|
64
59
|
})
|
|
65
|
-
.catch((
|
|
66
|
-
closeModal();
|
|
60
|
+
.catch((error) => {
|
|
67
61
|
showSnackbar({
|
|
68
62
|
title: t('appointmentEndError', 'Error ending appointment'),
|
|
69
|
-
subtitle:
|
|
63
|
+
subtitle: error?.message,
|
|
70
64
|
kind: 'error',
|
|
71
65
|
isLowContrast: true,
|
|
72
66
|
});
|
|
67
|
+
})
|
|
68
|
+
.finally(() => {
|
|
69
|
+
closeModal();
|
|
73
70
|
});
|
|
74
|
-
};
|
|
71
|
+
}, [activeVisit, mutate, mutateAppointments, closeModal, patientUuid, appointmentUuid]);
|
|
75
72
|
|
|
76
73
|
return (
|
|
77
74
|
<div>
|
|
@@ -84,7 +81,7 @@ const EndAppointmentModal: React.FC<EndAppointmentModalProps> = ({ patientUuid,
|
|
|
84
81
|
{activeVisit
|
|
85
82
|
? t(
|
|
86
83
|
'endAppointmentAndVisitConfirmationMessage',
|
|
87
|
-
'Checking the patient out will mark the appointment as complete
|
|
84
|
+
'Checking the patient out will mark the appointment as complete and close out the active visit for this patient.',
|
|
88
85
|
)
|
|
89
86
|
: t('endAppointmentConfirmationMessage', 'Checking the patient out will mark the appointment as complete.')}
|
|
90
87
|
</p>
|
|
@@ -93,7 +90,7 @@ const EndAppointmentModal: React.FC<EndAppointmentModalProps> = ({ patientUuid,
|
|
|
93
90
|
<Button kind="secondary" onClick={closeModal}>
|
|
94
91
|
{t('cancel', 'Cancel')}
|
|
95
92
|
</Button>
|
|
96
|
-
<Button kind="danger" onClick={
|
|
93
|
+
<Button kind="danger" onClick={handleEndAppointment}>
|
|
97
94
|
{t('checkOut', 'Check out')}
|
|
98
95
|
</Button>
|
|
99
96
|
</ModalFooter>
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { of } from 'rxjs';
|
|
3
2
|
import { render, screen } from '@testing-library/react';
|
|
4
3
|
import userEvent from '@testing-library/user-event';
|
|
5
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
updateVisit,
|
|
6
|
+
showSnackbar,
|
|
7
|
+
useVisit,
|
|
8
|
+
type VisitReturnType,
|
|
9
|
+
type FetchResponse,
|
|
10
|
+
type Visit,
|
|
11
|
+
} from '@openmrs/esm-framework';
|
|
6
12
|
import { changeAppointmentStatus } from '../../patient-appointments/patient-appointments.resource';
|
|
7
13
|
import EndAppointmentModal from './end-appointment.modal';
|
|
8
14
|
|
|
@@ -20,7 +26,7 @@ jest.mock('../../form/appointments-form.resource', () => ({
|
|
|
20
26
|
|
|
21
27
|
describe('EndAppointmentModal', () => {
|
|
22
28
|
beforeEach(() => {
|
|
23
|
-
mockUpdateVisit.
|
|
29
|
+
mockUpdateVisit.mockResolvedValue({} as FetchResponse<Visit>);
|
|
24
30
|
});
|
|
25
31
|
|
|
26
32
|
it('has a cancel button that closes the modal', async () => {
|
|
@@ -73,7 +79,7 @@ describe('EndAppointmentModal', () => {
|
|
|
73
79
|
expect(closeModal).toHaveBeenCalled();
|
|
74
80
|
expect(showSnackbar).toHaveBeenCalledWith({
|
|
75
81
|
title: 'Appointment ended',
|
|
76
|
-
subtitle: 'Appointment successfully ended and visit successfully closed
|
|
82
|
+
subtitle: 'Appointment successfully ended and visit successfully closed',
|
|
77
83
|
isLowContrast: true,
|
|
78
84
|
kind: 'success',
|
|
79
85
|
});
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
import { Download } from '@carbon/react/icons';
|
|
20
20
|
import { ConfigurableLink, useConfig, usePagination } from '@openmrs/esm-framework';
|
|
21
21
|
import { useUnscheduledAppointments } from '../../hooks/useUnscheduledAppointments';
|
|
22
|
-
import {
|
|
22
|
+
import { exportUnscheduledAppointmentsToSpreadsheet } from '../../helpers/excel';
|
|
23
23
|
import { EmptyState } from '../../empty-state/empty-state.component';
|
|
24
24
|
import { getPageSizes, useSearchResults } from '../utils';
|
|
25
25
|
import { type ConfigObject } from '../../config-schema';
|
|
@@ -99,7 +99,7 @@ const UnscheduledAppointments: React.FC = () => {
|
|
|
99
99
|
size="lg"
|
|
100
100
|
kind="tertiary"
|
|
101
101
|
renderIcon={Download}
|
|
102
|
-
onClick={() =>
|
|
102
|
+
onClick={() => exportUnscheduledAppointmentsToSpreadsheet(unscheduledAppointments)}>
|
|
103
103
|
{t('download', 'Download')}
|
|
104
104
|
</Button>
|
|
105
105
|
</TableToolbarContent>
|
|
@@ -5,10 +5,10 @@ import { getDefaultsFromConfigSchema, useConfig } from '@openmrs/esm-framework';
|
|
|
5
5
|
import { type ConfigObject, configSchema } from '../../config-schema';
|
|
6
6
|
import { getByTextWithMarkup } from 'tools';
|
|
7
7
|
import { useUnscheduledAppointments } from '../../hooks/useUnscheduledAppointments';
|
|
8
|
-
import {
|
|
8
|
+
import { exportUnscheduledAppointmentsToSpreadsheet } from '../../helpers/excel';
|
|
9
9
|
import UnscheduledAppointments from './unscheduled-appointments.component';
|
|
10
10
|
|
|
11
|
-
const
|
|
11
|
+
const mockExportUnscheduledAppointmentsToSpreadsheet = jest.mocked(exportUnscheduledAppointmentsToSpreadsheet);
|
|
12
12
|
const mockUseUnscheduledAppointments = jest.mocked(useUnscheduledAppointments);
|
|
13
13
|
const mockUseConfig = jest.mocked(useConfig<ConfigObject>);
|
|
14
14
|
|
|
@@ -113,7 +113,7 @@ describe('UnscheduledAppointments', () => {
|
|
|
113
113
|
const downloadButton = await screen.findByText('Download');
|
|
114
114
|
expect(downloadButton).toBeInTheDocument();
|
|
115
115
|
await user.click(downloadButton);
|
|
116
|
-
expect(
|
|
116
|
+
expect(mockExportUnscheduledAppointmentsToSpreadsheet).toHaveBeenCalledWith(mockUnscheduledAppointments);
|
|
117
117
|
});
|
|
118
118
|
|
|
119
119
|
it('renders a message if there are no unscheduled appointments', async () => {
|
|
@@ -30,7 +30,7 @@ const Appointments: React.FC = () => {
|
|
|
30
30
|
return (
|
|
31
31
|
<SelectedDateContext.Provider value={{ selectedDate, setSelectedDate }}>
|
|
32
32
|
<AppointmentsHeader
|
|
33
|
-
title={t('
|
|
33
|
+
title={t('appointments', 'Appointments')}
|
|
34
34
|
appointmentServiceType={appointmentServiceType}
|
|
35
35
|
onChange={setAppointmentServiceType}
|
|
36
36
|
/>
|
|
@@ -3,13 +3,13 @@ import { render, screen } from '@testing-library/react';
|
|
|
3
3
|
import Appointments from './appointments.component';
|
|
4
4
|
|
|
5
5
|
describe('Appointments', () => {
|
|
6
|
-
it('
|
|
6
|
+
it('renders the appointments dashboard', async () => {
|
|
7
7
|
render(<Appointments />);
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
expect(screen.getByText(/^appointments$/i)).toBeInTheDocument();
|
|
11
|
-
expect(screen.getByText(/home/i)).toBeInTheDocument();
|
|
12
|
-
expect(screen.getByPlaceholderText(/DD-MMM-YYYY/i)).toBeInTheDocument();
|
|
9
|
+
await screen.findByText(/^appointments$/i);
|
|
13
10
|
expect(screen.getByRole('button', { name: /appointments calendar/i })).toBeInTheDocument();
|
|
11
|
+
expect(screen.getByPlaceholderText(/dd-mmm-yyyy/i)).toBeInTheDocument();
|
|
12
|
+
expect(screen.getByRole('combobox', { name: /view/i })).toBeInTheDocument();
|
|
13
|
+
expect(screen.getByText(/appointment metrics/i)).toBeInTheDocument();
|
|
14
14
|
});
|
|
15
15
|
});
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import React, { useEffect, useState } from 'react';
|
|
2
2
|
import dayjs from 'dayjs';
|
|
3
|
+
import { useParams } from 'react-router-dom';
|
|
3
4
|
import { useTranslation } from 'react-i18next';
|
|
4
5
|
import { useAppointmentsCalendar } from '../hooks/useAppointmentsCalendar';
|
|
5
6
|
import AppointmentsHeader from '../header/appointments-header.component';
|
|
6
7
|
import CalendarHeader from './header/calendar-header.component';
|
|
7
8
|
import MonthlyCalendarView from './monthly/monthly-calendar-view.component';
|
|
8
9
|
import SelectedDateContext from '../hooks/selectedDateContext';
|
|
9
|
-
import { useParams } from 'react-router-dom';
|
|
10
10
|
import { omrsDateFormat } from '../constants';
|
|
11
11
|
|
|
12
12
|
const AppointmentsCalendarView: React.FC = () => {
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import React, { useContext } from 'react';
|
|
2
|
+
import dayjs from 'dayjs';
|
|
2
3
|
import { useTranslation } from 'react-i18next';
|
|
3
4
|
import { Button } from '@carbon/react';
|
|
4
5
|
import { ArrowLeft } from '@carbon/react/icons';
|
|
5
6
|
import { navigate } from '@openmrs/esm-framework';
|
|
6
7
|
import { spaHomePage } from '../../constants';
|
|
7
|
-
import styles from './calendar-header.scss';
|
|
8
8
|
import SelectedDateContext from '../../hooks/selectedDateContext';
|
|
9
|
-
import
|
|
9
|
+
import styles from './calendar-header.scss';
|
|
10
10
|
|
|
11
11
|
const CalendarHeader: React.FC = () => {
|
|
12
12
|
const { t } = useTranslation();
|
|
13
13
|
const { selectedDate } = useContext(SelectedDateContext);
|
|
14
|
-
|
|
14
|
+
|
|
15
|
+
const handleClick = () => {
|
|
15
16
|
navigate({ to: `${spaHomePage}/appointments/${dayjs(selectedDate).format('YYYY-MM-DD')}` });
|
|
16
17
|
};
|
|
17
18
|
|
|
@@ -19,10 +20,11 @@ const CalendarHeader: React.FC = () => {
|
|
|
19
20
|
<div className={styles.calendarHeaderContainer}>
|
|
20
21
|
<div className={styles.titleContainer}>
|
|
21
22
|
<Button
|
|
23
|
+
className={styles.backButton}
|
|
24
|
+
iconDescription={t('back', 'Back')}
|
|
22
25
|
kind="ghost"
|
|
23
|
-
onClick={
|
|
26
|
+
onClick={handleClick}
|
|
24
27
|
renderIcon={ArrowLeft}
|
|
25
|
-
iconDescription={t('back', 'Back')}
|
|
26
28
|
size="lg">
|
|
27
29
|
<span>{t('back', 'Back')}</span>
|
|
28
30
|
</Button>
|
|
@@ -24,6 +24,18 @@
|
|
|
24
24
|
align-items: center;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
.backButton {
|
|
28
|
+
svg {
|
|
29
|
+
order: 1;
|
|
30
|
+
margin-right: layout.$spacing-03;
|
|
31
|
+
margin-left: 0 !important;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
span {
|
|
35
|
+
order: 2;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
27
39
|
// Overriding styles for RTL support
|
|
28
40
|
html[dir='rtl'] {
|
|
29
41
|
.titleContent {
|
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
import dayjs from 'dayjs';
|
|
2
1
|
import React from 'react';
|
|
2
|
+
import classNames from 'classnames';
|
|
3
|
+
import dayjs from 'dayjs';
|
|
3
4
|
import styles from './days-of-week.scss';
|
|
4
5
|
|
|
5
|
-
interface
|
|
6
|
+
interface DaysOfWeekProps {
|
|
6
7
|
dayOfWeek: string;
|
|
7
8
|
}
|
|
8
|
-
|
|
9
|
+
|
|
10
|
+
const DaysOfWeekCard: React.FC<DaysOfWeekProps> = ({ dayOfWeek }) => {
|
|
9
11
|
const isToday = dayjs(new Date()).format('ddd').toUpperCase() === dayOfWeek;
|
|
10
12
|
return (
|
|
11
13
|
<div tabIndex={0} role="button" className={styles.tileContainer}>
|
|
12
|
-
<span className={
|
|
14
|
+
<span className={classNames({ [styles.bold]: isToday })}>{dayOfWeek}</span>
|
|
13
15
|
</div>
|
|
14
16
|
);
|
|
15
17
|
};
|
|
@@ -4,9 +4,9 @@ import isBetween from 'dayjs/plugin/isBetween';
|
|
|
4
4
|
import { type DailyAppointmentsCountByService } from '../../types';
|
|
5
5
|
import { monthDays } from '../../helpers';
|
|
6
6
|
import MonthlyViewWorkload from './monthly-workload-view.component';
|
|
7
|
-
import MonthlyHeader from './monthly-header.
|
|
8
|
-
import styles from '../appointments-calendar-view-view.scss';
|
|
7
|
+
import MonthlyHeader from './monthly-header.component';
|
|
9
8
|
import SelectedDateContext from '../../hooks/selectedDateContext';
|
|
9
|
+
import styles from '../appointments-calendar-view-view.scss';
|
|
10
10
|
|
|
11
11
|
dayjs.extend(isBetween);
|
|
12
12
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React, { useCallback, useContext } from 'react';
|
|
2
|
+
import dayjs from 'dayjs';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
import { Button } from '@carbon/react';
|
|
5
|
+
import { formatDate } from '@openmrs/esm-framework';
|
|
6
|
+
import { omrsDateFormat } from '../../constants';
|
|
7
|
+
import DaysOfWeekCard from './days-of-week.component';
|
|
8
|
+
import SelectedDateContext from '../../hooks/selectedDateContext';
|
|
9
|
+
import styles from './monthly-header.scss';
|
|
10
|
+
|
|
11
|
+
const DAYS_IN_WEEK = ['SUN', 'MON', 'TUE', 'WED', 'THUR', 'FRI', 'SAT'];
|
|
12
|
+
|
|
13
|
+
const MonthlyHeader: React.FC = () => {
|
|
14
|
+
const { t } = useTranslation();
|
|
15
|
+
const { selectedDate, setSelectedDate } = useContext(SelectedDateContext);
|
|
16
|
+
|
|
17
|
+
const handleSelectPrevMonth = useCallback(() => {
|
|
18
|
+
setSelectedDate(dayjs(selectedDate).subtract(1, 'month').format(omrsDateFormat));
|
|
19
|
+
}, [selectedDate, setSelectedDate]);
|
|
20
|
+
|
|
21
|
+
const handleSelectNextMonth = useCallback(() => {
|
|
22
|
+
setSelectedDate(dayjs(selectedDate).add(1, 'month').format(omrsDateFormat));
|
|
23
|
+
}, [selectedDate, setSelectedDate]);
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<>
|
|
27
|
+
<div className={styles.container}>
|
|
28
|
+
<Button
|
|
29
|
+
aria-label={t('previousMonth', 'Previous month')}
|
|
30
|
+
kind="tertiary"
|
|
31
|
+
onClick={handleSelectPrevMonth}
|
|
32
|
+
size="sm">
|
|
33
|
+
{t('prev', 'Prev')}
|
|
34
|
+
</Button>
|
|
35
|
+
<span>{formatDate(new Date(selectedDate), { day: false, time: false, noToday: true })}</span>
|
|
36
|
+
<Button aria-label={t('nextMonth', 'Next month')} kind="tertiary" onClick={handleSelectNextMonth} size="sm">
|
|
37
|
+
{t('next', 'Next')}
|
|
38
|
+
</Button>
|
|
39
|
+
</div>
|
|
40
|
+
<div className={styles.workLoadCard}>
|
|
41
|
+
{DAYS_IN_WEEK.map((day) => (
|
|
42
|
+
<DaysOfWeekCard key={day} dayOfWeek={day} />
|
|
43
|
+
))}
|
|
44
|
+
</div>
|
|
45
|
+
</>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export default MonthlyHeader;
|
|
@@ -29,7 +29,12 @@ const MonthlyWorkloadViewExpanded: React.FC<MonthlyWorkloadViewExpandedProps> =
|
|
|
29
29
|
|
|
30
30
|
return (
|
|
31
31
|
<Popover open={isOpen} align="top" ref={popoverRef}>
|
|
32
|
-
<button
|
|
32
|
+
<button
|
|
33
|
+
className={styles.showMoreItems}
|
|
34
|
+
onClick={(e) => {
|
|
35
|
+
e.stopPropagation();
|
|
36
|
+
setIsOpen((prev) => !prev);
|
|
37
|
+
}}>
|
|
33
38
|
{t('countMore', '{{count}} more', { count })}
|
|
34
39
|
</button>
|
|
35
40
|
<PopoverContent>
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import React, { useContext, useMemo } from 'react';
|
|
2
2
|
import classNames from 'classnames';
|
|
3
3
|
import dayjs, { type Dayjs } from 'dayjs';
|
|
4
|
+
import { User } from '@carbon/react/icons';
|
|
4
5
|
import { navigate, useLayoutType } from '@openmrs/esm-framework';
|
|
5
|
-
import { isSameMonth } from '../../helpers';
|
|
6
6
|
import { spaHomePage } from '../../constants';
|
|
7
|
-
import
|
|
7
|
+
import { isSameMonth } from '../../helpers';
|
|
8
8
|
import { type DailyAppointmentsCountByService } from '../../types';
|
|
9
9
|
import SelectedDateContext from '../../hooks/selectedDateContext';
|
|
10
|
-
import { User } from '@carbon/react/icons';
|
|
11
10
|
import MonthlyWorkloadViewExpanded from './monthly-workload-view-expanded.component';
|
|
11
|
+
import styles from './monthly-view-workload.scss';
|
|
12
12
|
|
|
13
13
|
export interface MonthlyWorkloadViewProps {
|
|
14
14
|
events: Array<DailyAppointmentsCountByService>;
|
package/src/config-schema.ts
CHANGED
|
@@ -2,6 +2,11 @@ import { Type, restBaseUrl, validators } from '@openmrs/esm-framework';
|
|
|
2
2
|
import { spaHomePage } from './constants';
|
|
3
3
|
|
|
4
4
|
export const configSchema = {
|
|
5
|
+
includePhoneNumberInExcelSpreadsheet: {
|
|
6
|
+
_type: Type.Boolean,
|
|
7
|
+
_description: 'Whether to include phone numbers in the exported Excel spreadsheet',
|
|
8
|
+
_default: false,
|
|
9
|
+
},
|
|
5
10
|
allowAllDayAppointments: {
|
|
6
11
|
_type: Type.Boolean,
|
|
7
12
|
_description: 'Whether to allow scheduling of all-day appointments (vs appointments with start time and end time)',
|
|
@@ -146,4 +151,5 @@ export interface ConfigObject {
|
|
|
146
151
|
showUnscheduledAppointmentsTab: boolean;
|
|
147
152
|
useBahmniAppointmentsUI: boolean;
|
|
148
153
|
useFullViewPrivilege: boolean;
|
|
154
|
+
includePhoneNumberInExcelSpreadsheet: boolean;
|
|
149
155
|
}
|
package/src/constants.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export const spaRoot = window['getOpenmrsSpaBase'];
|
|
2
2
|
export const basePath = '/appointments';
|
|
3
|
-
export const spaHomePage =
|
|
3
|
+
export const spaHomePage = `${window.spaBase}/home`;
|
|
4
4
|
export const omrsDateFormat = 'YYYY-MM-DDTHH:mm:ss.SSSZZ';
|
|
5
5
|
export const appointmentLocationTagName = 'Appointment Location';
|
|
6
6
|
|