@openmrs/esm-appointments-app 10.0.2 → 10.0.3-pre.1

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 (391) hide show
  1. package/.turbo/turbo-build.log +6 -5
  2. package/dist/1339.js +1 -0
  3. package/dist/1339.js.map +1 -0
  4. package/dist/1465.js +1 -0
  5. package/dist/1465.js.map +1 -0
  6. package/dist/1480.js +1 -0
  7. package/dist/1480.js.map +1 -0
  8. package/dist/1646.js +1 -0
  9. package/dist/1646.js.map +1 -0
  10. package/dist/1789.js +1 -0
  11. package/dist/1789.js.map +1 -0
  12. package/dist/1869.js +1 -0
  13. package/dist/1869.js.map +1 -0
  14. package/dist/1877.js +1 -0
  15. package/dist/1877.js.map +1 -0
  16. package/dist/1884.js +1 -0
  17. package/dist/1884.js.map +1 -0
  18. package/dist/1962.js +1 -0
  19. package/dist/1962.js.map +1 -0
  20. package/dist/2317.js +1 -0
  21. package/dist/2317.js.map +1 -0
  22. package/dist/2416.js +1 -0
  23. package/dist/2416.js.map +1 -0
  24. package/dist/2495.js +1 -0
  25. package/dist/2495.js.map +1 -0
  26. package/dist/2539.js +1 -0
  27. package/dist/2539.js.map +1 -0
  28. package/dist/2620.js +1 -0
  29. package/dist/2620.js.map +1 -0
  30. package/dist/2717.js +1 -0
  31. package/dist/2717.js.map +1 -0
  32. package/dist/282.js +1 -0
  33. package/dist/282.js.map +1 -0
  34. package/dist/2881.js +1 -0
  35. package/dist/2881.js.map +1 -0
  36. package/dist/3198.js +11 -0
  37. package/dist/3198.js.map +1 -0
  38. package/dist/3220.js +1 -0
  39. package/dist/3220.js.map +1 -0
  40. package/dist/3378.js +1 -0
  41. package/dist/3378.js.map +1 -0
  42. package/dist/3720.js +1 -0
  43. package/dist/3720.js.map +1 -0
  44. package/dist/3930.js +1 -0
  45. package/dist/3930.js.map +1 -0
  46. package/dist/3963.js +1 -0
  47. package/dist/3963.js.map +1 -0
  48. package/dist/3989.js +1 -0
  49. package/dist/3989.js.map +1 -0
  50. package/dist/4106.js +1 -0
  51. package/dist/4106.js.map +1 -0
  52. package/dist/4111.js +1 -0
  53. package/dist/4111.js.map +1 -0
  54. package/dist/4307.js +1 -0
  55. package/dist/4307.js.map +1 -0
  56. package/dist/434.js +1 -0
  57. package/dist/434.js.map +1 -0
  58. package/dist/4348.js +1 -0
  59. package/dist/4348.js.map +1 -0
  60. package/dist/4383.js +1 -0
  61. package/dist/4383.js.map +1 -0
  62. package/dist/4658.js +1 -0
  63. package/dist/4658.js.map +1 -0
  64. package/dist/466.js +1 -0
  65. package/dist/466.js.map +1 -0
  66. package/dist/4928.js +1 -0
  67. package/dist/4928.js.map +1 -0
  68. package/dist/5117.js +1 -0
  69. package/dist/5117.js.map +1 -0
  70. package/dist/5132.js +1 -0
  71. package/dist/5132.js.map +1 -0
  72. package/dist/5145.js +1 -0
  73. package/dist/5145.js.map +1 -0
  74. package/dist/5503.js +1 -0
  75. package/dist/5503.js.map +1 -0
  76. package/dist/556.js +1 -0
  77. package/dist/556.js.map +1 -0
  78. package/dist/5640.js +1 -0
  79. package/dist/5640.js.map +1 -0
  80. package/dist/5644.js +1 -0
  81. package/dist/5644.js.map +1 -0
  82. package/dist/5940.js +1 -0
  83. package/dist/5940.js.map +1 -0
  84. package/dist/6047.js +1 -0
  85. package/dist/6047.js.map +1 -0
  86. package/dist/6098.js +38 -0
  87. package/dist/6098.js.map +1 -0
  88. package/dist/6236.js +1 -0
  89. package/dist/6236.js.map +1 -0
  90. package/dist/635.js +1 -0
  91. package/dist/635.js.map +1 -0
  92. package/dist/6371.js +1 -0
  93. package/dist/6371.js.map +1 -0
  94. package/dist/6377.js +1 -0
  95. package/dist/6377.js.map +1 -0
  96. package/dist/6444.js +1 -0
  97. package/dist/6444.js.map +1 -0
  98. package/dist/6508.js +1 -0
  99. package/dist/6508.js.map +1 -0
  100. package/dist/6724.js +1 -0
  101. package/dist/6724.js.map +1 -0
  102. package/dist/6789.js +1 -0
  103. package/dist/6789.js.map +1 -0
  104. package/dist/689.js +1 -0
  105. package/dist/689.js.map +1 -0
  106. package/dist/6904.js +1 -0
  107. package/dist/6904.js.map +1 -0
  108. package/dist/7045.js +1 -0
  109. package/dist/7045.js.map +1 -0
  110. package/dist/7138.js +1 -0
  111. package/dist/7138.js.map +1 -0
  112. package/dist/7159.js +1 -0
  113. package/dist/7159.js.map +1 -0
  114. package/dist/7175.js +1 -0
  115. package/dist/7175.js.map +1 -0
  116. package/dist/7182.js +1 -0
  117. package/dist/7182.js.map +1 -0
  118. package/dist/7357.js +1 -0
  119. package/dist/7357.js.map +1 -0
  120. package/dist/7609.js +1 -0
  121. package/dist/7609.js.map +1 -0
  122. package/dist/7654.js +1 -0
  123. package/dist/7654.js.map +1 -0
  124. package/dist/7742.js +1 -0
  125. package/dist/7742.js.map +1 -0
  126. package/dist/7912.js +1 -0
  127. package/dist/7912.js.map +1 -0
  128. package/dist/8063.js +1 -0
  129. package/dist/8063.js.map +1 -0
  130. package/dist/8346.js +1 -0
  131. package/dist/8346.js.map +1 -0
  132. package/dist/8358.js +1 -0
  133. package/dist/8358.js.map +1 -0
  134. package/dist/8359.js +1 -0
  135. package/dist/8359.js.map +1 -0
  136. package/dist/8695.js +1 -0
  137. package/dist/8695.js.map +1 -0
  138. package/dist/8937.js +1 -0
  139. package/dist/8937.js.map +1 -0
  140. package/dist/8981.js +1 -0
  141. package/dist/8981.js.map +1 -0
  142. package/dist/903.js +1 -0
  143. package/dist/903.js.map +1 -0
  144. package/dist/9061.js +1 -0
  145. package/dist/9061.js.map +1 -0
  146. package/dist/9072.js +1 -0
  147. package/dist/9072.js.map +1 -0
  148. package/dist/9282.js +1 -0
  149. package/dist/9282.js.map +1 -0
  150. package/dist/9399.js +1 -0
  151. package/dist/9399.js.map +1 -0
  152. package/dist/9443.js +1 -0
  153. package/dist/9443.js.map +1 -0
  154. package/dist/9581.js +1 -0
  155. package/dist/9581.js.map +1 -0
  156. package/dist/9712.js +1 -0
  157. package/dist/9712.js.map +1 -0
  158. package/dist/9771.js +1 -0
  159. package/dist/9771.js.map +1 -0
  160. package/dist/9806.js +1 -0
  161. package/dist/9806.js.map +1 -0
  162. package/dist/main.js +6 -5
  163. package/dist/main.js.map +1 -1
  164. package/dist/openmrs-esm-appointments-app.js +6 -5
  165. package/dist/openmrs-esm-appointments-app.js.buildmanifest.json +625 -620
  166. package/dist/openmrs-esm-appointments-app.js.map +1 -1
  167. package/dist/routes.json +1 -1
  168. package/package.json +3 -2
  169. package/src/appointments/appointment-tabs.component.tsx +38 -16
  170. package/src/appointments/common-components/appointments-actions.component.tsx +39 -47
  171. package/src/appointments/common-components/appointments-actions.test.tsx +52 -99
  172. package/src/appointments/common-components/appointments-table.component.tsx +109 -135
  173. package/src/appointments/common-components/appointments-table.scss +10 -6
  174. package/src/appointments/common-components/appointments-table.test.tsx +2 -9
  175. package/src/appointments/common-components/batch-change-appointment-statuses.modal.tsx +1 -1
  176. package/src/appointments/common-components/batch-change-appointment-statuses.test.tsx +8 -6
  177. package/src/appointments/common-components/checkin-button.component.tsx +60 -29
  178. package/src/appointments/common-components/end-appointment.modal.tsx +1 -1
  179. package/src/appointments/common-components/end-appointment.test.tsx +1 -1
  180. package/src/appointments/scheduled/early-appointments.component.tsx +6 -7
  181. package/src/appointments/scheduled/scheduled-appointments.component.tsx +26 -206
  182. package/src/appointments/utils.tsx +29 -16
  183. package/src/appointments.component.tsx +5 -13
  184. package/src/appointments.test.tsx +16 -4
  185. package/src/calendar/appointments-calendar-view.component.tsx +3 -12
  186. package/src/calendar/appointments-calendar-view.test.tsx +6 -1
  187. package/src/calendar/header/calendar-header.component.tsx +2 -2
  188. package/src/calendar/monthly/monthly-calendar-view.component.tsx +2 -2
  189. package/src/calendar/monthly/monthly-header.component.tsx +9 -8
  190. package/src/calendar/monthly/monthly-workload-view.component.tsx +2 -2
  191. package/src/config-schema.ts +17 -9
  192. package/src/constants.ts +12 -1
  193. package/src/form/appointments-form.resource.ts +1 -120
  194. package/src/form/appointments-form.workspace.tsx +15 -18
  195. package/src/header/appointments-header.component.tsx +27 -9
  196. package/src/helpers/functions.ts +1 -50
  197. package/src/home/home-appointments.component.tsx +17 -4
  198. package/src/hooks/useActiveVisits.ts +14 -0
  199. package/src/hooks/useAppointmentList.ts +12 -29
  200. package/src/hooks/useAppointmentService.ts +6 -1
  201. package/src/hooks/useAppointmentsAppContext.ts +19 -0
  202. package/src/hooks/useAppointmentsCalendar.ts +2 -1
  203. package/src/hooks/useMutateAppointments.ts +24 -0
  204. package/src/hooks/usePatientAppointmentHistory.ts +3 -2
  205. package/src/hooks/useSelectedDate.ts +24 -0
  206. package/src/hooks/useUnscheduledAppointments.ts +5 -1
  207. package/src/index.ts +0 -21
  208. package/src/metrics/metrics-cards/highest-volume-service.extension.tsx +27 -6
  209. package/src/metrics/metrics-cards/metrics-card.component.tsx +2 -1
  210. package/src/metrics/metrics-cards/providers-booked.extension.tsx +14 -6
  211. package/src/metrics/metrics-cards/scheduled-appointments.extension.tsx +20 -16
  212. package/src/metrics/metrics-container.component.tsx +1 -5
  213. package/src/metrics/metrics-header.component.tsx +2 -2
  214. package/src/patient-appointments/patient-appointments-detailed-summary.extension.tsx +1 -1
  215. package/src/patient-appointments/patient-upcoming-appointments-card.component.tsx +3 -4
  216. package/src/routes.json +2 -26
  217. package/src/store.ts +24 -41
  218. package/src/types/index.ts +7 -0
  219. package/src/workload/monthly-view-workload/monthly-view.component.tsx +2 -2
  220. package/src/workload/workload.component.tsx +3 -3
  221. package/src/workload/workload.resource.ts +1 -1
  222. package/translations/am.json +15 -6
  223. package/translations/ar.json +15 -6
  224. package/translations/ar_SY.json +15 -6
  225. package/translations/bn.json +15 -6
  226. package/translations/cs.json +15 -6
  227. package/translations/de.json +177 -168
  228. package/translations/en.json +15 -6
  229. package/translations/en_US.json +15 -6
  230. package/translations/es.json +15 -6
  231. package/translations/es_MX.json +15 -6
  232. package/translations/fr.json +15 -6
  233. package/translations/he.json +15 -6
  234. package/translations/hi.json +15 -6
  235. package/translations/hi_IN.json +15 -6
  236. package/translations/id.json +15 -6
  237. package/translations/it.json +15 -6
  238. package/translations/ka.json +15 -6
  239. package/translations/km.json +15 -6
  240. package/translations/ku.json +15 -6
  241. package/translations/ky.json +15 -6
  242. package/translations/lg.json +15 -6
  243. package/translations/ne.json +15 -6
  244. package/translations/pl.json +15 -6
  245. package/translations/pt.json +15 -6
  246. package/translations/pt_BR.json +15 -6
  247. package/translations/qu.json +15 -6
  248. package/translations/ro_RO.json +15 -6
  249. package/translations/ru_RU.json +15 -6
  250. package/translations/si.json +15 -6
  251. package/translations/sq.json +15 -6
  252. package/translations/sw.json +15 -6
  253. package/translations/sw_KE.json +15 -6
  254. package/translations/tr.json +15 -6
  255. package/translations/tr_TR.json +15 -6
  256. package/translations/uk.json +15 -6
  257. package/translations/uz.json +15 -6
  258. package/translations/uz@Latn.json +15 -6
  259. package/translations/uz_UZ.json +15 -6
  260. package/translations/vi.json +15 -6
  261. package/translations/zh.json +63 -54
  262. package/translations/zh_CN.json +19 -10
  263. package/translations/zh_TW.json +15 -6
  264. package/dist/1187.js +0 -1
  265. package/dist/1187.js.map +0 -1
  266. package/dist/126.js +0 -1
  267. package/dist/1499.js +0 -1
  268. package/dist/1499.js.map +0 -1
  269. package/dist/15.js +0 -1
  270. package/dist/1522.js +0 -1
  271. package/dist/1522.js.map +0 -1
  272. package/dist/1564.js +0 -1
  273. package/dist/1567.js +0 -1
  274. package/dist/1777.js +0 -1
  275. package/dist/1777.js.map +0 -1
  276. package/dist/1845.js +0 -1
  277. package/dist/1899.js +0 -1
  278. package/dist/1899.js.map +0 -1
  279. package/dist/1953.js +0 -1
  280. package/dist/2056.js +0 -11
  281. package/dist/2056.js.map +0 -1
  282. package/dist/2104.js +0 -1
  283. package/dist/2104.js.map +0 -1
  284. package/dist/215.js +0 -1
  285. package/dist/2178.js +0 -1
  286. package/dist/2417.js +0 -1
  287. package/dist/2417.js.map +0 -1
  288. package/dist/2566.js +0 -1
  289. package/dist/2586.js +0 -1
  290. package/dist/2586.js.map +0 -1
  291. package/dist/2759.js +0 -1
  292. package/dist/276.js +0 -1
  293. package/dist/276.js.map +0 -1
  294. package/dist/3089.js +0 -1
  295. package/dist/3089.js.map +0 -1
  296. package/dist/3127.js +0 -1
  297. package/dist/3127.js.map +0 -1
  298. package/dist/3230.js +0 -1
  299. package/dist/3277.js +0 -1
  300. package/dist/3277.js.map +0 -1
  301. package/dist/3441.js +0 -1
  302. package/dist/3565.js +0 -1
  303. package/dist/3571.js +0 -1
  304. package/dist/3571.js.map +0 -1
  305. package/dist/3746.js +0 -1
  306. package/dist/3925.js +0 -1
  307. package/dist/3946.js +0 -1
  308. package/dist/4085.js +0 -1
  309. package/dist/4085.js.map +0 -1
  310. package/dist/4108.js +0 -1
  311. package/dist/4108.js.map +0 -1
  312. package/dist/4448.js +0 -1
  313. package/dist/4448.js.map +0 -1
  314. package/dist/4744.js +0 -1
  315. package/dist/4744.js.map +0 -1
  316. package/dist/4809.js +0 -1
  317. package/dist/486.js +0 -1
  318. package/dist/486.js.map +0 -1
  319. package/dist/4894.js +0 -1
  320. package/dist/4970.js +0 -1
  321. package/dist/4970.js.map +0 -1
  322. package/dist/5130.js +0 -1
  323. package/dist/5187.js +0 -1
  324. package/dist/5218.js +0 -1
  325. package/dist/5218.js.map +0 -1
  326. package/dist/5327.js +0 -1
  327. package/dist/5327.js.map +0 -1
  328. package/dist/5388.js +0 -1
  329. package/dist/5388.js.map +0 -1
  330. package/dist/5491.js +0 -1
  331. package/dist/5491.js.map +0 -1
  332. package/dist/5595.js +0 -1
  333. package/dist/5657.js +0 -38
  334. package/dist/5657.js.map +0 -1
  335. package/dist/5961.js +0 -1
  336. package/dist/6133.js +0 -1
  337. package/dist/634.js +0 -1
  338. package/dist/634.js.map +0 -1
  339. package/dist/6456.js +0 -1
  340. package/dist/6466.js +0 -1
  341. package/dist/6613.js +0 -1
  342. package/dist/6783.js +0 -1
  343. package/dist/703.js +0 -1
  344. package/dist/703.js.map +0 -1
  345. package/dist/7251.js +0 -1
  346. package/dist/7251.js.map +0 -1
  347. package/dist/7348.js +0 -1
  348. package/dist/7433.js +0 -1
  349. package/dist/7433.js.map +0 -1
  350. package/dist/7513.js +0 -1
  351. package/dist/7513.js.map +0 -1
  352. package/dist/7543.js +0 -1
  353. package/dist/7607.js +0 -1
  354. package/dist/772.js +0 -1
  355. package/dist/8139.js +0 -1
  356. package/dist/8139.js.map +0 -1
  357. package/dist/8456.js +0 -1
  358. package/dist/8456.js.map +0 -1
  359. package/dist/8588.js +0 -1
  360. package/dist/8588.js.map +0 -1
  361. package/dist/8599.js +0 -1
  362. package/dist/8727.js +0 -1
  363. package/dist/8847.js +0 -1
  364. package/dist/8919.js +0 -1
  365. package/dist/8919.js.map +0 -1
  366. package/dist/9015.js +0 -1
  367. package/dist/9051.js +0 -1
  368. package/dist/9051.js.map +0 -1
  369. package/dist/906.js +0 -1
  370. package/dist/9065.js +0 -1
  371. package/dist/9182.js +0 -1
  372. package/dist/9260.js +0 -1
  373. package/dist/9260.js.map +0 -1
  374. package/dist/9327.js +0 -1
  375. package/dist/9327.js.map +0 -1
  376. package/dist/9339.js +0 -1
  377. package/dist/9453.js +0 -1
  378. package/dist/9589.js +0 -1
  379. package/dist/9589.js.map +0 -1
  380. package/dist/9650.js +0 -1
  381. package/dist/9650.js.map +0 -1
  382. package/dist/9833.js +0 -1
  383. package/dist/9833.js.map +0 -1
  384. package/dist/9920.js +0 -1
  385. package/dist/9938.js +0 -1
  386. package/dist/9943.js +0 -1
  387. package/dist/9943.js.map +0 -1
  388. package/src/appointments/scheduled/appointments-list.component.tsx +0 -51
  389. package/src/hooks/useClinicalMetrics.ts +0 -94
  390. package/src/hooks/useTodaysVisits.ts +0 -19
  391. package/src/scheduled-appointments-config-schema.ts +0 -177
@@ -3,19 +3,18 @@ import { useTranslation } from 'react-i18next';
3
3
  import { filterByServiceType } from '../utils';
4
4
  import { useEarlyAppointmentList } from '../../hooks/useAppointmentList';
5
5
  import AppointmentsTable from '../common-components/appointments-table.component';
6
-
7
- interface EarlyAppointmentsProps {
8
- appointmentServiceTypes?: Array<string>;
9
- date: string;
10
- }
6
+ import { useAppointmentsStore } from '../../store';
7
+ import { useSelectedDate } from '../../hooks/useSelectedDate';
11
8
 
12
9
  /**
13
10
  * Component to display early appointments
14
11
  * Note that although we define this extension in routes.jsx, we currently don't wire it into the scheduled-appointments-panels-slot by default because it requests a custom endpoint (see useEarlyAppointments) not provided by the standard Bahmni Appointments module
15
12
  */
16
- const EarlyAppointments: React.FC<EarlyAppointmentsProps> = ({ appointmentServiceTypes, date }) => {
13
+ const EarlyAppointments: React.FC = () => {
17
14
  const { t } = useTranslation();
18
- const { earlyAppointmentList, isLoading } = useEarlyAppointmentList(date);
15
+ const { appointmentServiceTypes } = useAppointmentsStore();
16
+ const selectedDate = useSelectedDate();
17
+ const { earlyAppointmentList, isLoading } = useEarlyAppointmentList(selectedDate);
19
18
 
20
19
  const appointments = filterByServiceType(earlyAppointmentList, appointmentServiceTypes).map((appointment, index) => {
21
20
  return {
@@ -1,225 +1,45 @@
1
- import React, { useCallback, useEffect, useReducer, useRef, useState } from 'react';
1
+ import React from 'react';
2
2
  import dayjs from 'dayjs';
3
3
  import { useTranslation } from 'react-i18next';
4
- import { ContentSwitcher, Switch } from '@carbon/react';
5
4
  import isSameOrBefore from 'dayjs/plugin/isSameOrBefore';
6
- import {
7
- ExtensionSlot,
8
- Extension,
9
- useConnectedExtensions,
10
- type ConnectedExtension,
11
- type ConfigObject,
12
- useLayoutType,
13
- isDesktop,
14
- useAssignedExtensions,
15
- } from '@openmrs/esm-framework';
5
+ import { formatDate, parseDate, useDefineAppContext } from '@openmrs/esm-framework';
16
6
  import { useAppointmentsStore } from '../../store';
7
+ import { type AppointmentsAppContext } from '../../types';
8
+ import { useAppointmentList } from '../../hooks/useAppointmentList';
9
+ import AppointmentsTable from '../common-components/appointments-table.component';
10
+ import { useSelectedDate } from '../../hooks/useSelectedDate';
17
11
  import styles from './scheduled-appointments.scss';
18
- import { type AppointmentPanelConfig } from '../../scheduled-appointments-config-schema';
19
12
 
20
13
  dayjs.extend(isSameOrBefore);
21
14
 
22
- interface ScheduledAppointmentsProps {
23
- appointmentServiceTypes?: Array<string>;
24
- }
25
-
26
- type DateType = 'pastDate' | 'today' | 'futureDate';
27
-
28
- const scheduledAppointmentsPanelsSlot = 'scheduled-appointments-panels-slot';
29
-
30
- const ScheduledAppointments: React.FC<ScheduledAppointmentsProps> = ({ appointmentServiceTypes }) => {
15
+ const ScheduledAppointments: React.FC<{}> = () => {
31
16
  const { t } = useTranslation();
32
- const { selectedDate } = useAppointmentsStore();
33
- const layout = useLayoutType();
34
- const responsiveSize = isDesktop(layout) ? 'sm' : 'md';
17
+ const { appointmentServiceTypes } = useAppointmentsStore();
18
+ const selectedDate = useSelectedDate();
35
19
 
36
- // added to prevent auto-removal of translations for dynamic keys
37
- // t('checkedIn', 'Checked in');
38
- // t('expected', 'Expected');
20
+ const { appointmentList: appointmentsForSelectedDate, isLoading, error } = useAppointmentList(selectedDate);
39
21
 
40
- const [currentTab, setCurrentTab] = useState(null);
41
- const [dateType, setDateType] = useState<DateType>('today');
42
- const scheduledAppointmentPanels = useAssignedExtensions(scheduledAppointmentsPanelsSlot);
43
- const { allowedExtensions, showExtension, hideExtension } = useAllowedExtensions();
44
- const shouldShowPanel = useCallback(
45
- (panel: Omit<ConnectedExtension, 'config'>) => allowedExtensions[panel.name] ?? false,
46
- [allowedExtensions],
47
- );
22
+ const appointmentForSelectedDateFilteredByServiceTypes =
23
+ appointmentServiceTypes.length > 0
24
+ ? appointmentsForSelectedDate.filter(({ service }) => appointmentServiceTypes.includes(service.uuid))
25
+ : appointmentsForSelectedDate;
48
26
 
49
- useEffect(() => {
50
- const dayjsDate = dayjs(selectedDate);
51
- const now = dayjs();
52
- if (dayjsDate.isBefore(now, 'date')) {
53
- setDateType('pastDate');
54
- } else if (dayjsDate.isAfter(now, 'date')) {
55
- setDateType('futureDate');
56
- } else {
57
- setDateType('today');
58
- }
59
- }, [selectedDate]);
60
-
61
- useEffect(() => {
62
- // This is intended to cover two things:
63
- // 1. If no current tab is set, set it to the first allowed tab
64
- // 2. If a current tab is set, but the tab is no longer allowed in this context, set it to the
65
- // first allowed tab
66
- if (allowedExtensions && (currentTab === null || !allowedExtensions[currentTab])) {
67
- for (const extension of Object.getOwnPropertyNames(allowedExtensions)) {
68
- if (allowedExtensions[extension]) {
69
- setCurrentTab(extension);
70
- break;
71
- }
72
- }
73
- }
74
- }, [allowedExtensions, currentTab]);
75
-
76
- const panelsToShow = scheduledAppointmentPanels.filter(shouldShowPanel);
27
+ useDefineAppContext<AppointmentsAppContext>('appointments', {
28
+ appointmentForSelectedDateFilteredByServiceTypes,
29
+ isLoading,
30
+ error,
31
+ });
32
+ const formattedDate = formatDate(parseDate(selectedDate), { mode: 'standard', time: false });
77
33
 
78
34
  return (
79
- <>
80
- <ContentSwitcher
81
- className={styles.switcher}
82
- size={responsiveSize}
83
- onChange={({ name }) => setCurrentTab(name as string)}
84
- selectedIndex={panelsToShow.findIndex((panel) => panel.name == currentTab) ?? 0}
85
- selectionMode="manual">
86
- {panelsToShow.map((panel) => (
87
- <Switch key={`panel-${panel.name}`} name={panel.name}>
88
- {t(panel.config.title)}
89
- </Switch>
90
- ))}
91
- </ContentSwitcher>
92
-
93
- <ExtensionSlot name={scheduledAppointmentsPanelsSlot}>
94
- {(extension) => {
95
- return (
96
- <ExtensionWrapper
97
- appointmentServiceTypes={appointmentServiceTypes}
98
- currentTab={currentTab}
99
- date={selectedDate}
100
- dateType={dateType}
101
- extension={extension}
102
- hideExtensionTab={hideExtension}
103
- showExtensionTab={showExtension}
104
- />
105
- );
106
- }}
107
- </ExtensionSlot>
108
- </>
109
- );
110
- };
111
-
112
- function useAllowedExtensions() {
113
- const [allowedExtensions, dispatch] = useReducer(
114
- (state: Record<string, boolean>, action: { type: 'show_extension' | 'hide_extension'; extension: string }) => {
115
- let addedState = {} as Record<string, boolean>;
116
- switch (action.type) {
117
- case 'show_extension':
118
- addedState[action.extension] = true;
119
- break;
120
- case 'hide_extension':
121
- addedState[action.extension] = false;
122
- break;
123
- }
124
-
125
- return { ...state, ...addedState };
126
- },
127
- {},
128
- );
129
-
130
- return {
131
- allowedExtensions: allowedExtensions as Readonly<Record<string, boolean>>,
132
- showExtension: (extension: string) => dispatch({ type: 'show_extension', extension }),
133
- hideExtension: (extension: string) => dispatch({ type: 'hide_extension', extension }),
134
- };
135
- }
136
-
137
- function ExtensionWrapper({
138
- extension,
139
- currentTab,
140
- appointmentServiceTypes,
141
- date,
142
- dateType,
143
- showExtensionTab,
144
- hideExtensionTab,
145
- }: {
146
- extension: ConnectedExtension;
147
- currentTab: string;
148
- appointmentServiceTypes: Array<string>;
149
- date: string;
150
- dateType: DateType;
151
- showExtensionTab: (extension: string) => void;
152
- hideExtensionTab: (extension: string) => void;
153
- }) {
154
- const currentConfig = useRef(null);
155
- const currentDateType = useRef(dateType);
156
-
157
- // This use effect hook controls whether the tab for this extension should render
158
- useEffect(() => {
159
- if (
160
- currentConfig.current === null ||
161
- (currentConfig.current !== null && !shallowEqual(currentConfig.current, extension.config)) ||
162
- currentDateType.current !== dateType
163
- ) {
164
- currentConfig.current = extension.config;
165
- currentDateType.current = dateType;
166
- if (shouldDisplayExtensionTab(extension?.config as AppointmentPanelConfig, dateType)) {
167
- showExtensionTab(extension.name);
168
- } else {
169
- hideExtensionTab(extension.name);
170
- }
171
- }
172
- }, [extension, dateType, showExtensionTab, hideExtensionTab]);
173
-
174
- if (extension.config == null) {
175
- return null;
176
- }
177
-
178
- return (
179
- <div
180
- key={extension.name}
181
- className={styles.container}
182
- style={{ display: currentTab === extension.name ? 'block' : 'none' }}>
183
- <Extension
184
- state={{
185
- date,
186
- appointmentServiceTypes,
187
- status: extension.config?.status,
188
- title: extension.config?.title,
189
- }}
35
+ <div className={styles.container}>
36
+ <AppointmentsTable
37
+ appointments={appointmentForSelectedDateFilteredByServiceTypes}
38
+ isLoading={isLoading}
39
+ tableHeading={t('appointmentsForDate', 'Appointments for: {{date}}', { date: formattedDate })}
190
40
  />
191
41
  </div>
192
42
  );
193
- }
194
-
195
- function shouldDisplayExtensionTab(config: AppointmentPanelConfig | undefined, dateType: DateType): boolean {
196
- if (!config) {
197
- return false;
198
- }
199
-
200
- switch (dateType) {
201
- case 'futureDate':
202
- return config.showForFutureDate ?? false;
203
- case 'pastDate':
204
- return config.showForPastDate ?? false;
205
- case 'today':
206
- return config.showForToday ?? false;
207
- }
208
- }
209
-
210
- function shallowEqual(objA: object, objB: object) {
211
- if (Object.is(objA, objB)) {
212
- return true;
213
- }
214
-
215
- if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) {
216
- return false;
217
- }
218
-
219
- const objAKeys = Object.getOwnPropertyNames(objA);
220
- const objBKeys = Object.getOwnPropertyNames(objB);
221
-
222
- return objAKeys.length === objBKeys.length && objAKeys.every((key) => objA[key] === objB[key]);
223
- }
43
+ };
224
44
 
225
45
  export default ScheduledAppointments;
@@ -1,5 +1,5 @@
1
1
  import { useMemo } from 'react';
2
- import { type Appointment } from '../types';
2
+ import { type AppointmentStatus, type Appointment } from '../types';
3
3
 
4
4
  /**
5
5
  * Returns an array of page sizes for the given data and page size.
@@ -46,31 +46,44 @@ export function useSearchResults<T>(data: T[], searchString: string): T[] {
46
46
  }
47
47
 
48
48
  /**
49
- * Accepts an array of Appointment and a searchString
50
- * Returns those Appointments that match the search string based on the following:
49
+ * Performs client-side filtering of appointments based on the selected
50
+ * statuses and the search string.
51
+ * The search string matches on the following:
51
52
  * case-insensitive partial match on patient name
52
53
  * case-insensitive partial match on the primary patient identifier
53
54
  * case-insensitive exact match on any of the patient identifiers
54
55
  * @param {Appointment[]} data - The array of data to filter
55
56
  * @param {string} searchString - The string to search for in the data
57
+ * @param {Array<AppointmentStatus>} selectedAppointmentStatuses - The array of selected appointment statuses to filter by
56
58
  * @returns {Appointment[]} The filtered array of data
57
59
  */
58
- export function useAppointmentSearchResults(data: Appointment[], searchString: string): Appointment[] {
59
- return useMemo(() => {
60
- if (searchString && searchString.trim() !== '') {
61
- const lowerCaseSearch = searchString.toLowerCase();
62
- return data.filter((appointment) => {
63
- if (appointment.patient.name?.toLowerCase()?.includes(lowerCaseSearch)) {
64
- return true;
60
+ export function useAppointmentSearchResults(
61
+ data: Appointment[],
62
+ searchString: string,
63
+ selectedAppointmentStatuses: Array<AppointmentStatus>,
64
+ ): Appointment[] {
65
+ return useMemo(
66
+ () =>
67
+ data.filter((appointment) => {
68
+ if (selectedAppointmentStatuses.length > 0 && !selectedAppointmentStatuses.includes(appointment.status)) {
69
+ return false;
65
70
  }
66
- if (appointment.patient.identifier?.toLowerCase()?.includes(lowerCaseSearch)) {
71
+
72
+ if (searchString && searchString.trim() !== '') {
73
+ const lowerCaseSearch = searchString.toLowerCase();
74
+ if (appointment.patient.name?.toLowerCase()?.includes(lowerCaseSearch)) {
75
+ return true;
76
+ }
77
+ if (appointment.patient.identifier?.toLowerCase()?.includes(lowerCaseSearch)) {
78
+ return true;
79
+ }
80
+ return false;
81
+ } else {
67
82
  return true;
68
83
  }
69
- return false;
70
- });
71
- }
72
- return data;
73
- }, [searchString, data]);
84
+ }),
85
+ [searchString, data, selectedAppointmentStatuses],
86
+ );
74
87
  }
75
88
 
76
89
  export function filterByServiceType(appointmentList: Array<Appointment>, appointmentServiceTypes: Array<string>) {
@@ -1,36 +1,28 @@
1
1
  import React, { useEffect } from 'react';
2
- import dayjs from 'dayjs';
3
2
  import { useTranslation } from 'react-i18next';
4
3
  import { useParams } from 'react-router-dom';
5
- import { omrsDateFormat } from './constants';
6
4
  import AppointmentTabs from './appointments/appointment-tabs.component';
7
5
  import AppointmentsHeader from './header/appointments-header.component';
8
6
  import AppointmentMetrics from './metrics/metrics-container.component';
9
- import { useAppointmentsStore, setAppointmentServiceTypes, setSelectedDate } from './store';
7
+ import { useAppointmentsStore } from './store';
10
8
 
11
9
  const Appointments: React.FC = () => {
12
10
  const { t } = useTranslation();
13
- const { appointmentServiceTypes } = useAppointmentsStore();
11
+ const { setAppointmentServiceTypes } = useAppointmentsStore();
14
12
 
15
13
  const params = useParams();
16
14
 
17
- useEffect(() => {
18
- if (params.date) {
19
- setSelectedDate(dayjs(params.date).startOf('day').format(omrsDateFormat));
20
- }
21
- }, [params.date]);
22
-
23
15
  useEffect(() => {
24
16
  if (params.serviceType) {
25
17
  setAppointmentServiceTypes([params.serviceType]);
26
18
  }
27
- }, [params.serviceType]);
19
+ }, [params.serviceType, setAppointmentServiceTypes]);
28
20
 
29
21
  return (
30
22
  <>
31
23
  <AppointmentsHeader title={t('appointments', 'Appointments')} showServiceTypeFilter />
32
- <AppointmentMetrics appointmentServiceTypes={appointmentServiceTypes} />
33
- <AppointmentTabs appointmentServiceTypes={appointmentServiceTypes} />
24
+ <AppointmentMetrics />
25
+ <AppointmentTabs />
34
26
  </>
35
27
  );
36
28
  };
@@ -1,13 +1,25 @@
1
1
  import React from 'react';
2
2
  import { render, screen } from '@testing-library/react';
3
+ import { useConfig, getDefaultsFromConfigSchema } from '@openmrs/esm-framework';
3
4
  import Appointments from './appointments.component';
5
+ import { type ConfigObject, configSchema } from './config-schema';
6
+ import { BrowserRouter } from 'react-router-dom';
7
+
8
+ const mockUseConfig = jest.mocked(useConfig<ConfigObject>);
4
9
 
5
- // TODO: Tweak the ExtensionSlot stub in the framework to not return a function. Functions are not valid React children.
6
10
  describe('Appointments', () => {
7
- it('renders the appointments dashboard', async () => {
8
- render(<Appointments />);
11
+ beforeEach(() => {
12
+ mockUseConfig.mockReturnValue({
13
+ ...getDefaultsFromConfigSchema(configSchema),
14
+ });
15
+ });
9
16
 
10
- await screen.findByRole('combobox');
17
+ it('renders the appointments dashboard', async () => {
18
+ render(
19
+ <BrowserRouter>
20
+ <Appointments />
21
+ </BrowserRouter>,
22
+ );
11
23
 
12
24
  expect(screen.getByRole('button', { name: /appointments calendar/i })).toBeInTheDocument();
13
25
  expect(screen.getByText(/filter appointments by service type/i)).toBeInTheDocument();
@@ -1,27 +1,18 @@
1
- import React, { useEffect } from 'react';
1
+ import React from 'react';
2
2
  import dayjs from 'dayjs';
3
3
  import { useParams } from 'react-router-dom';
4
4
  import { useTranslation } from 'react-i18next';
5
- import { omrsDateFormat } from '../constants';
6
5
  import { useAppointmentsCalendar } from '../hooks/useAppointmentsCalendar';
7
6
  import AppointmentsHeader from '../header/appointments-header.component';
8
7
  import CalendarHeader from './header/calendar-header.component';
9
8
  import MonthlyCalendarView from './monthly/monthly-calendar-view.component';
10
- import { useAppointmentsStore, setSelectedDate } from '../store';
9
+ import { useSelectedDate } from '../hooks/useSelectedDate';
11
10
 
12
11
  const AppointmentsCalendarView: React.FC = () => {
13
12
  const { t } = useTranslation();
14
- const { selectedDate } = useAppointmentsStore();
13
+ const selectedDate = useSelectedDate();
15
14
  const { calendarEvents } = useAppointmentsCalendar(dayjs(selectedDate).toISOString(), 'monthly');
16
15
 
17
- let params = useParams();
18
-
19
- useEffect(() => {
20
- if (params.date) {
21
- setSelectedDate(dayjs(params.date).startOf('day').format(omrsDateFormat));
22
- }
23
- }, [params.date]);
24
-
25
16
  return (
26
17
  <div data-testid="appointments-calendar">
27
18
  <AppointmentsHeader title={t('calendar', 'Calendar')} />
@@ -1,10 +1,15 @@
1
1
  import React from 'react';
2
2
  import { render, screen } from '@testing-library/react';
3
3
  import AppointmentsCalendarView from './appointments-calendar-view.component';
4
+ import { BrowserRouter } from 'react-router-dom';
4
5
 
5
6
  describe('Appointment calendar view', () => {
6
7
  it('renders appointments in calendar view from appointments list', async () => {
7
- render(<AppointmentsCalendarView />);
8
+ render(
9
+ <BrowserRouter>
10
+ <AppointmentsCalendarView />
11
+ </BrowserRouter>,
12
+ );
8
13
 
9
14
  const expectedTableRows = [
10
15
  /John Wilson 30-Aug-2021 03:35 03:35 Dr James Cook Outpatient Walk in appointments/,
@@ -5,12 +5,12 @@ import { Button } from '@carbon/react';
5
5
  import { ArrowLeft } from '@carbon/react/icons';
6
6
  import { navigate } from '@openmrs/esm-framework';
7
7
  import { spaHomePage } from '../../constants';
8
- import { useAppointmentsStore } from '../../store';
8
+ import { useSelectedDate } from '../../hooks/useSelectedDate';
9
9
  import styles from './calendar-header.scss';
10
10
 
11
11
  const CalendarHeader: React.FC = () => {
12
12
  const { t } = useTranslation();
13
- const { selectedDate } = useAppointmentsStore();
13
+ const selectedDate = useSelectedDate();
14
14
 
15
15
  const handleClick = () => {
16
16
  navigate({ to: `${spaHomePage}/appointments/${dayjs(selectedDate).format('YYYY-MM-DD')}` });
@@ -2,10 +2,10 @@ import React from 'react';
2
2
  import dayjs from 'dayjs';
3
3
  import isBetween from 'dayjs/plugin/isBetween';
4
4
  import { type DailyAppointmentsCountByService } from '../../types';
5
- import { useAppointmentsStore } from '../../store';
6
5
  import { monthDays } from '../../helpers';
7
6
  import MonthlyViewWorkload from './monthly-workload-view.component';
8
7
  import MonthlyHeader from './monthly-header.component';
8
+ import { useSelectedDate } from '../../hooks/useSelectedDate';
9
9
  import styles from '../appointments-calendar-view-view.scss';
10
10
 
11
11
  dayjs.extend(isBetween);
@@ -15,7 +15,7 @@ interface MonthlyCalendarViewProps {
15
15
  }
16
16
 
17
17
  const MonthlyCalendarView: React.FC<MonthlyCalendarViewProps> = ({ events }) => {
18
- const { selectedDate } = useAppointmentsStore();
18
+ const selectedDate = useSelectedDate();
19
19
 
20
20
  return (
21
21
  <div className={styles.calendarViewContainer}>
@@ -1,26 +1,27 @@
1
- import React, { useCallback } from 'react';
1
+ import React, { useCallback, useState } from 'react';
2
2
  import dayjs from 'dayjs';
3
3
  import { useTranslation } from 'react-i18next';
4
4
  import { Button } from '@carbon/react';
5
5
  import { formatDate } from '@openmrs/esm-framework';
6
- import { omrsDateFormat } from '../../constants';
7
- import { useAppointmentsStore, setSelectedDate } from '../../store';
8
6
  import DaysOfWeekCard from './days-of-week.component';
9
7
  import styles from './monthly-header.scss';
8
+ import { useSelectedDate } from '../../hooks/useSelectedDate';
10
9
 
11
10
  const DAYS_IN_WEEK = ['SUN', 'MON', 'TUE', 'WED', 'THUR', 'FRI', 'SAT'];
12
11
 
13
12
  const MonthlyHeader: React.FC = () => {
14
13
  const { t } = useTranslation();
15
- const { selectedDate } = useAppointmentsStore();
14
+ const selectedDate = useSelectedDate();
15
+
16
+ const [calendarSelectedDate, setCalendarSelectedDate] = useState(dayjs(selectedDate));
16
17
 
17
18
  const handleSelectPrevMonth = useCallback(() => {
18
- setSelectedDate(dayjs(selectedDate).subtract(1, 'month').format(omrsDateFormat));
19
- }, [selectedDate]);
19
+ setCalendarSelectedDate(calendarSelectedDate.subtract(1, 'month'));
20
+ }, [calendarSelectedDate, setCalendarSelectedDate]);
20
21
 
21
22
  const handleSelectNextMonth = useCallback(() => {
22
- setSelectedDate(dayjs(selectedDate).add(1, 'month').format(omrsDateFormat));
23
- }, [selectedDate]);
23
+ setCalendarSelectedDate(calendarSelectedDate.add(1, 'month'));
24
+ }, [calendarSelectedDate, setCalendarSelectedDate]);
24
25
 
25
26
  return (
26
27
  <>
@@ -6,8 +6,8 @@ import { navigate, useLayoutType } from '@openmrs/esm-framework';
6
6
  import { spaHomePage } from '../../constants';
7
7
  import { isSameMonth } from '../../helpers';
8
8
  import { type DailyAppointmentsCountByService } from '../../types';
9
- import { useAppointmentsStore } from '../../store';
10
9
  import MonthlyWorkloadViewExpanded from './monthly-workload-view-expanded.component';
10
+ import { useSelectedDate } from '../../hooks/useSelectedDate';
11
11
  import styles from './monthly-view-workload.scss';
12
12
 
13
13
  export interface MonthlyWorkloadViewProps {
@@ -18,7 +18,7 @@ export interface MonthlyWorkloadViewProps {
18
18
 
19
19
  const MonthlyWorkloadView: React.FC<MonthlyWorkloadViewProps> = ({ dateTime, events, showAllServices = false }) => {
20
20
  const layout = useLayoutType();
21
- const { selectedDate } = useAppointmentsStore();
21
+ const selectedDate = useSelectedDate();
22
22
 
23
23
  const currentData = useMemo(
24
24
  () =>
@@ -1,8 +1,6 @@
1
1
  import { Type, validators } from '@openmrs/esm-framework';
2
2
  import { appointmentColumnTypes } from './constants';
3
3
 
4
- type AppointmentColumnType = (typeof appointmentColumnTypes)[number];
5
-
6
4
  export const configSchema = {
7
5
  allowAllDayAppointments: {
8
6
  _type: Type.Boolean,
@@ -33,11 +31,6 @@ export const configSchema = {
33
31
  _default: true,
34
32
  _description: 'Whether the check-in button on the "Appointments" list should be enabled',
35
33
  },
36
- showIfActiveVisit: {
37
- _type: Type.Boolean,
38
- _default: false,
39
- _description: 'Whether to show the check-in button if the patient currently has an active visit',
40
- },
41
34
  customUrl: {
42
35
  _type: Type.String,
43
36
  _default: '',
@@ -80,11 +73,26 @@ export const configSchema = {
80
73
  _description:
81
74
  'Whether to show the Unscheduled Appointments tab. Note that configuring this to true requires a custom unscheduledAppointment endpoint not currently available',
82
75
  },
76
+ showEarlyAppointmentsTab: {
77
+ _type: Type.Boolean,
78
+ _default: false,
79
+ _description:
80
+ 'Whether to show the Early Appointments tab. Note that configuring this to true requires a custom earlyAppointment endpoint not currently available',
81
+ },
83
82
  appointmentsTableColumns: {
84
83
  _type: Type.Array,
85
84
  _description:
86
85
  'Columns to display in the appointment table. Available options: ' + appointmentColumnTypes.join(', '),
87
- _default: ['patientName', 'identifier', 'location', 'serviceType', 'status'],
86
+ _default: [
87
+ 'patientName',
88
+ 'identifier',
89
+ 'location',
90
+ 'serviceType',
91
+ 'dateTime',
92
+ 'visitStartTime',
93
+ 'status',
94
+ 'actions',
95
+ ],
88
96
  _elements: {
89
97
  _type: Type.String,
90
98
  _validators: [validators.oneOf(appointmentColumnTypes)],
@@ -99,7 +107,6 @@ export interface ConfigObject {
99
107
  appointmentsTableColumns: Array<string>;
100
108
  checkInButton: {
101
109
  enabled: boolean;
102
- showIfActiveVisit: boolean;
103
110
  customUrl: string;
104
111
  };
105
112
  checkOutButton: {
@@ -110,4 +117,5 @@ export interface ConfigObject {
110
117
  includePhoneNumberInExcelSpreadsheet: boolean;
111
118
  patientIdentifierType: string;
112
119
  showUnscheduledAppointmentsTab: boolean;
120
+ showEarlyAppointmentsTab: boolean;
113
121
  }
package/src/constants.ts CHANGED
@@ -65,8 +65,19 @@ export const appointmentColumnTypes = [
65
65
  'serviceType',
66
66
  // t('status', 'Status')
67
67
  'status',
68
- // t('dateTime', 'Date & time')
68
+ // t('dateTime', 'Appointment time')
69
69
  'dateTime',
70
70
  // t('provider', 'Provider')
71
71
  'provider',
72
+ // t('actions', 'Actions')
73
+ 'actions',
74
+ // t('visitStartTime', 'Visit start time')
75
+ 'visitStartTime',
72
76
  ] as const;
77
+
78
+ // added to prevent auto-removal of translations for dynamic keys
79
+ // t('Scheduled', 'Scheduled')
80
+ // t('CheckedIn', 'Checked in')
81
+ // t('Cancelled', 'Cancelled')
82
+ // t('Missed', 'Missed')
83
+ // t('Completed', 'Completed')