@kenyaemr/esm-appointments-app 8.0.1-pre.99 → 8.0.3-pre.131

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 (104) hide show
  1. package/.turbo/turbo-build.log +25 -26
  2. package/dist/130.js +1 -1
  3. package/dist/130.js.LICENSE.txt +2 -0
  4. package/dist/130.js.map +1 -1
  5. package/dist/171.js +1 -0
  6. package/dist/171.js.map +1 -0
  7. package/dist/198.js +2 -0
  8. package/dist/198.js.map +1 -0
  9. package/dist/2.js +1 -0
  10. package/dist/2.js.map +1 -0
  11. package/dist/269.js +1 -0
  12. package/dist/269.js.map +1 -0
  13. package/dist/271.js +1 -1
  14. package/dist/319.js +1 -1
  15. package/dist/325.js +1 -0
  16. package/dist/325.js.map +1 -0
  17. package/dist/372.js +2 -0
  18. package/dist/372.js.map +1 -0
  19. package/dist/440.js +2 -0
  20. package/dist/440.js.LICENSE.txt +15 -0
  21. package/dist/440.js.map +1 -0
  22. package/dist/460.js +1 -1
  23. package/dist/529.js +1 -1
  24. package/dist/529.js.map +1 -1
  25. package/dist/574.js +1 -1
  26. package/dist/581.js +1 -1
  27. package/dist/644.js +1 -1
  28. package/dist/711.js +1 -0
  29. package/dist/711.js.map +1 -0
  30. package/dist/757.js +1 -1
  31. package/dist/787.js +1 -0
  32. package/dist/787.js.map +1 -0
  33. package/dist/788.js +1 -1
  34. package/dist/807.js +1 -1
  35. package/dist/833.js +1 -1
  36. package/dist/kenyaemr-esm-appointments-app.js +1 -1
  37. package/dist/kenyaemr-esm-appointments-app.js.buildmanifest.json +203 -182
  38. package/dist/kenyaemr-esm-appointments-app.js.map +1 -1
  39. package/dist/main.js +1 -1
  40. package/dist/main.js.map +1 -1
  41. package/dist/routes.json +1 -1
  42. package/package.json +3 -3
  43. package/src/appointments/appointment-tabs.component.tsx +1 -2
  44. package/src/appointments/common-components/appointments-table.component.tsx +2 -2
  45. package/src/appointments/common-components/appointments-table.test.tsx +3 -3
  46. package/src/appointments/common-components/end-appointment.modal.tsx +24 -27
  47. package/src/appointments/common-components/end-appointment.test.tsx +10 -4
  48. package/src/appointments/unscheduled/unscheduled-appointments.component.tsx +2 -2
  49. package/src/appointments/unscheduled/unscheduled-appointments.test.tsx +3 -3
  50. package/src/appointments.component.tsx +1 -1
  51. package/src/appointments.test.tsx +5 -5
  52. package/src/calendar/appointments-calendar-view.component.tsx +1 -1
  53. package/src/calendar/header/calendar-header.component.tsx +7 -5
  54. package/src/calendar/header/calendar-header.scss +12 -0
  55. package/src/calendar/monthly/days-of-week.component.tsx +6 -4
  56. package/src/calendar/monthly/days-of-week.scss +5 -0
  57. package/src/calendar/monthly/monthly-calendar-view.component.tsx +2 -2
  58. package/src/calendar/monthly/monthly-header.component.tsx +49 -0
  59. package/src/calendar/monthly/monthly-view-workload.scss +0 -4
  60. package/src/calendar/monthly/monthly-workload-view-expanded.component.tsx +6 -1
  61. package/src/calendar/monthly/monthly-workload-view.component.tsx +3 -3
  62. package/src/config-schema.ts +6 -0
  63. package/src/constants.ts +1 -1
  64. package/src/form/appointments-form.component.tsx +59 -30
  65. package/src/form/appointments-form.scss +9 -0
  66. package/src/header/appointments-header.component.tsx +38 -55
  67. package/src/header/appointments-header.scss +7 -56
  68. package/src/helpers/excel.ts +31 -15
  69. package/src/hooks/useClinicalMetrics.ts +7 -5
  70. package/src/index.ts +15 -13
  71. package/src/metrics/metrics-header.component.tsx +3 -4
  72. package/src/routes.json +14 -18
  73. package/src/types/index.ts +0 -1
  74. package/translations/am.json +7 -2
  75. package/translations/ar.json +7 -2
  76. package/translations/en.json +8 -3
  77. package/translations/es.json +69 -64
  78. package/translations/fr.json +71 -66
  79. package/translations/he.json +7 -2
  80. package/translations/km.json +7 -2
  81. package/translations/zh.json +7 -2
  82. package/translations/zh_CN.json +7 -2
  83. package/dist/152.js +0 -1
  84. package/dist/152.js.map +0 -1
  85. package/dist/23.js +0 -2
  86. package/dist/23.js.LICENSE.txt +0 -5
  87. package/dist/23.js.map +0 -1
  88. package/dist/255.js +0 -2
  89. package/dist/255.js.map +0 -1
  90. package/dist/303.js +0 -1
  91. package/dist/303.js.map +0 -1
  92. package/dist/646.js +0 -2
  93. package/dist/646.js.map +0 -1
  94. package/dist/729.js +0 -1
  95. package/dist/729.js.map +0 -1
  96. package/dist/85.js +0 -1
  97. package/dist/85.js.map +0 -1
  98. package/dist/89.js +0 -1
  99. package/dist/89.js.map +0 -1
  100. package/src/calendar/monthly/monthly-header.module.tsx +0 -40
  101. package/src/header/appointments-illustration.component.tsx +0 -22
  102. /package/dist/{646.js.LICENSE.txt → 198.js.LICENSE.txt} +0 -0
  103. /package/dist/{255.js.LICENSE.txt → 372.js.LICENSE.txt} +0 -0
  104. /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"},"modals":[{"name":"end-appointment-modal","component":"endAppointmentModal"}],"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":"check-in-appointment-modal","slot":"todays-appointment-slot","component":"checkInModal"},{"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":"patient-appointment-cancel-confirmation-dialog","component":"patientAppointmentsCancelConfirmationDialog"},{"name":"edit-appointments-form","component":"appointementForm","meta":{"title":{"key":"editAppointment","default":"Edit Appointment"}}},{"name":"search-patient","component":"searchPatient"},{"name":"create-appointment","component":"appointementForm","meta":{"title":{"key":"appointmentForm","default":"Appointment Form"}}},{"name":"add-appointment","component":"appointementForm","meta":{"title":{"key":"createNewAppointment","default":"Create new appointment"}}},{"name":"home-appointments-tile","slot":"home-metrics-tiles-slot","component":"homeAppointmentsTile"}],"version":"8.0.1-pre.99"}
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.3-pre.131"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kenyaemr/esm-appointments-app",
3
- "version": "8.0.1-pre.99",
3
+ "version": "8.0.3-pre.131",
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": "7.x",
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",
@@ -53,5 +53,5 @@
53
53
  "devDependencies": {
54
54
  "webpack": "^5.74.0"
55
55
  },
56
- "stableVersion": "8.0.0"
56
+ "stableVersion": "8.0.2"
57
57
  }
@@ -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 { downloadAppointmentsAsExcel } from '../../helpers/excel';
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
- downloadAppointmentsAsExcel(appointments, `${tableHeading}_appointments_${date}`);
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 { downloadAppointmentsAsExcel } from '../../helpers/excel';
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 mockDownloadAppointmentsAsExcel = jest.mocked(downloadAppointmentsAsExcel);
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(mockDownloadAppointmentsAsExcel).toHaveBeenCalledWith(mockAppointments, expect.anything());
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 { useVisit, updateVisit, parseDate, showSnackbar } from '@openmrs/esm-framework';
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 endAppointment = () => {
20
- return changeAppointmentStatus('Completed', appointmentUuid)
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
- location: activeVisit.location.uuid,
27
- startDatetime: parseDate(activeVisit.startDatetime),
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
- closeModal();
38
+ mutate();
45
39
  })
46
- .catch((err) => {
47
- closeModal();
40
+ .catch((error) => {
48
41
  showSnackbar({
49
- title: t('appointmentEndedButVisitNotClosedError', 'Appointment ended, but error closing visit'),
50
- subtitle: err?.message,
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((err) => {
66
- closeModal();
60
+ .catch((error) => {
67
61
  showSnackbar({
68
62
  title: t('appointmentEndError', 'Error ending appointment'),
69
- subtitle: err?.message,
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, and close out the active visit for this patient.',
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={endAppointment}>
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 { updateVisit, showSnackbar, useVisit, type VisitReturnType } from '@openmrs/esm-framework';
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.mockImplementation(() => of({}));
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 { downloadUnscheduledAppointments } from '../../helpers/excel';
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={() => downloadUnscheduledAppointments(unscheduledAppointments)}>
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 { downloadUnscheduledAppointments } from '../../helpers/excel';
8
+ import { exportUnscheduledAppointmentsToSpreadsheet } from '../../helpers/excel';
9
9
  import UnscheduledAppointments from './unscheduled-appointments.component';
10
10
 
11
- const mockDownloadAppointmentsAsExcel = jest.mocked(downloadUnscheduledAppointments);
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(mockDownloadAppointmentsAsExcel).toHaveBeenCalledWith(mockUnscheduledAppointments);
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('home', 'Home')}
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('should render correctly', () => {
6
+ it('renders the appointments dashboard', async () => {
7
7
  render(<Appointments />);
8
8
 
9
- expect(screen.getByTitle(/patient queue illustration/i)).toBeInTheDocument();
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 dayjs from 'dayjs';
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
- const backButtonOnClick = () => {
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={backButtonOnClick}
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 DaysOfWeekProp {
6
+ interface DaysOfWeekProps {
6
7
  dayOfWeek: string;
7
8
  }
8
- const DaysOfWeekCard: React.FC<DaysOfWeekProp> = ({ dayOfWeek }) => {
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={isToday ? styles.bold : ''}>{dayOfWeek}</span>
14
+ <span className={classNames({ [styles.bold]: isToday })}>{dayOfWeek}</span>
13
15
  </div>
14
16
  );
15
17
  };
@@ -10,6 +10,11 @@
10
10
  width: 100%;
11
11
  align-items: center;
12
12
  border: 1px solid colors.$gray-20;
13
+ border-right: none;
14
+
15
+ &:last-child {
16
+ border-right: 1px solid colors.$gray-20;
17
+ }
13
18
 
14
19
  & > span {
15
20
  font-size: layout.$spacing-05;
@@ -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.module';
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;
@@ -13,10 +13,6 @@
13
13
  text-align: right;
14
14
  @include type.type-style('body-compact-02');
15
15
 
16
- &:nth-child(-n + 7) {
17
- border-top: 1px solid colors.$gray-20;
18
- }
19
-
20
16
  &:nth-child(7n) {
21
17
  border-right: 1px solid colors.$gray-20;
22
18
  }
@@ -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 className={styles.showMoreItems} onClick={() => setIsOpen((prev) => !prev)}>
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 styles from './monthly-view-workload.scss';
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>;
@@ -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 = ` ${window.getOpenmrsSpaBase()}home`;
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