@kenyaemr/esm-appointments-app 7.0.2-pre.65

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 (181) hide show
  1. package/.turbo/turbo-build.log +42 -0
  2. package/dist/130.js +2 -0
  3. package/dist/130.js.LICENSE.txt +3 -0
  4. package/dist/130.js.map +1 -0
  5. package/dist/152.js +1 -0
  6. package/dist/152.js.map +1 -0
  7. package/dist/224.js +1 -0
  8. package/dist/224.js.map +1 -0
  9. package/dist/255.js +2 -0
  10. package/dist/255.js.LICENSE.txt +9 -0
  11. package/dist/255.js.map +1 -0
  12. package/dist/271.js +1 -0
  13. package/dist/303.js +1 -0
  14. package/dist/303.js.map +1 -0
  15. package/dist/309.js +1 -0
  16. package/dist/309.js.map +1 -0
  17. package/dist/319.js +1 -0
  18. package/dist/4.js +1 -0
  19. package/dist/4.js.map +1 -0
  20. package/dist/445.js +2 -0
  21. package/dist/445.js.LICENSE.txt +54 -0
  22. package/dist/445.js.map +1 -0
  23. package/dist/460.js +1 -0
  24. package/dist/501.js +1 -0
  25. package/dist/501.js.map +1 -0
  26. package/dist/574.js +1 -0
  27. package/dist/591.js +2 -0
  28. package/dist/591.js.LICENSE.txt +32 -0
  29. package/dist/591.js.map +1 -0
  30. package/dist/644.js +1 -0
  31. package/dist/729.js +1 -0
  32. package/dist/729.js.map +1 -0
  33. package/dist/757.js +1 -0
  34. package/dist/784.js +2 -0
  35. package/dist/784.js.LICENSE.txt +9 -0
  36. package/dist/784.js.map +1 -0
  37. package/dist/788.js +1 -0
  38. package/dist/807.js +1 -0
  39. package/dist/833.js +1 -0
  40. package/dist/857.js +2 -0
  41. package/dist/857.js.LICENSE.txt +5 -0
  42. package/dist/857.js.map +1 -0
  43. package/dist/904.js +1 -0
  44. package/dist/904.js.map +1 -0
  45. package/dist/kenyaemr-esm-appointments-app.js +1 -0
  46. package/dist/kenyaemr-esm-appointments-app.js.buildmanifest.json +699 -0
  47. package/dist/kenyaemr-esm-appointments-app.js.map +1 -0
  48. package/dist/main.js +2 -0
  49. package/dist/main.js.LICENSE.txt +64 -0
  50. package/dist/main.js.map +1 -0
  51. package/dist/routes.json +1 -0
  52. package/jest.config.js +3 -0
  53. package/package.json +57 -0
  54. package/src/admin/appointment-services/appointment-services-hook.ts +31 -0
  55. package/src/admin/appointment-services/appointment-services-validation.ts +17 -0
  56. package/src/admin/appointment-services/appointment-services.component.tsx +182 -0
  57. package/src/admin/appointment-services/appointment-services.scss +25 -0
  58. package/src/appointments/appointment-tabs.component.tsx +48 -0
  59. package/src/appointments/appointment-tabs.scss +53 -0
  60. package/src/appointments/appointment-tabs.test.tsx +55 -0
  61. package/src/appointments/common-components/appointments-actions.component.tsx +86 -0
  62. package/src/appointments/common-components/appointments-actions.scss +4 -0
  63. package/src/appointments/common-components/appointments-actions.test.tsx +201 -0
  64. package/src/appointments/common-components/appointments-table.component.tsx +277 -0
  65. package/src/appointments/common-components/appointments-table.scss +133 -0
  66. package/src/appointments/common-components/appointments-table.test.tsx +134 -0
  67. package/src/appointments/common-components/checkin-button.component.tsx +43 -0
  68. package/src/appointments/common-components/end-appointment-modal.component.tsx +104 -0
  69. package/src/appointments/common-components/end-appointment-modal.test.tsx +80 -0
  70. package/src/appointments/common-components/location-select-option.component.tsx +48 -0
  71. package/src/appointments/details/appointment-details.component.tsx +91 -0
  72. package/src/appointments/details/appointment-details.scss +81 -0
  73. package/src/appointments/details/appointment-details.test.tsx +103 -0
  74. package/src/appointments/scheduled/appointments-list.component.tsx +33 -0
  75. package/src/appointments/scheduled/early-appointments.component.tsx +32 -0
  76. package/src/appointments/scheduled/scheduled-appointments.component.tsx +215 -0
  77. package/src/appointments/scheduled/scheduled-appointments.scss +4 -0
  78. package/src/appointments/unscheduled/unscheduled-appointments.component.tsx +146 -0
  79. package/src/appointments/unscheduled/unscheduled-appointments.test.tsx +131 -0
  80. package/src/appointments/utils.tsx +80 -0
  81. package/src/appointments.component.tsx +44 -0
  82. package/src/appointments.test.tsx +15 -0
  83. package/src/calendar/appointments-calendar-view-view.scss +24 -0
  84. package/src/calendar/appointments-calendar-view.component.tsx +36 -0
  85. package/src/calendar/appointments-calendar-view.test.tsx +22 -0
  86. package/src/calendar/header/calendar-header.component.tsx +34 -0
  87. package/src/calendar/header/calendar-header.scss +32 -0
  88. package/src/calendar/monthly/days-of-week.component.tsx +16 -0
  89. package/src/calendar/monthly/days-of-week.scss +33 -0
  90. package/src/calendar/monthly/monthly-calendar-view.component.tsx +34 -0
  91. package/src/calendar/monthly/monthly-header.module.scss +14 -0
  92. package/src/calendar/monthly/monthly-header.module.tsx +40 -0
  93. package/src/calendar/monthly/monthly-view-workload.scss +188 -0
  94. package/src/calendar/monthly/monthly-workload-view-expanded.component.tsx +42 -0
  95. package/src/calendar/monthly/monthly-workload-view.component.tsx +109 -0
  96. package/src/config-schema.ts +151 -0
  97. package/src/constants.ts +55 -0
  98. package/src/createDashboardLink.component.tsx +39 -0
  99. package/src/dashboard.meta.ts +21 -0
  100. package/src/declarations.d.ts +4 -0
  101. package/src/empty-state/empty-data-illustration.component.tsx +39 -0
  102. package/src/empty-state/empty-state.component.tsx +32 -0
  103. package/src/empty-state/empty-state.scss +69 -0
  104. package/src/form/appointments-form.component.tsx +891 -0
  105. package/src/form/appointments-form.resource.ts +165 -0
  106. package/src/form/appointments-form.scss +113 -0
  107. package/src/form/appointments-form.test.tsx +212 -0
  108. package/src/header/appointments-header.component.tsx +79 -0
  109. package/src/header/appointments-header.scss +95 -0
  110. package/src/header/appointments-illustration.component.tsx +22 -0
  111. package/src/helpers/excel.ts +61 -0
  112. package/src/helpers/functions.ts +82 -0
  113. package/src/helpers/index.ts +2 -0
  114. package/src/helpers/time.tsx +15 -0
  115. package/src/home/home-appointments.component.tsx +22 -0
  116. package/src/home/home-appointments.scss +10 -0
  117. package/src/hooks/patientAppointmentContext.ts +15 -0
  118. package/src/hooks/selectedDateContext.ts +10 -0
  119. package/src/hooks/useAppointmentList.ts +48 -0
  120. package/src/hooks/useAppointmentService.ts +11 -0
  121. package/src/hooks/useAppointmentsCalendar.ts +68 -0
  122. package/src/hooks/useClinicalMetrics.ts +79 -0
  123. package/src/hooks/useDefaultLocation.ts +14 -0
  124. package/src/hooks/useOverlay.tsx +45 -0
  125. package/src/hooks/usePatientAppointmentHistory.ts +49 -0
  126. package/src/hooks/useProviders.ts +18 -0
  127. package/src/hooks/useTodaysVisits.ts +19 -0
  128. package/src/hooks/useUnscheduledAppointments.ts +45 -0
  129. package/src/index.ts +111 -0
  130. package/src/metrics/appointments-metrics.component.tsx +71 -0
  131. package/src/metrics/appointments-metrics.scss +15 -0
  132. package/src/metrics/appointments-metrics.test.tsx +49 -0
  133. package/src/metrics/metrics-card.component.tsx +76 -0
  134. package/src/metrics/metrics-card.scss +77 -0
  135. package/src/metrics/metrics-header.component.tsx +62 -0
  136. package/src/metrics/metrics-header.scss +33 -0
  137. package/src/past-visit/encounter-list.component.tsx +54 -0
  138. package/src/past-visit/past-visit.component.tsx +106 -0
  139. package/src/past-visit/past-visit.resource.ts +25 -0
  140. package/src/past-visit/past-visit.scss +106 -0
  141. package/src/patient-appointments/patient-appointments-action-menu.component.tsx +65 -0
  142. package/src/patient-appointments/patient-appointments-action-menu.scss +7 -0
  143. package/src/patient-appointments/patient-appointments-base.component.tsx +165 -0
  144. package/src/patient-appointments/patient-appointments-base.scss +85 -0
  145. package/src/patient-appointments/patient-appointments-base.test.tsx +91 -0
  146. package/src/patient-appointments/patient-appointments-cancel-modal.component.tsx +66 -0
  147. package/src/patient-appointments/patient-appointments-detailed-summary.component.tsx +15 -0
  148. package/src/patient-appointments/patient-appointments-header.scss +27 -0
  149. package/src/patient-appointments/patient-appointments-header.tsx +42 -0
  150. package/src/patient-appointments/patient-appointments-overview.component.tsx +35 -0
  151. package/src/patient-appointments/patient-appointments-overview.scss +7 -0
  152. package/src/patient-appointments/patient-appointments-table.scss +0 -0
  153. package/src/patient-appointments/patient-appointments-table.tsx +128 -0
  154. package/src/patient-appointments/patient-appointments.resource.ts +72 -0
  155. package/src/patient-appointments/patient-upcoming-appointments-card.component.tsx +122 -0
  156. package/src/patient-appointments/patient-upcoming-appointments-card.scss +46 -0
  157. package/src/patient-search/patient-search.component.tsx +34 -0
  158. package/src/patient-search/patient-search.scss +23 -0
  159. package/src/root.component.tsx +26 -0
  160. package/src/root.scss +50 -0
  161. package/src/routes.json +153 -0
  162. package/src/scheduled-appointments-config-schema.ts +169 -0
  163. package/src/types/index.ts +189 -0
  164. package/src/workload/monthly-view-workload/monthly-view.component.tsx +69 -0
  165. package/src/workload/monthly-view-workload/monthly-workload.scss +223 -0
  166. package/src/workload/monthly-view-workload/monthlyWorkCard.tsx +45 -0
  167. package/src/workload/workload-card.component.tsx +31 -0
  168. package/src/workload/workload.component.tsx +47 -0
  169. package/src/workload/workload.resource.ts +78 -0
  170. package/src/workload/workload.scss +92 -0
  171. package/translations/am.json +148 -0
  172. package/translations/ar.json +148 -0
  173. package/translations/en.json +159 -0
  174. package/translations/es.json +148 -0
  175. package/translations/fr.json +148 -0
  176. package/translations/he.json +148 -0
  177. package/translations/km.json +148 -0
  178. package/translations/zh.json +148 -0
  179. package/translations/zh_CN.json +148 -0
  180. package/tsconfig.json +5 -0
  181. package/webpack.config.js +1 -0
@@ -0,0 +1,61 @@
1
+ import { formatDate } from '@openmrs/esm-framework';
2
+ import * as XLSX from 'xlsx';
3
+ import { type Appointment } from '../types';
4
+
5
+ /**
6
+ * Downloads the provided appointments as an Excel file.
7
+ * @param {Array<Appointment>} appointments - The list of appointments to download.
8
+ * @param {string} [fileName] - The name of the downloaded file
9
+ */
10
+ export function downloadAppointmentsAsExcel(appointments: Array<Appointment>, fileName = 'Appointments') {
11
+ const appointmentsJSON = appointments?.map((appointment: Appointment) => ({
12
+ 'Patient name': appointment.patient.name,
13
+ Gender: appointment.patient.gender === 'F' ? 'Female' : 'Male',
14
+ Age: appointment.patient.age,
15
+ Identifier: appointment.patient.identifier ?? '--',
16
+ 'Appointment type': appointment.service?.name,
17
+ Date: formatDate(new Date(appointment.startDateTime), { mode: 'wide' }),
18
+ }));
19
+
20
+ const worksheet = createWorksheet(appointmentsJSON);
21
+ const workbook = createWorkbook(worksheet, 'Appointment list');
22
+ XLSX.writeFile(workbook, `${fileName}.xlsx`, { compression: true });
23
+ }
24
+
25
+ /**
26
+ Downloads unscheduled appointments as an Excel file.
27
+ @param {Array<Object>} unscheduledAppointments - The list of unscheduled appointments to download.
28
+ @param {string} fileName - The name of the file to download. Defaults to 'Unscheduled appointments {current date and time}'.
29
+ */
30
+ export function downloadUnscheduledAppointments(
31
+ unscheduledAppointments: Array<any>,
32
+ fileName = `Unscheduled appointments ${formatDate(new Date(), { year: true, time: true })}`,
33
+ ) {
34
+ const appointmentsJSON = unscheduledAppointments?.map((appointment) => ({
35
+ 'Patient name': appointment.name,
36
+ Gender: appointment.gender === 'F' ? 'Female' : 'Male',
37
+ Age: appointment.age,
38
+ 'Phone Number': appointment.phoneNumber ?? '--',
39
+ Identifier: appointment.identifier ?? '--',
40
+ }));
41
+
42
+ const worksheet = createWorksheet(appointmentsJSON);
43
+ const workbook = createWorkbook(worksheet, 'Appointment list');
44
+
45
+ XLSX.writeFile(workbook, `${fileName}.xlsx`, {
46
+ compression: true,
47
+ });
48
+ }
49
+
50
+ function createWorksheet(data: any[]) {
51
+ const max_width = data.reduce((w, r) => Math.max(w, r['Patient name'].length), 30);
52
+ const worksheet = XLSX.utils.json_to_sheet(data);
53
+ worksheet['!cols'] = [{ wch: max_width }];
54
+ return worksheet;
55
+ }
56
+
57
+ function createWorkbook(worksheet: XLSX.WorkSheet, sheetName: string) {
58
+ const workbook = XLSX.utils.book_new();
59
+ XLSX.utils.book_append_sheet(workbook, worksheet, sheetName);
60
+ return workbook;
61
+ }
@@ -0,0 +1,82 @@
1
+ import dayjs, { type Dayjs } from 'dayjs';
2
+ import { formatDate, parseDate } from '@openmrs/esm-framework';
3
+ import { type AppointmentSummary, type Appointment } from '../types';
4
+ import { configSchema } from '../config-schema';
5
+
6
+ export const getHighestAppointmentServiceLoad = (appointmentSummary: Array<any> = []) => {
7
+ const groupedAppointments = appointmentSummary?.map(({ countMap, serviceName }) => ({
8
+ serviceName: serviceName,
9
+ count: countMap.reduce((cummulator, currentValue) => cummulator + currentValue.allAppointmentsCount, 0),
10
+ }));
11
+ return groupedAppointments.find((summary) => summary.count === Math.max(...groupedAppointments.map((x) => x.count)));
12
+ };
13
+
14
+ export const flattenAppointmentSummary = (appointmentToTransfrom: Array<any>) =>
15
+ appointmentToTransfrom.flatMap((el: any) => ({
16
+ serviceName: el.appointmentService.name,
17
+ countMap: Object.entries(el.appointmentCountMap).flatMap((el) => el[1]),
18
+ }));
19
+
20
+ export const getServiceCountByAppointmentType = (
21
+ appointmentSummary: Array<AppointmentSummary>,
22
+ appointmentType: string,
23
+ ) => {
24
+ return appointmentSummary
25
+ .map((el) => Object.entries(el.appointmentCountMap).flatMap((el) => el[1][appointmentType]))
26
+ .flat(1)
27
+ .reduce((count, val) => count + val, 0);
28
+ };
29
+
30
+ export const formatAMPM = (date) => {
31
+ let hours = date.getHours();
32
+ let minutes = date.getMinutes();
33
+ let ampm = hours >= 12 ? 'PM' : 'AM';
34
+ hours = hours % 12;
35
+ hours = hours ? hours : 12; // the hour '0' should be '12'
36
+ minutes = minutes < 10 ? '0' + minutes : minutes;
37
+ const strTime = hours + ':' + minutes + ' ' + ampm;
38
+ return strTime;
39
+ };
40
+
41
+ export const isSameMonth = (cellDate: Dayjs, currentDate: Dayjs) => {
42
+ return cellDate.isSame(currentDate, 'month');
43
+ };
44
+
45
+ export const monthDays = (currentDate: Dayjs) => {
46
+ const monthStart = dayjs(currentDate).startOf('month');
47
+ const monthEnd = dayjs(currentDate).endOf('month');
48
+ const monthDays = dayjs(currentDate).daysInMonth();
49
+ const lastMonth = dayjs(currentDate).subtract(1, 'month');
50
+ const nextMonth = dayjs(currentDate).add(1, 'month');
51
+ let days: Dayjs[] = [];
52
+
53
+ for (let i = lastMonth.daysInMonth() - monthStart.day() + 1; i <= lastMonth.daysInMonth(); i++) {
54
+ days.push(dayjs().month(lastMonth.month()).date(i));
55
+ }
56
+
57
+ for (let i = 1; i <= monthDays; i++) {
58
+ days.push(currentDate.date(i));
59
+ }
60
+
61
+ const dayLen = days.length > 30 ? 7 : 14;
62
+
63
+ for (let i = 1; i < dayLen - monthEnd.day(); i++) {
64
+ days.push(dayjs().month(nextMonth.month()).date(i));
65
+ }
66
+ return days;
67
+ };
68
+
69
+ export const getGender = (gender, t) => {
70
+ switch (gender) {
71
+ case 'M':
72
+ return t('male', 'Male');
73
+ case 'F':
74
+ return t('female', 'Female');
75
+ case 'O':
76
+ return t('other', 'Other');
77
+ case 'U':
78
+ return t('unknown', 'Unknown');
79
+ default:
80
+ return gender;
81
+ }
82
+ };
@@ -0,0 +1,2 @@
1
+ export * from './functions';
2
+ export * from './time';
@@ -0,0 +1,15 @@
1
+ export type amPm = 'AM' | 'PM';
2
+
3
+ export const convertTime12to24 = (time12h, timeFormat: amPm) => {
4
+ let [hours, minutes] = time12h?.split(':');
5
+
6
+ if (hours === '12' && timeFormat === 'AM') {
7
+ hours = '00';
8
+ }
9
+
10
+ if (timeFormat === 'PM') {
11
+ hours = hours === '12' ? hours : parseInt(hours, 10) + 12;
12
+ }
13
+
14
+ return [hours, minutes];
15
+ };
@@ -0,0 +1,22 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import AppointmentsList from '../appointments/scheduled/appointments-list.component';
4
+ import dayjs from 'dayjs';
5
+ import styles from './home-appointments.scss';
6
+ import { toOmrsIsoString } from '@openmrs/esm-framework';
7
+
8
+ const HomeAppointments = () => {
9
+ const { t } = useTranslation();
10
+
11
+ return (
12
+ <div className={styles.container}>
13
+ <AppointmentsList
14
+ date={toOmrsIsoString(dayjs().startOf('day').toDate())}
15
+ title={t('todays', "Today's")}
16
+ filterCancelled={true}
17
+ />
18
+ </div>
19
+ );
20
+ };
21
+
22
+ export default HomeAppointments;
@@ -0,0 +1,10 @@
1
+ @use '@carbon/colors';
2
+ @use '@carbon/layout';
3
+
4
+ .container {
5
+ width: 100%;
6
+ margin: 0 auto;
7
+ max-width: 95vw;
8
+ margin-top: layout.$spacing-09;
9
+ margin-bottom: layout.$spacing-12;
10
+ }
@@ -0,0 +1,15 @@
1
+ import { createContext } from 'react';
2
+
3
+ /**
4
+ * When rendering the Patient Appointments view, let the view know if this is being rendered in the Patient Chart or the Appointments App
5
+ * (we need to know this to determine if we open the appointments form as part of the patient chart workspace or as an overlay in the appointments app)
6
+ */
7
+ export enum PatientAppointmentContextTypes {
8
+ PATIENT_CHART,
9
+ APPOINTMENTS_APP,
10
+ }
11
+ const PatientAppointmentContext = createContext<PatientAppointmentContextTypes>(
12
+ PatientAppointmentContextTypes.PATIENT_CHART,
13
+ );
14
+
15
+ export default PatientAppointmentContext;
@@ -0,0 +1,10 @@
1
+ import { createContext } from 'react';
2
+ import dayjs from 'dayjs';
3
+ import { omrsDateFormat } from '../constants';
4
+
5
+ const SelectedDateContext = createContext({
6
+ selectedDate: dayjs().startOf('day').format(omrsDateFormat),
7
+ setSelectedDate: (date: string) => {},
8
+ });
9
+
10
+ export default SelectedDateContext;
@@ -0,0 +1,48 @@
1
+ import useSWR from 'swr';
2
+ import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
3
+ import { type AppointmentsFetchResponse } from '../types';
4
+ import dayjs from 'dayjs';
5
+ import { useContext } from 'react';
6
+ import SelectedDateContext from './selectedDateContext';
7
+
8
+ export const useAppointmentList = (appointmentStatus: string, date?: string) => {
9
+ const { selectedDate } = useContext(SelectedDateContext);
10
+ const startDate = date ? date : selectedDate;
11
+ const endDate = dayjs(startDate).endOf('day').format('YYYY-MM-DDTHH:mm:ss.SSSZZ'); // TODO: fix? is this correct?
12
+ const searchUrl = `${restBaseUrl}/appointments/search`;
13
+ const abortController = new AbortController();
14
+
15
+ const fetcher = ([url, startDate, endDate, status]) =>
16
+ openmrsFetch(url, {
17
+ method: 'POST',
18
+ signal: abortController.signal,
19
+ headers: {
20
+ 'Content-Type': 'application/json',
21
+ },
22
+ body: {
23
+ startDate: startDate,
24
+ endDate: endDate,
25
+ status: status,
26
+ },
27
+ });
28
+
29
+ const { data, error, isLoading, mutate } = useSWR<AppointmentsFetchResponse, Error>(
30
+ [searchUrl, startDate, endDate, appointmentStatus],
31
+ fetcher,
32
+ { errorRetryCount: 2 },
33
+ );
34
+
35
+ return { appointmentList: data?.data ?? [], isLoading, error, mutate };
36
+ };
37
+
38
+ export const useEarlyAppointmentList = (startDate?: string) => {
39
+ const { selectedDate } = useContext(SelectedDateContext);
40
+ const forDate = startDate ? startDate : selectedDate;
41
+ const url = `${restBaseUrl}/appointment/earlyAppointment?forDate=${forDate}`;
42
+
43
+ const { data, error, isLoading } = useSWR<AppointmentsFetchResponse, Error>(url, openmrsFetch, {
44
+ errorRetryCount: 2,
45
+ });
46
+
47
+ return { earlyAppointmentList: data?.data ?? [], isLoading, error };
48
+ };
@@ -0,0 +1,11 @@
1
+ import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
2
+ import useSWR from 'swr';
3
+ import { type AppointmentService } from '../types';
4
+
5
+ export function useAppointmentServices() {
6
+ const { data, error, isLoading } = useSWR<{ data: Array<AppointmentService> }>(
7
+ `${restBaseUrl}/appointmentService/all/default`,
8
+ openmrsFetch,
9
+ );
10
+ return { serviceTypes: data?.data ?? [], isLoading, error };
11
+ }
@@ -0,0 +1,68 @@
1
+ import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
2
+ import dayjs from 'dayjs';
3
+ import useSWR from 'swr';
4
+ import { omrsDateFormat } from '../constants';
5
+ import { type DailyAppointmentsCountByService } from '../types';
6
+
7
+ interface AppointmentCountMapEntry {
8
+ allAppointmentsCount: number;
9
+ }
10
+
11
+ interface AppointmentSummaryResponse {
12
+ appointmentService: {
13
+ name: string;
14
+ uuid: string;
15
+ };
16
+ appointmentCountMap: Map<string, AppointmentCountMapEntry>;
17
+ }
18
+
19
+ export const useAppointmentsCalendar = (forDate: string, period: string) => {
20
+ const { startDate, endDate } = evaluateAppointmentCalendarDates(forDate, period);
21
+ const url = `${restBaseUrl}/appointment/appointmentSummary?startDate=${startDate}&endDate=${endDate}`;
22
+
23
+ const { data, error, isLoading } = useSWR<{ data: Array<AppointmentSummaryResponse> }>(
24
+ startDate && endDate ? url : null,
25
+ openmrsFetch,
26
+ { errorRetryCount: 2 },
27
+ );
28
+ const results: Array<DailyAppointmentsCountByService> = data?.data.reduce((acc, service) => {
29
+ const serviceName = service.appointmentService.name;
30
+ const serviceUuid = service.appointmentService.uuid;
31
+ Object.entries(service.appointmentCountMap).map(([key, value]) => {
32
+ const existingEntry = acc.find((entry) => entry.appointmentDate === key);
33
+ if (existingEntry) {
34
+ existingEntry.services.push({ serviceName, serviceUuid, count: value.allAppointmentsCount });
35
+ } else {
36
+ acc.push({
37
+ appointmentDate: key,
38
+ services: [{ serviceName, serviceUuid, count: value.allAppointmentsCount }],
39
+ });
40
+ }
41
+ });
42
+ return acc;
43
+ }, []);
44
+ return { isLoading, calendarEvents: results, error };
45
+ };
46
+
47
+ function evaluateAppointmentCalendarDates(forDate: string, period: string) {
48
+ if (period === 'daily') {
49
+ return {
50
+ startDate: dayjs(forDate).startOf('day').format(omrsDateFormat),
51
+ endDate: dayjs(forDate).endOf('day').format(omrsDateFormat),
52
+ };
53
+ }
54
+
55
+ if (period === 'weekly') {
56
+ return {
57
+ startDate: dayjs(forDate).startOf('week').format(omrsDateFormat),
58
+ endDate: dayjs(forDate).endOf('week').format(omrsDateFormat),
59
+ };
60
+ }
61
+
62
+ if (period === 'monthly') {
63
+ return {
64
+ startDate: dayjs(forDate).startOf('month').format(omrsDateFormat),
65
+ endDate: dayjs(forDate).endOf('month').format(omrsDateFormat),
66
+ };
67
+ }
68
+ }
@@ -0,0 +1,79 @@
1
+ import useSWR from 'swr';
2
+ import dayjs from 'dayjs';
3
+ import uniqBy from 'lodash-es/uniqBy';
4
+ import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
5
+ import { type Appointment, type AppointmentSummary } from '../types';
6
+ import { omrsDateFormat } from '../constants';
7
+ import {
8
+ getHighestAppointmentServiceLoad,
9
+ flattenAppointmentSummary,
10
+ getServiceCountByAppointmentType,
11
+ } from '../helpers';
12
+ import isEmpty from 'lodash-es/isEmpty';
13
+ import SelectedDateContext from './selectedDateContext';
14
+ import { useContext } from 'react';
15
+
16
+ export const useClinicalMetrics = () => {
17
+ const { selectedDate } = useContext(SelectedDateContext);
18
+ const endDate = dayjs(new Date(selectedDate).setHours(23, 59, 59, 59)).format(omrsDateFormat);
19
+ const url = `${restBaseUrl}/appointment/appointmentSummary?startDate=${selectedDate}&endDate=${endDate}`;
20
+ const { data, error, isLoading } = useSWR<{
21
+ data: Array<AppointmentSummary>;
22
+ }>(url, openmrsFetch);
23
+
24
+ const totalAppointments = getServiceCountByAppointmentType(data?.data ?? [], 'allAppointmentsCount');
25
+
26
+ const missedAppointments = getServiceCountByAppointmentType(data?.data ?? [], 'missedAppointmentsCount');
27
+
28
+ const transformedAppointments = flattenAppointmentSummary(data?.data ?? []);
29
+ const highestServiceLoad = getHighestAppointmentServiceLoad(transformedAppointments);
30
+
31
+ return {
32
+ isLoading,
33
+ error,
34
+ totalAppointments,
35
+ missedAppointments,
36
+ highestServiceLoad,
37
+ };
38
+ };
39
+
40
+ export function useAllAppointmentsByDate() {
41
+ const { selectedDate } = useContext(SelectedDateContext);
42
+ const apiUrl = `${restBaseUrl}/appointment/all?forDate=${selectedDate}`;
43
+ const { data, error, isLoading, isValidating, mutate } = useSWR<{ data: Array<Appointment> }, Error>(
44
+ apiUrl,
45
+ openmrsFetch,
46
+ );
47
+
48
+ const providersArray = data?.data?.filter(({ providers }) => providers !== null) ?? [];
49
+ const providersCount = uniqBy(
50
+ providersArray.map(({ providers }) => providers).flat(),
51
+ (provider) => provider.uuid,
52
+ ).length;
53
+ return {
54
+ totalProviders: providersCount ? providersCount : 0,
55
+ isLoading,
56
+ isError: error,
57
+ isValidating,
58
+ mutate,
59
+ };
60
+ }
61
+
62
+ export const useScheduledAppointment = (serviceUuid: string) => {
63
+ const { selectedDate } = useContext(SelectedDateContext);
64
+ const url = `${restBaseUrl}/appointment/all?forDate=${selectedDate}`;
65
+
66
+ const { data, error, isLoading } = useSWR<{
67
+ data: Array<any>;
68
+ }>(url, openmrsFetch);
69
+
70
+ const totalScheduledAppointments = !isEmpty(serviceUuid)
71
+ ? data?.data?.filter((appt) => appt?.service?.uuid === serviceUuid)?.length ?? 0
72
+ : data?.data?.length ?? 0;
73
+
74
+ return {
75
+ isLoading,
76
+ error,
77
+ totalScheduledAppointments,
78
+ };
79
+ };
@@ -0,0 +1,14 @@
1
+ import { type FetchResponse, openmrsFetch, useConfig } from '@openmrs/esm-framework';
2
+ import useSWRImmutable from 'swr/immutable';
3
+
4
+ export const useDefaultLoginLocation = () => {
5
+ const config = useConfig();
6
+ const apiUrl = config.defaultFacilityUrl;
7
+ const { data, error, isLoading } = useSWRImmutable<FetchResponse>(apiUrl, openmrsFetch);
8
+
9
+ return {
10
+ defaultFacility: data ? data?.data : null,
11
+ isLoading: isLoading,
12
+ isError: error,
13
+ };
14
+ };
@@ -0,0 +1,45 @@
1
+ import { getGlobalStore } from '@openmrs/esm-framework';
2
+ import { useCallback, useEffect, useState } from 'react';
3
+
4
+ export interface OverlayStore {
5
+ isOverlayOpen: boolean;
6
+ component?: any;
7
+ header: string;
8
+ }
9
+
10
+ const initialState: OverlayStore = { isOverlayOpen: false, header: '' };
11
+
12
+ const getOverlayStore = () => {
13
+ return getGlobalStore('appointment-store', initialState);
14
+ };
15
+
16
+ export const launchOverlay = (headerTitle: string, componentToRender: any) => {
17
+ const store = getOverlayStore();
18
+ store.setState({ isOverlayOpen: true, component: componentToRender, header: headerTitle });
19
+ };
20
+
21
+ export const closeOverlay = (): void => {
22
+ const store = getOverlayStore();
23
+ store.setState({ component: null, isOverlayOpen: false });
24
+ };
25
+
26
+ export const useOverlay = () => {
27
+ const [overlay, setOverlay] = useState<OverlayStore>();
28
+
29
+ const update = useCallback((state: OverlayStore) => {
30
+ setOverlay(state);
31
+ }, []);
32
+
33
+ useEffect(() => {
34
+ update(getOverlayStore().getState());
35
+ getOverlayStore().subscribe(update);
36
+ }, [update]);
37
+
38
+ const { isOverlayOpen, component, header } = overlay ?? {};
39
+
40
+ return {
41
+ isOverlayOpen,
42
+ component,
43
+ header,
44
+ };
45
+ };
@@ -0,0 +1,49 @@
1
+ import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
2
+ import { type AppointmentsFetchResponse } from '../types';
3
+ import useSWR from 'swr';
4
+ import dayjs from 'dayjs';
5
+ import SelectedDateContext from './selectedDateContext';
6
+ import { useContext } from 'react';
7
+
8
+ export function usePatientAppointmentHistory(patientUuid: string) {
9
+ const abortController = new AbortController();
10
+ const appointmentsSearchUrl = `${restBaseUrl}/appointments/search`;
11
+ const { selectedDate } = useContext(SelectedDateContext);
12
+ const fetcher = () =>
13
+ openmrsFetch(appointmentsSearchUrl, {
14
+ method: 'POST',
15
+ signal: abortController.signal,
16
+ headers: {
17
+ 'Content-Type': 'application/json',
18
+ },
19
+ body: {
20
+ patientUuid: patientUuid,
21
+ startDate: selectedDate,
22
+ },
23
+ });
24
+
25
+ const { data, error, isLoading, isValidating } = useSWR<AppointmentsFetchResponse, Error>(
26
+ patientUuid ? appointmentsSearchUrl : null,
27
+ fetcher,
28
+ );
29
+
30
+ const missedAppointments = data?.data?.length
31
+ ? data.data.filter((appointment) => appointment.status === 'Missed').length
32
+ : 0;
33
+ const completedAppointments = data?.data?.length
34
+ ? data.data.filter((appointment) => appointment.status === 'Completed').length
35
+ : 0;
36
+ const cancelledAppointments = data?.data?.length
37
+ ? data.data.filter((appointment) => appointment.status === 'Cancelled').length
38
+ : 0;
39
+ const upcomingAppointments = data?.data?.length
40
+ ? data.data?.filter((appointment: any) => dayjs((appointment.startDateTime / 1000) * 1000).isAfter(dayjs())).length
41
+ : 0;
42
+
43
+ return {
44
+ appointmentsCount: { missedAppointments, completedAppointments, cancelledAppointments, upcomingAppointments },
45
+ isError: error,
46
+ isLoading,
47
+ isValidating,
48
+ };
49
+ }
@@ -0,0 +1,18 @@
1
+ import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
2
+ import useSWR from 'swr';
3
+ import { type Provider } from '../types';
4
+
5
+ export function useProviders() {
6
+ const apiUrl = `${restBaseUrl}/provider`;
7
+ const { data, error, isLoading, isValidating } = useSWR<{ data: { results: Array<Provider> } }, Error>(
8
+ apiUrl,
9
+ openmrsFetch,
10
+ );
11
+
12
+ return {
13
+ providers: data ? data.data?.results : [],
14
+ isLoading,
15
+ isError: error,
16
+ isValidating,
17
+ };
18
+ }
@@ -0,0 +1,19 @@
1
+ import useSWR from 'swr';
2
+ import { useSession, type Visit, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
3
+ import dayjs from 'dayjs';
4
+
5
+ /**
6
+ * Custom hook to fetch visits from the OpenMRS REST API.
7
+ * This fetches all visits that started on today
8
+ * @returns An object containing the visits, isLoading flag, and error message.
9
+ */
10
+ export const useTodaysVisits = () => {
11
+ const session = useSession();
12
+ const visitsUrl = `${restBaseUrl}/visit?includeInactive=true&includeParentLocations=true&v=custom:(uuid,patient:(uuid,identifiers:(identifier,uuid),person:(age,display,gender,uuid)),visitType:(uuid,name,display),location:(uuid,name,display),startDatetime,stopDatetime)&fromStartDate=${dayjs().format(
13
+ 'YYYY-MM-DD',
14
+ )}&location=${session?.sessionLocation?.uuid}`;
15
+ const { data, error, isLoading, mutate } = useSWR<{ data: { results: Visit[] } }>(visitsUrl, openmrsFetch);
16
+ const visits = data?.data?.results ?? [];
17
+
18
+ return { isLoading, visits, error, mutateVisit: mutate };
19
+ };
@@ -0,0 +1,45 @@
1
+ import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
2
+ import useSWR from 'swr';
3
+ import { type Identifier } from '../types';
4
+ import { configSchema } from '../config-schema';
5
+ import SelectedDateContext from './selectedDateContext';
6
+ import { useContext } from 'react';
7
+
8
+ export interface Response {
9
+ age: number;
10
+ dob: number;
11
+ gender: string;
12
+ identifiers: Array<Identifier>;
13
+ name: string;
14
+ uuid: string;
15
+ phoneNumber: string;
16
+ visit: {
17
+ stopDateTime: Date;
18
+ startDateTime: Date;
19
+ visitType: string;
20
+ };
21
+ }
22
+
23
+ export function useUnscheduledAppointments() {
24
+ const { selectedDate } = useContext(SelectedDateContext);
25
+ // TODO/NOTE: this endpoint is not implemented in main Bahmni Appointments backend
26
+ const url = `${restBaseUrl}/appointment/unScheduledAppointment?forDate=${selectedDate}`;
27
+ const { data, error, isLoading } = useSWR<{ data: Array<Response> }>(url, openmrsFetch, { errorRetryCount: 2 });
28
+ const appointments = data?.data?.map((appointment) => toAppointmentObject(appointment));
29
+
30
+ return { isLoading, data: appointments ?? [], error };
31
+ }
32
+
33
+ function toAppointmentObject(appointment: Response) {
34
+ return {
35
+ name: appointment.name,
36
+ identifier: appointment?.identifiers?.find(
37
+ (identifier) => identifier.identifierName === configSchema.patientIdentifierType._default,
38
+ ).identifier,
39
+ dateTime: appointment?.visit.startDateTime,
40
+ gender: appointment.gender,
41
+ phoneNumber: appointment.phoneNumber,
42
+ age: appointment.age,
43
+ uuid: appointment.uuid,
44
+ };
45
+ }