@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,35 @@
1
+ import React from 'react';
2
+ import { usePatient, useLayoutType, isDesktop, WorkspaceOverlay } from '@openmrs/esm-framework';
3
+ import PatientAppointmentsBase from './patient-appointments-base.component';
4
+ import { useParams } from 'react-router-dom';
5
+ import PatientAppointmentContext, { PatientAppointmentContextTypes } from '../hooks/patientAppointmentContext';
6
+ import PatientAppointmentsHeader from './patient-appointments-header';
7
+ import { DataTableSkeleton } from '@carbon/react';
8
+ import styles from './patient-appointments-overview.scss';
9
+
10
+ /**
11
+ * This component renders the patient appointments view (all appointments for a single patient) outside of the context of the patient chart.
12
+ * Currently, it is not linked directly within the Appointments app, but can be accessed via the home/appointments/patient/:patientUuid route,
13
+ * providing a means for other apps (or legacy O2 UIs) to link to the patient appointments overview.
14
+ * It uses the PatientAppointmentsBase component to render the actual appointments data.
15
+ * @constructor
16
+ */
17
+ const PatientAppointmentsOverview: React.FC = () => {
18
+ let params = useParams();
19
+ const response = usePatient(params.patientUuid);
20
+ const layout = useLayoutType();
21
+
22
+ return response.isLoading ? (
23
+ <DataTableSkeleton role="progressbar" compact={isDesktop(layout)} zebra />
24
+ ) : (
25
+ <PatientAppointmentContext.Provider value={PatientAppointmentContextTypes.APPOINTMENTS_APP}>
26
+ <div className={styles.patientAppointmentsOverview}>
27
+ <PatientAppointmentsHeader patient={response.patient} />
28
+ <PatientAppointmentsBase patientUuid={response.patient.id} />
29
+ <WorkspaceOverlay contextKey={`patient/${params.patientUuid}`} />
30
+ </div>
31
+ </PatientAppointmentContext.Provider>
32
+ );
33
+ };
34
+
35
+ export default PatientAppointmentsOverview;
@@ -0,0 +1,7 @@
1
+ @use '@carbon/styles/scss/type';
2
+ @use '@carbon/colors';
3
+ @use '@carbon/layout';
4
+
5
+ .patientAppointmentsOverview {
6
+ background-color: colors.$gray-10;
7
+ }
@@ -0,0 +1,128 @@
1
+ import React, { useEffect, useMemo } from 'react';
2
+ import classNames from 'classnames';
3
+ import dayjs from 'dayjs';
4
+ const utc = require('dayjs/plugin/utc');
5
+ dayjs.extend(utc);
6
+ import { useTranslation } from 'react-i18next';
7
+ import {
8
+ DataTable,
9
+ type DataTableHeader,
10
+ Table,
11
+ TableCell,
12
+ TableContainer,
13
+ TableBody,
14
+ TableHead,
15
+ TableHeader,
16
+ TableRow,
17
+ } from '@carbon/react';
18
+ import { PatientChartPagination } from '@openmrs/esm-patient-common-lib';
19
+ import { formatDatetime, parseDate, useLayoutType, usePagination } from '@openmrs/esm-framework';
20
+ import { type Appointment } from '../types';
21
+ import { PatientAppointmentsActionMenu } from './patient-appointments-action-menu.component';
22
+ import styles from './patient-appointments-action-menu.scss';
23
+
24
+ const pageSize = 10;
25
+
26
+ interface AppointmentTableProps {
27
+ patientAppointments: Array<Appointment>;
28
+ switchedView: boolean;
29
+ setSwitchedView: (value: boolean) => void;
30
+ patientUuid: string;
31
+ }
32
+
33
+ const PatientAppointmentsTable: React.FC<AppointmentTableProps> = ({
34
+ patientAppointments,
35
+ patientUuid,
36
+ switchedView,
37
+ setSwitchedView,
38
+ }) => {
39
+ const { t } = useTranslation();
40
+ const { results: paginatedAppointments, currentPage, goTo } = usePagination(patientAppointments, pageSize);
41
+ const isTablet = useLayoutType() === 'tablet';
42
+
43
+ useEffect(() => {
44
+ if (switchedView && currentPage !== 1) {
45
+ goTo(1);
46
+ }
47
+ }, [switchedView, goTo, currentPage]);
48
+
49
+ const tableHeaders: Array<typeof DataTableHeader> = useMemo(
50
+ () => [
51
+ { key: 'date', header: t('date', 'Date') },
52
+ { key: 'location', header: t('location', 'Location') },
53
+ { key: 'service', header: t('service', 'Service') },
54
+ { key: 'status', header: t('status', 'Status') },
55
+ { key: 'type', header: t('type', 'Type') },
56
+ { key: 'notes', header: t('notes', 'Notes') },
57
+ ],
58
+ [t],
59
+ );
60
+
61
+ const tableRows = useMemo(
62
+ () =>
63
+ paginatedAppointments?.map((appointment) => {
64
+ return {
65
+ id: appointment.uuid,
66
+ date: formatDatetime(parseDate(appointment.startDateTime), { mode: 'wide' }),
67
+ location: appointment?.location?.name ? appointment?.location?.name : '——',
68
+ service: appointment.service.name,
69
+ status: appointment.status,
70
+ type: appointment.appointmentKind ? appointment.appointmentKind : '——',
71
+ notes: appointment.comments ? appointment.comments : '——',
72
+ };
73
+ }),
74
+ [paginatedAppointments],
75
+ );
76
+
77
+ return (
78
+ <div>
79
+ <DataTable rows={tableRows} headers={tableHeaders} isSortable size={isTablet ? 'lg' : 'sm'} useZebraStyles>
80
+ {({ rows, headers, getHeaderProps, getTableProps }) => (
81
+ <TableContainer>
82
+ <Table {...getTableProps()}>
83
+ <TableHead>
84
+ <TableRow>
85
+ {headers.map((header) => (
86
+ <TableHeader
87
+ className={classNames(styles.productiveHeading01, styles.text02)}
88
+ {...getHeaderProps({
89
+ header,
90
+ isSortable: header.isSortable,
91
+ })}>
92
+ {header.header?.content ?? header.header}
93
+ </TableHeader>
94
+ ))}
95
+ <TableHeader />
96
+ </TableRow>
97
+ </TableHead>
98
+ <TableBody>
99
+ {rows.map((row, i) => (
100
+ <TableRow key={row.id}>
101
+ {row.cells.map((cell) => (
102
+ <TableCell key={cell.id}>{cell.value?.content ?? cell.value}</TableCell>
103
+ ))}
104
+ <TableCell className="cds--table-column-menu">
105
+ <PatientAppointmentsActionMenu appointment={paginatedAppointments[i]} patientUuid={patientUuid} />
106
+ </TableCell>
107
+ </TableRow>
108
+ ))}
109
+ </TableBody>
110
+ </Table>
111
+ </TableContainer>
112
+ )}
113
+ </DataTable>
114
+ <PatientChartPagination
115
+ currentItems={paginatedAppointments.length}
116
+ totalItems={patientAppointments.length}
117
+ onPageNumberChange={({ page }) => {
118
+ setSwitchedView(false);
119
+ goTo(page);
120
+ }}
121
+ pageNumber={currentPage}
122
+ pageSize={pageSize}
123
+ />
124
+ </div>
125
+ );
126
+ };
127
+
128
+ export default PatientAppointmentsTable;
@@ -0,0 +1,72 @@
1
+ import dayjs from 'dayjs';
2
+ import useSWR from 'swr';
3
+ import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
4
+ import { type AppointmentsFetchResponse } from '../types';
5
+ import isToday from 'dayjs/plugin/isToday';
6
+ dayjs.extend(isToday);
7
+
8
+ const appointmentsSearchUrl = `${restBaseUrl}/appointments/search`;
9
+
10
+ export function usePatientAppointments(patientUuid: string, startDate: string, abortController: AbortController) {
11
+ /*
12
+ SWR isn't meant to make POST requests for data fetching. This is a consequence of the API only exposing this resource via POST.
13
+ This works but likely isn't recommended.
14
+ */
15
+ const fetcher = () =>
16
+ openmrsFetch(appointmentsSearchUrl, {
17
+ method: 'POST',
18
+ signal: abortController.signal,
19
+ headers: {
20
+ 'Content-Type': 'application/json',
21
+ },
22
+ body: {
23
+ patientUuid: patientUuid,
24
+ startDate: startDate,
25
+ },
26
+ });
27
+
28
+ const { data, error, isLoading, isValidating, mutate } = useSWR<AppointmentsFetchResponse, Error>(
29
+ appointmentsSearchUrl,
30
+ fetcher,
31
+ );
32
+
33
+ const appointments = data?.data?.length ? data.data : null;
34
+
35
+ const pastAppointments = appointments
36
+ ?.sort((a, b) => (b.startDateTime > a.startDateTime ? 1 : -1))
37
+ ?.filter(({ status }) => status !== 'Cancelled')
38
+ ?.filter(({ startDateTime }) =>
39
+ dayjs(new Date(startDateTime).toISOString()).isBefore(new Date().setHours(0, 0, 0, 0)),
40
+ );
41
+
42
+ const upcomingAppointments = appointments
43
+ ?.sort((a, b) => (a.startDateTime > b.startDateTime ? 1 : -1))
44
+ ?.filter(({ status }) => status !== 'Cancelled')
45
+ ?.filter(({ startDateTime }) => dayjs(new Date(startDateTime).toISOString()).isAfter(new Date()));
46
+
47
+ const todaysAppointments = appointments
48
+ ?.sort((a, b) => (a.startDateTime > b.startDateTime ? 1 : -1))
49
+ ?.filter(({ status }) => status !== 'Cancelled')
50
+ ?.filter(({ startDateTime }) => dayjs(new Date(startDateTime).toISOString()).isToday());
51
+
52
+ return {
53
+ data: data ? { pastAppointments, upcomingAppointments, todaysAppointments } : null,
54
+ isError: error,
55
+ isLoading,
56
+ isValidating,
57
+ mutate,
58
+ };
59
+ }
60
+
61
+ // TODO: move?
62
+ export const changeAppointmentStatus = async (toStatus: string, appointmentUuid: string) => {
63
+ const omrsDateFormat = 'YYYY-MM-DDTHH:mm:ss.SSSZZ';
64
+ const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
65
+ const statusChangeTime = dayjs(new Date()).format(omrsDateFormat);
66
+ const url = `${restBaseUrl}/appointments/${appointmentUuid}/status-change`;
67
+ return await openmrsFetch(url, {
68
+ body: { toStatus, onDate: statusChangeTime, timeZone: timeZone },
69
+ method: 'POST',
70
+ headers: { 'Content-Type': 'application/json' },
71
+ });
72
+ };
@@ -0,0 +1,122 @@
1
+ import React, { useEffect, useMemo, useState } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import {
4
+ InlineLoading,
5
+ InlineNotification,
6
+ RadioButton,
7
+ StructuredListBody,
8
+ StructuredListCell,
9
+ StructuredListHead,
10
+ StructuredListRow,
11
+ StructuredListWrapper,
12
+ } from '@carbon/react';
13
+ import { formatDate, parseDate } from '@openmrs/esm-framework';
14
+ import { usePatientAppointments } from './patient-appointments.resource';
15
+ import { ErrorState } from '@openmrs/esm-patient-common-lib';
16
+ import styles from './patient-upcoming-appointments-card.scss';
17
+ import dayjs from 'dayjs';
18
+ import { type Appointment } from '../types';
19
+
20
+ interface PatientUpcomingAppointmentsProps {
21
+ patientUuid: string;
22
+ setUpcomingAppointment: (value: Appointment) => void;
23
+ }
24
+
25
+ const PatientUpcomingAppointmentsCard: React.FC<PatientUpcomingAppointmentsProps> = ({
26
+ patientUuid,
27
+ setUpcomingAppointment,
28
+ }) => {
29
+ const { t } = useTranslation();
30
+ const startDate = dayjs(new Date().toISOString()).subtract(6, 'month').toISOString();
31
+ const headerTitle = t('upcomingAppointments', 'Upcoming appointments');
32
+ const [selectedAppointment, setSelectedAppointment] = useState(null);
33
+
34
+ const ac = useMemo<AbortController>(() => new AbortController(), []);
35
+ useEffect(() => () => ac.abort(), [ac]);
36
+ const { data: appointmentsData, isError, isLoading } = usePatientAppointments(patientUuid, startDate, ac);
37
+
38
+ const todaysAppointments = appointmentsData?.todaysAppointments?.length ? appointmentsData?.todaysAppointments : [];
39
+ const futureAppointments = appointmentsData?.upcomingAppointments?.length
40
+ ? appointmentsData?.upcomingAppointments
41
+ : [];
42
+
43
+ const appointments = todaysAppointments
44
+ .concat(futureAppointments)
45
+ .filter((appointment) => appointment.status !== 'CheckedIn');
46
+
47
+ useEffect(() => {
48
+ if (appointments.length === 1) {
49
+ setSelectedAppointment(appointments[0]);
50
+ setUpcomingAppointment(appointments[0]);
51
+ }
52
+ }, [appointments]);
53
+
54
+ const handleRadioChange = (appointment: Appointment) => {
55
+ setSelectedAppointment(appointment);
56
+ setUpcomingAppointment(appointment);
57
+ };
58
+
59
+ if (isError) {
60
+ return <ErrorState headerTitle={headerTitle} error={isError} />;
61
+ }
62
+ if (isLoading) {
63
+ return (
64
+ <span>
65
+ <InlineLoading />
66
+ </span>
67
+ );
68
+ }
69
+
70
+ if (appointments.length) {
71
+ return (
72
+ <div>
73
+ <div>
74
+ <p className={styles.sectionTitle}>{headerTitle}</p>
75
+ <span className={styles.headerLabel}>{t('appointmentToFulfill', 'Select appointment to fulfill')}</span>
76
+ </div>
77
+ <StructuredListWrapper>
78
+ <StructuredListHead>
79
+ <StructuredListRow head>
80
+ <StructuredListCell head>{t('date', 'Date')}</StructuredListCell>
81
+ <StructuredListCell head>{t('appointmentType', 'Appointment type')}</StructuredListCell>
82
+ <StructuredListCell head>{t('action', 'Action')}</StructuredListCell>
83
+ </StructuredListRow>
84
+ </StructuredListHead>
85
+ <StructuredListBody>
86
+ {appointments.map((appointment, index) => (
87
+ <StructuredListRow key={index} className={styles.structuredList}>
88
+ <StructuredListCell>
89
+ {formatDate(parseDate(appointment.startDateTime), { mode: 'wide' })}
90
+ </StructuredListCell>
91
+ <StructuredListCell>{appointment.service ? appointment.service.name : '——'}</StructuredListCell>
92
+ <StructuredListCell>
93
+ <RadioButton
94
+ className={styles.radioButton}
95
+ labelText=""
96
+ id={`radio-${index}`}
97
+ name="appointmentRadio"
98
+ value={appointment.uuid}
99
+ checked={selectedAppointment === appointment}
100
+ onChange={() => handleRadioChange(appointment)}
101
+ />
102
+ </StructuredListCell>
103
+ </StructuredListRow>
104
+ ))}
105
+ </StructuredListBody>
106
+ </StructuredListWrapper>
107
+ </div>
108
+ );
109
+ }
110
+
111
+ return (
112
+ <InlineNotification
113
+ kind={'info'}
114
+ lowContrast
115
+ className={styles.inlineNotification}
116
+ title={t('upcomingAppointments', 'Upcoming appointments')}
117
+ subtitle={t('noUpcomingAppointments', 'No upcoming appointments found')}
118
+ />
119
+ );
120
+ };
121
+
122
+ export default PatientUpcomingAppointmentsCard;
@@ -0,0 +1,46 @@
1
+ @use '@carbon/styles/scss/spacing';
2
+ @use '@carbon/styles/scss/type';
3
+ @import '@openmrs/esm-styleguide/src/vars';
4
+
5
+ .container {
6
+ margin: spacing.$spacing-05;
7
+
8
+ & section {
9
+ margin: spacing.$spacing-05 0;
10
+ }
11
+ }
12
+
13
+ .sectionTitle {
14
+ @include type.type-style('heading-compact-02');
15
+ color: $text-02;
16
+ margin: 0 0 spacing.$spacing-03 0;
17
+ }
18
+ .checkbox {
19
+ &:not(:first-child) {
20
+ margin: 0rem 0rem;
21
+ }
22
+ }
23
+
24
+ .input {
25
+ margin: 0rem 1rem 1rem;
26
+ }
27
+
28
+ .headerLabel {
29
+ @include type.type-style('label-01');
30
+ color: $text-02;
31
+ }
32
+
33
+ .checkboxContainer {
34
+ display: grid;
35
+ grid-template-columns: 1fr 1fr;
36
+ }
37
+
38
+ .structuredList {
39
+ padding: 0.5rem 0.5rem 0.5rem;
40
+ }
41
+
42
+ .inlineNotification {
43
+ width: 100%;
44
+ max-width: unset;
45
+ padding: '0rem';
46
+ }
@@ -0,0 +1,34 @@
1
+ import React from 'react';
2
+ import { ExtensionSlot, launchWorkspace } from '@openmrs/esm-framework';
3
+ import styles from './patient-search.scss';
4
+ import { useTranslation } from 'react-i18next';
5
+
6
+ const PatientSearch: React.FC = () => {
7
+ const { t } = useTranslation();
8
+ const launchCreateAppointmentForm = (patient) => {
9
+ const props = {
10
+ patientUuid: patient.uuid,
11
+ context: 'creating',
12
+ mutate: () => {}, // TODO get this to mutate properly
13
+ };
14
+ launchWorkspace('create-appointment', { ...props });
15
+ };
16
+
17
+ return (
18
+ <div className="omrs-main-content">
19
+ <span className={styles.searchBarWrapper}>
20
+ <ExtensionSlot
21
+ name="patient-search-bar-slot"
22
+ state={{
23
+ selectPatientAction: launchCreateAppointmentForm,
24
+ buttonProps: {
25
+ kind: 'primary',
26
+ },
27
+ }}
28
+ />
29
+ </span>
30
+ </div>
31
+ );
32
+ };
33
+
34
+ export default PatientSearch;
@@ -0,0 +1,23 @@
1
+ @use '@carbon/styles/scss/spacing';
2
+ @use '@carbon/styles/scss/colors';
3
+ @import '~@openmrs/esm-styleguide/src/vars';
4
+
5
+ .padded {
6
+ padding: spacing.$spacing-05;
7
+ }
8
+
9
+ [data-extension-id='patient-search-bar'] {
10
+ & > div {
11
+ width: 100%;
12
+
13
+ & input {
14
+ background-color: $ui-02;
15
+ border-bottom: none;
16
+ min-height: spacing.$spacing-09;
17
+ }
18
+
19
+ & button {
20
+ height: spacing.$spacing-09;
21
+ }
22
+ }
23
+ }
@@ -0,0 +1,26 @@
1
+ import React from 'react';
2
+ import { BrowserRouter, Route, Routes } from 'react-router-dom';
3
+ import AppointmentsCalendarView from './calendar/appointments-calendar-view.component';
4
+ import Appointments from './appointments.component';
5
+ import PatientAppointmentsOverview from './patient-appointments/patient-appointments-overview.component';
6
+
7
+ const RootComponent: React.FC = () => {
8
+ const appointmentsBasename = window.getOpenmrsSpaBase() + 'home/appointments';
9
+
10
+ return (
11
+ <main>
12
+ <BrowserRouter basename={appointmentsBasename}>
13
+ <Routes>
14
+ <Route path="/" element={<Appointments />} />
15
+ <Route path="/:date" element={<Appointments />} />
16
+ <Route path="/:date/:serviceType" element={<Appointments />} />
17
+ <Route path="/calendar" element={<AppointmentsCalendarView />} />
18
+ <Route path="/calendar/:date" element={<AppointmentsCalendarView />} />
19
+ <Route path="/patient/:patientUuid" element={<PatientAppointmentsOverview />} />
20
+ </Routes>
21
+ </BrowserRouter>
22
+ </main>
23
+ );
24
+ };
25
+
26
+ export default RootComponent;
package/src/root.scss ADDED
@@ -0,0 +1,50 @@
1
+ @use '@carbon/styles/scss/spacing';
2
+ @use '@carbon/styles/scss/type';
3
+ @import '~@openmrs/esm-styleguide/src/vars';
4
+
5
+ .productiveHeading01 {
6
+ @include type.type-style('heading-compact-01');
7
+ }
8
+
9
+ .productiveHeading02 {
10
+ @include type.type-style('heading-compact-02');
11
+ }
12
+
13
+ .productiveHeading03 {
14
+ @include type.type-style('heading-03');
15
+ }
16
+
17
+ .productiveHeading04 {
18
+ @include type.type-style('heading-04');
19
+ }
20
+
21
+ .bodyLong01 {
22
+ @include type.type-style('body-01');
23
+ }
24
+
25
+ .bodyShort01 {
26
+ @include type.type-style('body-compact-01');
27
+ }
28
+
29
+ .bodyShort02 {
30
+ @include type.type-style('body-compact-02');
31
+ }
32
+
33
+ .text02 {
34
+ color: $text-02;
35
+ }
36
+
37
+ .label01 {
38
+ @include type.type-style('label-01');
39
+ }
40
+
41
+ .modal {
42
+ position: fixed;
43
+ width: 100vw;
44
+ height: 100vh;
45
+ display: grid;
46
+ background-color: rgba(0, 0, 0, 0.5);
47
+ z-index: 9000;
48
+ justify-items: center;
49
+ align-items: center;
50
+ }