@openmrs/esm-fast-data-entry-app 1.0.1-pre.17 → 1.0.1-pre.176

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 (176) hide show
  1. package/README.md +21 -2
  2. package/__mocks__/react-i18next.js +9 -14
  3. package/dist/101.js +1 -0
  4. package/dist/101.js.map +1 -0
  5. package/dist/132.js +1 -1
  6. package/dist/143.js +1 -0
  7. package/dist/143.js.map +1 -0
  8. package/dist/188.js +1 -0
  9. package/dist/188.js.map +1 -0
  10. package/dist/197.js +1 -0
  11. package/dist/219.js +1 -0
  12. package/dist/219.js.map +1 -0
  13. package/dist/221.js +1 -0
  14. package/dist/221.js.map +1 -0
  15. package/dist/259.js +1 -0
  16. package/dist/259.js.map +1 -0
  17. package/dist/29.js +2 -0
  18. package/dist/29.js.LICENSE.txt +3 -0
  19. package/dist/29.js.map +1 -0
  20. package/dist/300.js +1 -0
  21. package/dist/31.js +2 -0
  22. package/dist/{569.js.LICENSE.txt → 31.js.LICENSE.txt} +9 -6
  23. package/dist/31.js.map +1 -0
  24. package/dist/326.js +1 -0
  25. package/dist/326.js.map +1 -0
  26. package/dist/335.js +1 -0
  27. package/dist/367.js +1 -0
  28. package/dist/367.js.map +1 -0
  29. package/dist/480.js +1 -0
  30. package/dist/491.js +1 -0
  31. package/dist/491.js.map +1 -0
  32. package/dist/540.js +2 -0
  33. package/dist/540.js.map +1 -0
  34. package/dist/55.js +1 -0
  35. package/dist/564.js +1 -0
  36. package/dist/564.js.map +1 -0
  37. package/dist/602.js +1 -0
  38. package/dist/602.js.map +1 -0
  39. package/dist/626.js +2 -0
  40. package/dist/626.js.LICENSE.txt +9 -0
  41. package/dist/626.js.map +1 -0
  42. package/dist/652.js +1 -0
  43. package/dist/685.js +1 -0
  44. package/dist/685.js.map +1 -0
  45. package/dist/773.js +2 -0
  46. package/dist/{68.js.LICENSE.txt → 773.js.LICENSE.txt} +13 -2
  47. package/dist/773.js.map +1 -0
  48. package/dist/91.js +1 -0
  49. package/dist/91.js.map +1 -0
  50. package/dist/961.js +2 -0
  51. package/dist/961.js.map +1 -0
  52. package/dist/99.js +1 -0
  53. package/dist/99.js.map +1 -0
  54. package/dist/main.js +1 -1
  55. package/dist/main.js.map +1 -0
  56. package/dist/openmrs-esm-fast-data-entry-app.js +1 -1
  57. package/dist/openmrs-esm-fast-data-entry-app.js.buildmanifest.json +403 -136
  58. package/dist/openmrs-esm-fast-data-entry-app.js.map +1 -0
  59. package/dist/routes.json +1 -0
  60. package/jest.config.json +2 -1
  61. package/package.json +41 -38
  62. package/prettier.config.js +8 -0
  63. package/src/CancelModal.tsx +42 -0
  64. package/src/CompleteModal.tsx +35 -0
  65. package/src/FormBootstrap.tsx +39 -11
  66. package/src/Root.tsx +7 -12
  67. package/src/add-group-modal/AddGroupModal.tsx +107 -120
  68. package/src/add-group-modal/styles.scss +7 -3
  69. package/src/config-schema.ts +77 -16
  70. package/src/constant.ts +1 -1
  71. package/src/context/FormWorkflowContext.tsx +31 -32
  72. package/src/context/FormWorkflowReducer.ts +53 -67
  73. package/src/context/GroupFormWorkflowContext.tsx +56 -44
  74. package/src/context/GroupFormWorkflowReducer.ts +177 -68
  75. package/src/declarations.d.ts +4 -0
  76. package/src/empty-state/EmptyDataIllustration.tsx +4 -16
  77. package/src/empty-state/EmptyState.tsx +8 -13
  78. package/src/empty-state/styles.scss +14 -14
  79. package/src/form-entry-workflow/FormEntryWorkflow.tsx +78 -124
  80. package/src/form-entry-workflow/form-review-card/FormReviewCard.tsx +7 -7
  81. package/src/form-entry-workflow/form-review-card/index.ts +1 -1
  82. package/src/form-entry-workflow/form-review-card/styles.scss +9 -11
  83. package/src/form-entry-workflow/index.ts +1 -1
  84. package/src/form-entry-workflow/patient-banner/PatientBanner.test.tsx +5 -5
  85. package/src/form-entry-workflow/patient-banner/PatientBanner.tsx +14 -27
  86. package/src/form-entry-workflow/patient-banner/index.ts +1 -1
  87. package/src/form-entry-workflow/patient-banner/styles.scss +11 -12
  88. package/src/form-entry-workflow/patient-search-header/PatientSearchHeader.tsx +19 -28
  89. package/src/form-entry-workflow/patient-search-header/index.ts +1 -1
  90. package/src/form-entry-workflow/patient-search-header/styles.scss +13 -10
  91. package/src/form-entry-workflow/styles.scss +13 -14
  92. package/src/form-entry-workflow/workflow-review/WorkflowReview.tsx +13 -11
  93. package/src/form-entry-workflow/workflow-review/index.ts +1 -1
  94. package/src/form-entry-workflow/workflow-review/styles.scss +0 -4
  95. package/src/forms-app-menu-link.tsx +4 -6
  96. package/src/forms-page/FormsPage.tsx +24 -47
  97. package/src/forms-page/forms-table/FormsTable.tsx +33 -47
  98. package/src/forms-page/forms-table/index.ts +1 -1
  99. package/src/forms-page/forms-table/styles.scss +4 -5
  100. package/src/forms-page/index.ts +1 -1
  101. package/src/forms-page/styles.scss +3 -5
  102. package/src/group-form-entry-workflow/GroupFormEntryWorkflow.tsx +15 -402
  103. package/src/group-form-entry-workflow/GroupSessionWorkspace.tsx +207 -0
  104. package/src/group-form-entry-workflow/SessionDetailsForm.tsx +154 -0
  105. package/src/group-form-entry-workflow/SessionMetaWorkspace.tsx +99 -0
  106. package/src/group-form-entry-workflow/attendance-table/AttendanceTable.tsx +130 -0
  107. package/src/group-form-entry-workflow/attendance-table/index.ts +1 -0
  108. package/src/group-form-entry-workflow/configurable-questions/ConfigurableQuestionsSection.tsx +41 -0
  109. package/src/group-form-entry-workflow/group-display-header/GroupDisplayHeader.test.tsx +5 -5
  110. package/src/group-form-entry-workflow/group-display-header/GroupDisplayHeader.tsx +14 -30
  111. package/src/group-form-entry-workflow/group-display-header/index.ts +1 -1
  112. package/src/group-form-entry-workflow/group-display-header/styles.scss +20 -20
  113. package/src/group-form-entry-workflow/group-search/CompactGroupResults.tsx +24 -35
  114. package/src/group-form-entry-workflow/group-search/CompactGroupSearch.tsx +13 -15
  115. package/src/group-form-entry-workflow/group-search/GroupSearch.tsx +22 -38
  116. package/src/group-form-entry-workflow/group-search/compact-group-result.scss +16 -17
  117. package/src/group-form-entry-workflow/group-search/compact-group-search.scss +7 -8
  118. package/src/group-form-entry-workflow/group-search/group-search.scss +20 -23
  119. package/src/group-form-entry-workflow/group-search-header/GroupSearchHeader.tsx +41 -18
  120. package/src/group-form-entry-workflow/group-search-header/index.ts +1 -1
  121. package/src/group-form-entry-workflow/group-search-header/styles.scss +8 -8
  122. package/src/group-form-entry-workflow/index.ts +1 -1
  123. package/src/group-form-entry-workflow/styles.scss +15 -17
  124. package/src/hooks/index.ts +7 -6
  125. package/src/hooks/useForm.ts +56 -0
  126. package/src/hooks/useFormState.ts +3 -3
  127. package/src/hooks/useGetAllForms.ts +7 -15
  128. package/src/hooks/useGetEncounter.ts +3 -3
  129. package/src/hooks/useGetPatient.ts +3 -3
  130. package/src/hooks/useGetPatients.ts +32 -0
  131. package/src/hooks/useGetSystemSetting.ts +36 -0
  132. package/src/hooks/useKeyPress.ts +5 -5
  133. package/src/hooks/usePostEndpoint.ts +16 -10
  134. package/src/hooks/useSearchEndpoint.ts +23 -40
  135. package/src/hooks/useStartVisit.ts +82 -0
  136. package/src/index.ts +12 -76
  137. package/src/patient-card/PatientCard.tsx +8 -20
  138. package/src/patient-card/index.ts +1 -1
  139. package/src/patient-card/styles.scss +3 -4
  140. package/src/routes.json +24 -0
  141. package/src/setup-tests.ts +1 -1
  142. package/src/types.ts +20 -0
  143. package/tools/i18next-parser.config.js +93 -0
  144. package/translations/am.json +75 -0
  145. package/translations/ar.json +75 -0
  146. package/translations/en.json +32 -11
  147. package/translations/es.json +75 -0
  148. package/translations/fr.json +75 -0
  149. package/translations/he.json +75 -0
  150. package/translations/km.json +75 -0
  151. package/turbo.json +18 -0
  152. package/webpack.config.js +1 -1
  153. package/dist/247.js +0 -1
  154. package/dist/255.js +0 -1
  155. package/dist/294.js +0 -2
  156. package/dist/32.js +0 -1
  157. package/dist/327.js +0 -1
  158. package/dist/403.js +0 -2
  159. package/dist/403.js.LICENSE.txt +0 -14
  160. package/dist/553.js +0 -2
  161. package/dist/553.js.LICENSE.txt +0 -14
  162. package/dist/569.js +0 -2
  163. package/dist/574.js +0 -1
  164. package/dist/595.js +0 -2
  165. package/dist/595.js.LICENSE.txt +0 -1
  166. package/dist/617.js +0 -1
  167. package/dist/68.js +0 -2
  168. package/dist/776.js +0 -1
  169. package/dist/804.js +0 -1
  170. package/dist/820.js +0 -1
  171. package/dist/906.js +0 -1
  172. package/dist/935.js +0 -2
  173. package/dist/openmrs-esm-fast-data-entry-app.old +0 -1
  174. package/src/declarations.d.tsx +0 -2
  175. /package/dist/{294.js.LICENSE.txt → 540.js.LICENSE.txt} +0 -0
  176. /package/dist/{935.js.LICENSE.txt → 961.js.LICENSE.txt} +0 -0
@@ -1,413 +1,26 @@
1
- import {
2
- ExtensionSlot,
3
- getGlobalStore,
4
- useStore,
5
- } from "@openmrs/esm-framework";
6
- import {
7
- Button,
8
- ComposedModal,
9
- ModalBody,
10
- ModalFooter,
11
- ModalHeader,
12
- Layer,
13
- Tile,
14
- TextInput,
15
- TextArea,
16
- DatePicker,
17
- DatePickerInput,
18
- } from "@carbon/react";
19
- import React, { useContext, useEffect, useState } from "react";
20
- import { useNavigate } from "react-router-dom";
21
- import PatientCard from "../patient-card/PatientCard";
22
- import GroupDisplayHeader from "./group-display-header";
23
- import styles from "./styles.scss";
24
- import { useTranslation } from "react-i18next";
25
- import GroupFormWorkflowContext, {
26
- GroupFormWorkflowProvider,
27
- } from "../context/GroupFormWorkflowContext";
28
- import GroupSearchHeader from "./group-search-header";
29
- import {
30
- Controller,
31
- FormProvider,
32
- useForm,
33
- useFormContext,
34
- } from "react-hook-form";
35
- import FormBootstrap from "../FormBootstrap";
36
-
37
- const formStore = getGlobalStore("ampath-form-state");
38
-
39
- const CancelModal = ({ open, setOpen }) => {
40
- const { destroySession, closeSession } = useContext(GroupFormWorkflowContext);
41
- const { t } = useTranslation();
42
- const navigate = useNavigate();
43
-
44
- const discard = async () => {
45
- await destroySession();
46
- setOpen(false);
47
- navigate("../");
48
- };
49
-
50
- const saveAndClose = async () => {
51
- await closeSession();
52
- setOpen(false);
53
- navigate("../");
54
- };
55
-
56
- return (
57
- <ComposedModal open={open}>
58
- <ModalHeader>{t("areYouSure", "Are you sure?")}</ModalHeader>
59
- <ModalBody>
60
- {t(
61
- "cancelExplanation",
62
- "You will lose any unsaved changes on the current form. Do you want to discard the current session?"
63
- )}
64
- </ModalBody>
65
- <ModalFooter>
66
- <Button kind="secondary" onClick={() => setOpen(false)}>
67
- {t("cancel", "Cancel")}
68
- </Button>
69
- <Button kind="danger" onClick={discard}>
70
- {t("discard", "Discard")}
71
- </Button>
72
- <Button kind="primary" onClick={saveAndClose}>
73
- {t("saveSession", "Save Session")}
74
- </Button>
75
- </ModalFooter>
76
- </ComposedModal>
77
- );
78
- };
79
-
80
- const CompleteModal = ({ open, setOpen }) => {
81
- const { submitForComplete } = useContext(GroupFormWorkflowContext);
82
- const { t } = useTranslation();
83
-
84
- const completeSession = () => {
85
- submitForComplete();
86
- setOpen(false);
87
- };
88
-
89
- return (
90
- <ComposedModal open={open}>
91
- <ModalHeader>{t("areYouSure", "Are you sure?")}</ModalHeader>
92
- <ModalBody>
93
- {t(
94
- "saveExplanation",
95
- "Do you want to save the current form and exit the workflow?"
96
- )}
97
- </ModalBody>
98
- <ModalFooter>
99
- <Button kind="secondary" onClick={() => setOpen(false)}>
100
- {t("cancel", "Cancel")}
101
- </Button>
102
- <Button kind="primary" onClick={completeSession}>
103
- {t("complete", "Complete")}
104
- </Button>
105
- </ModalFooter>
106
- </ComposedModal>
107
- );
108
- };
109
-
110
- const NewGroupWorkflowButtons = () => {
111
- const { t } = useTranslation();
112
- const { workflowState } = useContext(GroupFormWorkflowContext);
113
- const [cancelModalOpen, setCancelModalOpen] = useState(false);
114
- if (workflowState !== "NEW_GROUP_SESSION") return null;
115
-
116
- return (
117
- <>
118
- <div className={styles.rightPanelActionButtons}>
119
- <Button kind="secondary" type="submit">
120
- {t("createNewSession", "Create New Session")}
121
- </Button>
122
- <Button
123
- kind="tertiary"
124
- onClick={() => {
125
- setCancelModalOpen(true);
126
- }}
127
- >
128
- {t("cancel", "Cancel")}
129
- </Button>
130
- </div>
131
- <CancelModal open={cancelModalOpen} setOpen={setCancelModalOpen} />
132
- </>
133
- );
134
- };
135
-
136
- const WorkflowNavigationButtons = () => {
137
- const { activeFormUuid, submitForNext, patientUuids, activePatientUuid } =
138
- useContext(GroupFormWorkflowContext);
139
- const store = useStore(formStore);
140
- const formState = store[activeFormUuid];
141
- const navigationDisabled = formState !== "ready";
142
- const [cancelModalOpen, setCancelModalOpen] = useState(false);
143
- const [completeModalOpen, setCompleteModalOpen] = useState(false);
144
- const { t } = useTranslation();
145
-
146
- const isLastPatient =
147
- activePatientUuid === patientUuids[patientUuids.length - 1];
148
-
149
- return (
150
- <>
151
- <div className={styles.rightPanelActionButtons}>
152
- <Button
153
- kind="primary"
154
- onClick={() => submitForNext()}
155
- disabled={navigationDisabled}
156
- >
157
- {isLastPatient
158
- ? t("saveForm", "Save Form")
159
- : t("nextPatient", "Next Patient")}
160
- </Button>
161
- <Button kind="secondary" onClick={() => setCompleteModalOpen(true)}>
162
- {t("saveAndComplete", "Save & Complete")}
163
- </Button>
164
- <Button kind="tertiary" onClick={() => setCancelModalOpen(true)}>
165
- {t("cancel", "Cancel")}
166
- </Button>
167
- </div>
168
- <CancelModal open={cancelModalOpen} setOpen={setCancelModalOpen} />
169
- <CompleteModal open={completeModalOpen} setOpen={setCompleteModalOpen} />
170
- </>
171
- );
172
- };
173
-
174
- const SessionDetails = () => {
175
- const { t } = useTranslation();
176
- const {
177
- register,
178
- formState: { errors },
179
- control,
180
- } = useFormContext();
181
-
182
- return (
183
- <div className={styles.formSection}>
184
- <h4>{t("sessionDetails", "Session details")}</h4>
185
- <div>
186
- <p>
187
- {t(
188
- "allFieldsRequired",
189
- "All fields are required unless marked optional"
190
- )}
191
- </p>
192
- </div>
193
- <Layer>
194
- <Tile className={styles.formSectionTile}>
195
- <Layer>
196
- <div
197
- style={{
198
- display: "flex",
199
- flexDirection: "column",
200
- rowGap: "1.5rem",
201
- }}
202
- >
203
- <TextInput
204
- id="text"
205
- type="text"
206
- labelText={t("sessionName", "Session Name")}
207
- {...register("sessionName", { required: true })}
208
- invalid={errors.sessionName}
209
- invalidText={"This field is required"}
210
- />
211
- <TextInput
212
- id="text"
213
- type="text"
214
- labelText={t("practitionerName", "Practitioner Name")}
215
- {...register("practitionerName", { required: true })}
216
- invalid={errors.practitionerName}
217
- invalidText={"This field is required"}
218
- />
219
- <Controller
220
- name="sessionDate"
221
- control={control}
222
- rules={{ required: true }}
223
- render={({ field }) => (
224
- <DatePicker
225
- datePickerType="single"
226
- size="md"
227
- maxDate={new Date()}
228
- {...field}
229
- >
230
- <DatePickerInput
231
- id="session-date"
232
- labelText={t("sessionDate", "Session Date")}
233
- placeholder="mm/dd/yyyy"
234
- size="md"
235
- invalid={errors.sessionDate}
236
- invalidText={"This field is required"}
237
- />
238
- </DatePicker>
239
- )}
240
- />
241
- <TextArea
242
- id="text"
243
- type="text"
244
- labelText={t("sessionNotes", "Session Notes")}
245
- {...register("sessionNotes", { required: true })}
246
- invalid={errors.sessionNotes}
247
- invalidText={"This field is required"}
248
- />
249
- </div>
250
- </Layer>
251
- </Tile>
252
- </Layer>
253
- </div>
254
- );
255
- };
256
-
257
- const GroupIdField = () => {
258
- const { t } = useTranslation();
259
- const {
260
- register,
261
- formState: { errors },
262
- setValue,
263
- } = useFormContext();
264
- const { activeGroupUuid } = useContext(GroupFormWorkflowContext);
265
-
266
- useEffect(() => {
267
- if (activeGroupUuid) setValue("groupUuid", activeGroupUuid);
268
- }, [activeGroupUuid, setValue]);
269
-
270
- return (
271
- <>
272
- <input
273
- hidden
274
- {...register("groupUuid", {
275
- value: activeGroupUuid,
276
- required: t("chooseGroupError", "Please choose a group."),
277
- })}
278
- />
279
- {errors.groupUuid && !activeGroupUuid && (
280
- <div className={styles.formError}>
281
- {errors.groupUuid.message as string}
282
- </div>
283
- )}
284
- </>
285
- );
286
- };
287
-
288
- const SessionMetaWorkspace = () => {
289
- const { t } = useTranslation();
290
- const { setSessionMeta } = useContext(GroupFormWorkflowContext);
291
- const methods = useForm();
292
-
293
- const onSubmit = (data) => {
294
- const { sessionDate, ...rest } = data;
295
- setSessionMeta({ ...rest, sessionDate: sessionDate[0] });
296
- };
297
-
298
- return (
299
- <FormProvider {...methods}>
300
- <form onSubmit={methods.handleSubmit(onSubmit)}>
301
- <div className={styles.workspace}>
302
- <div className={styles.formMainContent}>
303
- <div className={styles.formContainer}>
304
- <SessionDetails />
305
- </div>
306
- <div className={styles.rightPanel}>
307
- <h4>{t("newGroupSession", "New Group Session")}</h4>
308
- <GroupIdField />
309
- <hr style={{ width: "100%" }} />
310
- <NewGroupWorkflowButtons />
311
- </div>
312
- </div>
313
- </div>
314
- </form>
315
- </FormProvider>
316
- );
317
- };
318
-
319
- const GroupSessionWorkspace = () => {
320
- const { t } = useTranslation();
321
- const {
322
- patientUuids,
323
- activePatientUuid,
324
- editEncounter,
325
- encounters,
326
- activeEncounterUuid,
327
- activeFormUuid,
328
- saveEncounter,
329
- // activeSessionMeta,
330
- } = useContext(GroupFormWorkflowContext);
331
-
332
- // const handleEncounterCreate = (payload: Record<string, unknown>) => {
333
- // console.log("payload", payload);
334
- // Object.entries(activeSessionMeta).forEach((key, value) => {
335
- // payload[key as unknown as string] = value;
336
- // });
337
- // };
338
-
339
- const handlePostResponse = (encounter) => {
340
- if (encounter && encounter.uuid) {
341
- saveEncounter(encounter.uuid);
342
- }
343
- };
344
-
345
- return (
346
- <div className={styles.workspace}>
347
- <div className={styles.formMainContent}>
348
- <div className={styles.formContainer}>
349
- <FormBootstrap
350
- patientUuid={activePatientUuid}
351
- encounterUuid={activeEncounterUuid}
352
- {...{
353
- formUuid: activeFormUuid,
354
- handlePostResponse,
355
- // handleEncounterCreate,
356
- }}
357
- />
358
- </div>
359
- <div className={styles.rightPanel}>
360
- <h4>{t("formsFilled", "Forms filled")}</h4>
361
- <div className={styles.patientCardsSection}>
362
- {patientUuids?.map((patientUuid) => (
363
- <PatientCard
364
- key={patientUuid}
365
- {...{
366
- patientUuid,
367
- activePatientUuid,
368
- editEncounter,
369
- encounters,
370
- }}
371
- />
372
- ))}
373
- </div>
374
- <WorkflowNavigationButtons />
375
- </div>
376
- </div>
377
- </div>
378
- );
379
- };
1
+ import { ExtensionSlot } from '@openmrs/esm-framework';
2
+ import React from 'react';
3
+ import GroupDisplayHeader from './group-display-header';
4
+ import styles from './styles.scss';
5
+ import { GroupFormWorkflowProvider } from '../context/GroupFormWorkflowContext';
6
+ import GroupSearchHeader from './group-search-header';
7
+ import SessionMetaWorkspace from './SessionMetaWorkspace';
8
+ import GroupSessionWorkspace from './GroupSessionWorkspace';
380
9
 
381
10
  const GroupFormEntryWorkflow = () => {
382
- const { workflowState } = useContext(GroupFormWorkflowContext);
383
-
384
11
  return (
385
- <>
12
+ <GroupFormWorkflowProvider>
386
13
  <div className={styles.breadcrumbsContainer}>
387
- <ExtensionSlot extensionSlotName="breadcrumbs-slot" />
14
+ <ExtensionSlot name="breadcrumbs-slot" />
388
15
  </div>
389
16
  <GroupSearchHeader />
390
17
  <GroupDisplayHeader />
391
- {workflowState === "NEW_GROUP_SESSION" && (
392
- <div className={styles.workspaceWrapper}>
393
- <SessionMetaWorkspace />
394
- </div>
395
- )}
396
- {["EDIT_FORM"].includes(workflowState) && (
397
- <div className={styles.workspaceWrapper}>
398
- <GroupSessionWorkspace />
399
- </div>
400
- )}
401
- </>
402
- );
403
- };
404
-
405
- const GroupFormEntryWorkflowWrapper = () => {
406
- return (
407
- <GroupFormWorkflowProvider>
408
- <GroupFormEntryWorkflow />
18
+ <div className={styles.workspaceWrapper}>
19
+ <SessionMetaWorkspace />
20
+ <GroupSessionWorkspace />
21
+ </div>
409
22
  </GroupFormWorkflowProvider>
410
23
  );
411
24
  };
412
25
 
413
- export default GroupFormEntryWorkflowWrapper;
26
+ export default GroupFormEntryWorkflow;
@@ -0,0 +1,207 @@
1
+ import { getGlobalStore, useConfig, useSession, useStore } from '@openmrs/esm-framework';
2
+ import { Button } from '@carbon/react';
3
+ import React, { useCallback, useContext, useEffect, useState } from 'react';
4
+ import PatientCard from '../patient-card/PatientCard';
5
+ import styles from './styles.scss';
6
+ import { useTranslation } from 'react-i18next';
7
+ import { v4 as uuid } from 'uuid';
8
+ import GroupFormWorkflowContext from '../context/GroupFormWorkflowContext';
9
+ import FormBootstrap from '../FormBootstrap';
10
+ import CompleteModal from '../CompleteModal';
11
+ import CancelModal from '../CancelModal';
12
+
13
+ const formStore = getGlobalStore('ampath-form-state');
14
+
15
+ const WorkflowNavigationButtons = () => {
16
+ const context = useContext(GroupFormWorkflowContext);
17
+ const { activeFormUuid, submitForNext, patientUuids, activePatientUuid, workflowState } = context;
18
+ const store = useStore(formStore);
19
+ const formState = store[activeFormUuid];
20
+ const navigationDisabled =
21
+ (formState !== 'ready' || workflowState !== 'EDIT_FORM') && formState !== 'readyWithValidationErrors';
22
+ const [cancelModalOpen, setCancelModalOpen] = useState(false);
23
+ const [completeModalOpen, setCompleteModalOpen] = useState(false);
24
+ const { t } = useTranslation();
25
+
26
+ const isLastPatient = activePatientUuid === patientUuids[patientUuids.length - 1];
27
+
28
+ const handleClickNext = () => {
29
+ if (workflowState === 'EDIT_FORM' || formState === 'readyWithValidationErrors') {
30
+ submitForNext();
31
+ }
32
+ };
33
+
34
+ return (
35
+ <>
36
+ <div className={styles.rightPanelActionButtons}>
37
+ <Button kind="primary" onClick={handleClickNext} disabled={navigationDisabled}>
38
+ {isLastPatient ? t('saveForm', 'Save Form') : t('nextPatient', 'Next patient')}
39
+ </Button>
40
+ <Button kind="secondary" onClick={() => setCompleteModalOpen(true)}>
41
+ {t('saveAndComplete', 'Save & Complete')}
42
+ </Button>
43
+ <Button kind="tertiary" onClick={() => setCancelModalOpen(true)}>
44
+ {t('cancel', 'Cancel')}
45
+ </Button>
46
+ </div>
47
+ <CancelModal open={cancelModalOpen} setOpen={setCancelModalOpen} context={context} />
48
+ <CompleteModal open={completeModalOpen} setOpen={setCompleteModalOpen} context={context} validateFirst={false} />
49
+ </>
50
+ );
51
+ };
52
+
53
+ const GroupSessionWorkspace = () => {
54
+ const { groupSessionConcepts } = useConfig();
55
+ const { t } = useTranslation();
56
+ const {
57
+ patientUuids,
58
+ activePatientUuid,
59
+ encounters,
60
+ activeEncounterUuid,
61
+ activeVisitUuid,
62
+ activeFormUuid,
63
+ activeGroupUuid,
64
+ activeGroupName,
65
+ activeSessionUuid,
66
+ saveEncounter,
67
+ activeSessionMeta,
68
+ groupVisitTypeUuid,
69
+ updateVisitUuid,
70
+ submitForNext,
71
+ workflowState,
72
+ } = useContext(GroupFormWorkflowContext);
73
+
74
+ const { sessionLocation } = useSession();
75
+
76
+ useEffect(() => {
77
+ if (activeVisitUuid) {
78
+ updateVisitUuid(activeVisitUuid);
79
+ }
80
+ }, [updateVisitUuid, activeVisitUuid, activePatientUuid]);
81
+
82
+ // If there's no active visit, trigger the creation of a new one
83
+ const handleEncounterCreate = useCallback(
84
+ (payload) => {
85
+ // Create a visit with the same date as the encounter being saved
86
+ const obsTime = new Date(activeSessionMeta.sessionDate);
87
+ payload.obs.forEach((item, index) => {
88
+ payload.obs[index] = {
89
+ ...item,
90
+ groupMembers: item.groupMembers?.map((mem) => ({
91
+ ...mem,
92
+ obsDatetime: obsTime.toISOString(),
93
+ })),
94
+ obsDatetime: obsTime.toISOString(),
95
+ };
96
+ });
97
+ const visitUuid = activeVisitUuid ? activeVisitUuid : uuid();
98
+ if (!activeVisitUuid) {
99
+ Object.entries(groupSessionConcepts).forEach(([field, uuid]) => {
100
+ if (activeSessionMeta?.[field] != null) {
101
+ payload.obs.push({
102
+ concept: uuid,
103
+ value: activeSessionMeta[field],
104
+ });
105
+ }
106
+ });
107
+
108
+ const otherIdentifiers = [
109
+ { concept: groupSessionConcepts.cohortId, value: activeGroupUuid },
110
+ { concept: groupSessionConcepts.cohortName, value: activeGroupName },
111
+ {
112
+ concept: groupSessionConcepts.sessionUuid,
113
+ value: activeSessionUuid,
114
+ },
115
+ ];
116
+ payload.obs.push(...otherIdentifiers);
117
+ // If this is a newly created encounter and visit, add session concepts to encounter payload.
118
+ const visitInfo = {
119
+ startDatetime: activeSessionMeta.sessionDate,
120
+ stopDatetime: activeSessionMeta.sessionDate,
121
+ uuid: visitUuid,
122
+ patient: {
123
+ uuid: activePatientUuid,
124
+ },
125
+ location: {
126
+ uuid: sessionLocation?.uuid,
127
+ },
128
+ visitType: {
129
+ uuid: groupVisitTypeUuid,
130
+ },
131
+ };
132
+ payload.visit = visitInfo;
133
+ updateVisitUuid(visitUuid);
134
+ }
135
+ payload.location = sessionLocation?.uuid;
136
+ payload.encounterDatetime = obsTime.toISOString();
137
+ },
138
+ [
139
+ activeSessionMeta,
140
+ activeVisitUuid,
141
+ sessionLocation?.uuid,
142
+ groupSessionConcepts,
143
+ activeGroupUuid,
144
+ activeGroupName,
145
+ activeSessionUuid,
146
+ activePatientUuid,
147
+ groupVisitTypeUuid,
148
+ updateVisitUuid,
149
+ ],
150
+ );
151
+
152
+ // Once form has been posted, save the new encounter uuid so we can edit it later
153
+ const handlePostResponse = useCallback(
154
+ (encounter) => {
155
+ if (encounter && encounter.uuid) {
156
+ saveEncounter(encounter.uuid);
157
+ }
158
+ },
159
+ [saveEncounter],
160
+ );
161
+
162
+ const switchPatient = useCallback(
163
+ (patientUuid) => {
164
+ submitForNext(patientUuid);
165
+ },
166
+ [submitForNext],
167
+ );
168
+
169
+ if (workflowState === 'NEW_GROUP_SESSION') return null;
170
+
171
+ return (
172
+ <div className={styles.workspace}>
173
+ <div className={styles.formMainContent}>
174
+ <div className={styles.formContainer}>
175
+ <FormBootstrap
176
+ patientUuid={activePatientUuid}
177
+ encounterUuid={activeEncounterUuid}
178
+ {...{
179
+ formUuid: activeFormUuid,
180
+ handlePostResponse,
181
+ handleEncounterCreate,
182
+ }}
183
+ />
184
+ </div>
185
+ <div className={styles.rightPanel}>
186
+ <h4>{t('formsFilled', 'Forms filled')}</h4>
187
+ <div className={styles.patientCardsSection}>
188
+ {patientUuids?.map((patientUuid) => (
189
+ <PatientCard
190
+ key={patientUuid}
191
+ {...{
192
+ patientUuid,
193
+ activePatientUuid,
194
+ editEncounter: switchPatient,
195
+ encounters,
196
+ }}
197
+ />
198
+ ))}
199
+ </div>
200
+ <WorkflowNavigationButtons />
201
+ </div>
202
+ </div>
203
+ </div>
204
+ );
205
+ };
206
+
207
+ export default GroupSessionWorkspace;