@openmrs/esm-fast-data-entry-app 1.0.0-pre.9 → 1.0.1-pre.101

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 (186) hide show
  1. package/README.md +58 -12
  2. package/dist/153.js +1 -0
  3. package/dist/153.js.map +1 -0
  4. package/dist/233.js +2 -0
  5. package/dist/{382.js.LICENSE.txt → 233.js.LICENSE.txt} +3 -2
  6. package/dist/233.js.map +1 -0
  7. package/dist/262.js +1 -0
  8. package/dist/262.js.map +1 -0
  9. package/dist/279.js +1 -0
  10. package/dist/279.js.map +1 -0
  11. package/dist/294.js +1 -2
  12. package/dist/294.js.LICENSE.txt +2 -7
  13. package/dist/294.js.map +1 -1
  14. package/dist/327.js +1 -0
  15. package/dist/327.js.map +1 -0
  16. package/dist/409.js +2 -0
  17. package/dist/409.js.LICENSE.txt +27 -0
  18. package/dist/409.js.map +1 -0
  19. package/dist/415.js +1 -2
  20. package/dist/415.js.map +1 -1
  21. package/dist/559.js +1 -0
  22. package/dist/559.js.map +1 -0
  23. package/dist/574.js +1 -1
  24. package/dist/651.js +1 -0
  25. package/dist/651.js.map +1 -0
  26. package/dist/706.js +1 -0
  27. package/dist/706.js.map +1 -0
  28. package/dist/757.js +1 -0
  29. package/dist/800.js +2 -0
  30. package/dist/800.js.LICENSE.txt +5 -0
  31. package/dist/800.js.map +1 -0
  32. package/dist/820.js +1 -0
  33. package/dist/820.js.map +1 -0
  34. package/dist/883.js +1 -0
  35. package/dist/883.js.map +1 -0
  36. package/dist/889.js +1 -0
  37. package/dist/889.js.map +1 -0
  38. package/dist/897.js +2 -0
  39. package/dist/897.js.LICENSE.txt +21 -0
  40. package/dist/897.js.map +1 -0
  41. package/dist/92.js +1 -0
  42. package/dist/92.js.map +1 -0
  43. package/dist/935.js +2 -0
  44. package/dist/{735.js.LICENSE.txt → 935.js.LICENSE.txt} +6 -16
  45. package/dist/935.js.map +1 -0
  46. package/dist/959.js +1 -0
  47. package/dist/959.js.map +1 -0
  48. package/dist/main.js +1 -0
  49. package/dist/main.js.map +1 -0
  50. package/dist/openmrs-esm-fast-data-entry-app.js +1 -1
  51. package/dist/openmrs-esm-fast-data-entry-app.js.buildmanifest.json +374 -89
  52. package/dist/openmrs-esm-fast-data-entry-app.js.map +1 -1
  53. package/dist/routes.json +1 -0
  54. package/docs/config-icrc-forms.png +0 -0
  55. package/docs/config-other-forms.png +0 -0
  56. package/docs/configuring-form-categories.md +77 -0
  57. package/docs/fde-workflow.mov +0 -0
  58. package/docs/form-workflow-state-diagram.png +0 -0
  59. package/jest.config.json +21 -18
  60. package/package.json +100 -106
  61. package/src/CancelModal.tsx +48 -0
  62. package/src/CompleteModal.tsx +46 -0
  63. package/src/FormBootstrap.tsx +166 -0
  64. package/src/Root.tsx +14 -3
  65. package/src/add-group-modal/AddGroupModal.tsx +288 -0
  66. package/src/add-group-modal/styles.scss +45 -0
  67. package/src/config-schema.ts +85 -31
  68. package/src/context/FormWorkflowContext.tsx +126 -0
  69. package/src/context/FormWorkflowReducer.ts +287 -0
  70. package/src/context/GroupFormWorkflowContext.tsx +176 -0
  71. package/src/context/GroupFormWorkflowReducer.ts +430 -0
  72. package/src/empty-state/EmptyDataIllustration.tsx +51 -0
  73. package/src/empty-state/EmptyState.tsx +33 -0
  74. package/src/empty-state/styles.scss +55 -0
  75. package/src/form-entry-workflow/FormEntryWorkflow.tsx +196 -0
  76. package/src/form-entry-workflow/form-review-card/FormReviewCard.tsx +50 -0
  77. package/src/form-entry-workflow/form-review-card/index.ts +3 -0
  78. package/src/form-entry-workflow/form-review-card/styles.scss +39 -0
  79. package/src/form-entry-workflow/index.ts +3 -0
  80. package/src/form-entry-workflow/patient-banner/PatientBanner.test.tsx +9 -0
  81. package/src/form-entry-workflow/patient-banner/PatientBanner.tsx +86 -0
  82. package/src/form-entry-workflow/patient-banner/index.ts +3 -0
  83. package/src/form-entry-workflow/patient-banner/styles.scss +45 -0
  84. package/src/form-entry-workflow/patient-search-header/PatientSearchHeader.tsx +63 -0
  85. package/src/form-entry-workflow/patient-search-header/index.ts +3 -0
  86. package/src/form-entry-workflow/patient-search-header/styles.scss +22 -0
  87. package/src/form-entry-workflow/styles.scss +65 -0
  88. package/src/form-entry-workflow/workflow-review/WorkflowReview.tsx +35 -0
  89. package/src/form-entry-workflow/workflow-review/index.ts +3 -0
  90. package/src/form-entry-workflow/workflow-review/styles.scss +34 -0
  91. package/src/forms-app-menu-link.tsx +3 -2
  92. package/src/forms-page/FormsPage.tsx +134 -0
  93. package/src/forms-page/forms-table/FormsTable.tsx +137 -0
  94. package/src/forms-page/forms-table/index.ts +3 -0
  95. package/src/forms-page/forms-table/styles.scss +20 -0
  96. package/src/forms-page/index.ts +3 -0
  97. package/src/forms-page/styles.scss +11 -0
  98. package/src/group-form-entry-workflow/GroupFormEntryWorkflow.tsx +26 -0
  99. package/src/group-form-entry-workflow/GroupSessionWorkspace.tsx +247 -0
  100. package/src/group-form-entry-workflow/SessionDetailsForm.tsx +131 -0
  101. package/src/group-form-entry-workflow/SessionMetaWorkspace.tsx +107 -0
  102. package/src/group-form-entry-workflow/attendance-table/AttendanceTable.tsx +144 -0
  103. package/src/group-form-entry-workflow/attendance-table/index.ts +1 -0
  104. package/src/group-form-entry-workflow/group-display-header/GroupDisplayHeader.test.tsx +9 -0
  105. package/src/group-form-entry-workflow/group-display-header/GroupDisplayHeader.tsx +63 -0
  106. package/src/group-form-entry-workflow/group-display-header/index.ts +3 -0
  107. package/src/group-form-entry-workflow/group-display-header/styles.scss +60 -0
  108. package/src/group-form-entry-workflow/group-search/CompactGroupResults.tsx +139 -0
  109. package/src/group-form-entry-workflow/group-search/CompactGroupSearch.tsx +68 -0
  110. package/src/group-form-entry-workflow/group-search/GroupSearch.tsx +150 -0
  111. package/src/group-form-entry-workflow/group-search/compact-group-result.scss +64 -0
  112. package/src/group-form-entry-workflow/group-search/compact-group-search.scss +35 -0
  113. package/src/group-form-entry-workflow/group-search/group-search.scss +96 -0
  114. package/src/group-form-entry-workflow/group-search-header/GroupSearchHeader.tsx +73 -0
  115. package/src/group-form-entry-workflow/group-search-header/index.ts +3 -0
  116. package/src/group-form-entry-workflow/group-search-header/styles.scss +20 -0
  117. package/src/group-form-entry-workflow/index.ts +3 -0
  118. package/src/group-form-entry-workflow/styles.scss +97 -0
  119. package/src/hooks/index.ts +7 -0
  120. package/src/hooks/useFormState.ts +23 -0
  121. package/src/hooks/useGetAllForms.ts +45 -0
  122. package/src/hooks/useGetEncounter.ts +21 -0
  123. package/src/hooks/useGetPatient.ts +23 -0
  124. package/src/hooks/useGetPatients.ts +34 -0
  125. package/src/hooks/useGetSystemSetting.ts +38 -0
  126. package/src/hooks/useKeyPress.ts +31 -0
  127. package/src/hooks/usePostEndpoint.ts +76 -0
  128. package/src/hooks/useSearchEndpoint.ts +120 -0
  129. package/src/hooks/useStartVisit.ts +92 -0
  130. package/src/index.ts +26 -62
  131. package/src/patient-card/PatientCard.tsx +67 -0
  132. package/src/patient-card/index.ts +3 -0
  133. package/src/patient-card/styles.scss +46 -0
  134. package/src/routes.json +24 -0
  135. package/tools/i18next-parser.config.js +93 -0
  136. package/translations/en.json +69 -4
  137. package/translations/fr.json +50 -0
  138. package/tsconfig.json +26 -23
  139. package/.editorconfig +0 -12
  140. package/.eslintignore +0 -2
  141. package/.eslintrc +0 -4
  142. package/.github/workflows/node.js.yml +0 -79
  143. package/.husky/pre-commit +0 -6
  144. package/.husky/pre-push +0 -6
  145. package/.prettierignore +0 -14
  146. package/dist/24.js +0 -3
  147. package/dist/24.js.LICENSE.txt +0 -16
  148. package/dist/24.js.map +0 -1
  149. package/dist/296.js +0 -2
  150. package/dist/296.js.map +0 -1
  151. package/dist/299.js +0 -2
  152. package/dist/299.js.map +0 -1
  153. package/dist/382.js +0 -3
  154. package/dist/382.js.map +0 -1
  155. package/dist/595.js +0 -3
  156. package/dist/595.js.LICENSE.txt +0 -1
  157. package/dist/595.js.map +0 -1
  158. package/dist/69.js +0 -2
  159. package/dist/69.js.map +0 -1
  160. package/dist/735.js +0 -3
  161. package/dist/735.js.map +0 -1
  162. package/dist/777.js +0 -2
  163. package/dist/777.js.map +0 -1
  164. package/dist/860.js +0 -2
  165. package/dist/860.js.map +0 -1
  166. package/dist/906.js +0 -2
  167. package/dist/906.js.map +0 -1
  168. package/dist/openmrs-esm-fast-data-entry-app.old +0 -2
  169. package/src/boxes/extensions/blue-box.tsx +0 -15
  170. package/src/boxes/extensions/box.scss +0 -23
  171. package/src/boxes/extensions/brand-box.tsx +0 -15
  172. package/src/boxes/extensions/red-box.tsx +0 -15
  173. package/src/boxes/slot/boxes.css +0 -23
  174. package/src/boxes/slot/boxes.tsx +0 -19
  175. package/src/forms/FormsRoot.tsx +0 -32
  176. package/src/forms/FormsTable.tsx +0 -64
  177. package/src/forms/mockData.ts +0 -43
  178. package/src/greeter/greeter.css +0 -4
  179. package/src/greeter/greeter.test.tsx +0 -29
  180. package/src/greeter/greeter.tsx +0 -25
  181. package/src/hello.css +0 -3
  182. package/src/hello.test.tsx +0 -45
  183. package/src/hello.tsx +0 -30
  184. package/src/patient-getter/patient-getter.resource.ts +0 -31
  185. package/src/patient-getter/patient-getter.test.tsx +0 -28
  186. package/src/patient-getter/patient-getter.tsx +0 -28
@@ -0,0 +1,126 @@
1
+ import React, { useEffect, useMemo, useReducer } from "react";
2
+ import reducer from "./FormWorkflowReducer";
3
+ import { useParams, useLocation } from "react-router-dom";
4
+ import useGetSystemSetting from "../hooks/useGetSystemSetting";
5
+ import { useSession } from "@openmrs/esm-framework";
6
+ interface ParamTypes {
7
+ formUuid?: string;
8
+ }
9
+
10
+ const initialActions = {
11
+ addPatient: (uuid: string | number) => undefined,
12
+ openPatientSearch: () => undefined,
13
+ saveEncounter: (encounterUuid: string | number) => undefined,
14
+ editEncounter: (patientUuid: string | number) => undefined,
15
+ submitForNext: () => undefined,
16
+ submitForReview: () => undefined,
17
+ submitForComplete: () => undefined,
18
+ goToReview: () => undefined,
19
+ destroySession: () => undefined,
20
+ closeSession: () => undefined,
21
+ };
22
+
23
+ export const initialWorkflowState = {
24
+ // activeFormUuid and forms are the only two real values stored at state root level
25
+ activeFormUuid: null, // the corrently open form
26
+ forms: {}, // object containing all forms session data
27
+ // the following fields will be available in context but are not stored at the
28
+ // state root level, but refer to nested values for the current
29
+ // aciveFormUuid
30
+ workflowState: null, // pseudo field from state[activeFormUuid].workflowState
31
+ activePatientUuid: null, // pseudo field from state[activeFormUuid].activePatientUuid
32
+ activeEncounterUuid: null, // pseudo field from state[activeFormUuid].encounterUuid
33
+ patientUuids: [], // pseudo field from state[activeFormUuid].patientUuids
34
+ encounters: {}, // pseudo field from state[activeFormUuid].encounters
35
+ singleSessionVisitTypeUuid: null,
36
+ userUuid: null, // UUID of the user to which this workflow state belongs to
37
+ };
38
+
39
+ const FormWorkflowContext = React.createContext({
40
+ ...initialWorkflowState,
41
+ ...initialActions,
42
+ });
43
+
44
+ const FormWorkflowProvider = ({ children }) => {
45
+ const { user } = useSession();
46
+ const { formUuid } = useParams() as ParamTypes;
47
+ const activeFormUuid = formUuid.split("&")[0];
48
+ const { search } = useLocation();
49
+ const newPatientUuid = new URLSearchParams(search).get("patientUuid");
50
+ const [state, dispatch] = useReducer(reducer, {
51
+ ...initialWorkflowState,
52
+ ...initialActions,
53
+ });
54
+ const systemSetting = useGetSystemSetting(
55
+ "@openmrs/esm-fast-data-entry-app.groupSessionVisitTypeUuid"
56
+ );
57
+ const singleSessionVisitTypeUuid =
58
+ systemSetting?.result?.data?.results?.[0]?.value;
59
+
60
+ const actions = useMemo(
61
+ () => ({
62
+ initializeWorkflowState: ({ activeFormUuid, newPatientUuid }) =>
63
+ dispatch({
64
+ type: "INITIALIZE_WORKFLOW_STATE",
65
+ activeFormUuid,
66
+ newPatientUuid,
67
+ userUuid: user.uuid,
68
+ }),
69
+ addPatient: (patientUuid) =>
70
+ dispatch({ type: "ADD_PATIENT", patientUuid }),
71
+ openPatientSearch: () => dispatch({ type: "OPEN_PATIENT_SEARCH" }),
72
+ saveEncounter: (encounterUuid) =>
73
+ dispatch({
74
+ type: "SAVE_ENCOUNTER",
75
+ encounterUuid,
76
+ }),
77
+ submitForNext: () => dispatch({ type: "SUBMIT_FOR_NEXT" }),
78
+ submitForReview: () => dispatch({ type: "SUBMIT_FOR_REVIEW" }),
79
+ submitForComplete: () => dispatch({ type: "SUBMIT_FOR_COMPLETE" }),
80
+ editEncounter: (patientUuid) =>
81
+ dispatch({ type: "EDIT_ENCOUNTER", patientUuid }),
82
+ goToReview: () => dispatch({ type: "GO_TO_REVIEW" }),
83
+ destroySession: () => dispatch({ type: "DESTROY_SESSION" }),
84
+ closeSession: () => dispatch({ type: "CLOSE_SESSION" }),
85
+ }),
86
+ [user]
87
+ );
88
+
89
+ // if formUuid isn't a part of state yet, grab it from the url params
90
+ // this is the entry into the workflow system
91
+ useEffect(() => {
92
+ if (state?.workflowState === null && activeFormUuid) {
93
+ actions.initializeWorkflowState({ activeFormUuid, newPatientUuid });
94
+ }
95
+ }, [activeFormUuid, newPatientUuid, state?.workflowState, actions]);
96
+
97
+ return (
98
+ <FormWorkflowContext.Provider
99
+ value={{
100
+ ...state,
101
+ ...actions,
102
+ workflowState:
103
+ state.forms?.[state.activeFormUuid]?.workflowState ??
104
+ initialWorkflowState.workflowState,
105
+ activePatientUuid:
106
+ state.forms?.[state.activeFormUuid]?.activePatientUuid ??
107
+ initialWorkflowState.activePatientUuid,
108
+ activeEncounterUuid:
109
+ state.forms?.[state.activeFormUuid]?.activeEncounterUuid ??
110
+ initialWorkflowState.activeEncounterUuid,
111
+ patientUuids:
112
+ state.forms?.[state.activeFormUuid]?.patientUuids ??
113
+ initialWorkflowState.patientUuids,
114
+ encounters:
115
+ state.forms?.[state.activeFormUuid]?.encounters ??
116
+ initialWorkflowState.encounters,
117
+ singleSessionVisitTypeUuid,
118
+ }}
119
+ >
120
+ {children}
121
+ </FormWorkflowContext.Provider>
122
+ );
123
+ };
124
+
125
+ export default FormWorkflowContext;
126
+ export { FormWorkflowProvider };
@@ -0,0 +1,287 @@
1
+ import { navigate } from "@openmrs/esm-framework";
2
+ import { initialWorkflowState } from "./FormWorkflowContext";
3
+
4
+ export const fdeWorkflowStorageVersion = "1.1.0";
5
+ export const fdeWorkflowStorageName = "openmrs:fastDataEntryWorkflowState";
6
+ const persistData = (data) => {
7
+ localStorage.setItem(
8
+ fdeWorkflowStorageName + ":" + data.userUuid,
9
+ JSON.stringify(data)
10
+ );
11
+ };
12
+
13
+ const initialFormState = {
14
+ workflowState: "NEW_PATIENT",
15
+ activePatientUuid: null,
16
+ activeEncounterUuid: null,
17
+ patientUuids: [],
18
+ encounters: {},
19
+ };
20
+
21
+ const reducer = (state, action) => {
22
+ switch (action.type) {
23
+ case "INITIALIZE_WORKFLOW_STATE": {
24
+ const savedData = localStorage.getItem(
25
+ fdeWorkflowStorageName + ":" + action.userUuid
26
+ );
27
+ const savedDataObject = savedData ? JSON.parse(savedData) : {};
28
+ let newState: { [key: string]: unknown } = {};
29
+ const newPatient = action.newPatientUuid
30
+ ? {
31
+ activePatientUuid: action.newPatientUuid,
32
+ workflowState: "EDIT_FORM",
33
+ }
34
+ : {};
35
+
36
+ if (
37
+ savedData &&
38
+ savedDataObject["_storageVersion"] === fdeWorkflowStorageVersion
39
+ ) {
40
+ // there is localStorage data and it is still valid
41
+ newState = {
42
+ ...savedDataObject,
43
+ activeFormUuid: action.activeFormUuid,
44
+ forms: {
45
+ ...savedDataObject.forms,
46
+ // initialize this particular form if it hasn't been created already
47
+ [action.activeFormUuid]: {
48
+ ...initialFormState,
49
+ ...savedDataObject.forms[action.activeFormUuid],
50
+ // if we receive activePatientUuid from a query parameter use that one
51
+ ...newPatient,
52
+ patientUuids:
53
+ savedDataObject.forms[action.activeFormUuid]?.patientUuids ||
54
+ initialFormState.patientUuids,
55
+ },
56
+ },
57
+ };
58
+ if (
59
+ action.newPatientUuid &&
60
+ !newState.forms[action.activeFormUuid].patientUuids.includes(
61
+ action.newPatientUuid
62
+ )
63
+ ) {
64
+ newState.forms[action.activeFormUuid].patientUuids.push(
65
+ action.newPatientUuid
66
+ );
67
+ }
68
+ } else {
69
+ // no localStorage data, or we should void it
70
+ newState = {
71
+ ...initialWorkflowState,
72
+ _storageVersion: fdeWorkflowStorageVersion,
73
+ forms: {
74
+ [action.activeFormUuid]: initialFormState,
75
+ },
76
+ activeFormUuid: action.activeFormUuid,
77
+ userUuid: action.userUuid,
78
+ };
79
+ }
80
+ persistData(newState);
81
+ return { ...newState };
82
+ }
83
+ case "ADD_PATIENT": {
84
+ const newState = {
85
+ ...state,
86
+ forms: {
87
+ ...state.forms,
88
+ [state.activeFormUuid]: {
89
+ ...state.forms[state.activeFormUuid],
90
+ patientUuids: [
91
+ ...state.forms[state.activeFormUuid].patientUuids,
92
+ action.patientUuid,
93
+ ],
94
+ activePatientUuid: action.patientUuid,
95
+ activeEncounterUuid: null,
96
+ workflowState: "EDIT_FORM",
97
+ },
98
+ },
99
+ };
100
+ persistData(newState);
101
+ return newState;
102
+ }
103
+ case "OPEN_PATIENT_SEARCH": {
104
+ const newState = {
105
+ ...state,
106
+ forms: {
107
+ ...state.forms,
108
+ [state.activeFormUuid]: {
109
+ ...state.forms[state.activeFormUuid],
110
+ activePatientUuid: null,
111
+ activeEncounterUuid: null,
112
+ workflowState: "NEW_PATIENT",
113
+ },
114
+ },
115
+ };
116
+ // the persist here is optional...
117
+ persistData(newState);
118
+ return newState;
119
+ }
120
+ case "SAVE_ENCOUNTER": {
121
+ if (
122
+ state.forms[state.activeFormUuid].workflowState ===
123
+ "SUBMIT_FOR_COMPLETE"
124
+ ) {
125
+ const { [state.activeFormUuid]: activeForm, ...formRest } = state.forms;
126
+ const newState = {
127
+ ...state,
128
+ forms: formRest,
129
+ activeFormUuid: null,
130
+ };
131
+ persistData(newState);
132
+ // eslint-disable-next-line
133
+ navigate({ to: "${openmrsSpaBase}/forms" });
134
+ return newState;
135
+ } else {
136
+ const newState = {
137
+ ...state,
138
+ forms: {
139
+ ...state.forms,
140
+ [state.activeFormUuid]: {
141
+ ...state.forms[state.activeFormUuid],
142
+ encounters: {
143
+ ...state.forms[state.activeFormUuid].encounters,
144
+ [state.forms[state.activeFormUuid].activePatientUuid]:
145
+ action.encounterUuid,
146
+ },
147
+ activePatientUuid: null,
148
+ activeEncounterUuid: null,
149
+ workflowState:
150
+ state.forms[state.activeFormUuid].workflowState ===
151
+ "SUBMIT_FOR_NEXT"
152
+ ? "NEW_PATIENT"
153
+ : state.forms[state.activeFormUuid].workflowState ===
154
+ "SUBMIT_FOR_REVIEW"
155
+ ? "REVIEW"
156
+ : state.forms[state.activeFormUuid].workflowState,
157
+ },
158
+ },
159
+ };
160
+ persistData(newState);
161
+ return newState;
162
+ }
163
+ }
164
+ case "EDIT_ENCOUNTER": {
165
+ const newState = {
166
+ ...state,
167
+ forms: {
168
+ ...state.forms,
169
+ [state.activeFormUuid]: {
170
+ ...state.forms[state.activeFormUuid],
171
+ activeEncounterUuid:
172
+ state.forms[state.activeFormUuid].encounters[action.patientUuid],
173
+ activePatientUuid: action.patientUuid,
174
+ workflowState: "EDIT_FORM",
175
+ },
176
+ },
177
+ };
178
+ persistData(newState);
179
+ return newState;
180
+ }
181
+ case "SUBMIT_FOR_NEXT":
182
+ // this state should not be persisted
183
+ window.dispatchEvent(
184
+ new CustomEvent("ampath-form-action", {
185
+ detail: {
186
+ formUuid: state.activeFormUuid,
187
+ patientUuid: state.forms[state.activeFormUuid].activePatientUuid,
188
+ action: "onSubmit",
189
+ },
190
+ })
191
+ );
192
+ return {
193
+ ...state,
194
+ forms: {
195
+ ...state.forms,
196
+ [state.activeFormUuid]: {
197
+ ...state.forms[state.activeFormUuid],
198
+ workflowState: "SUBMIT_FOR_NEXT",
199
+ },
200
+ },
201
+ };
202
+ case "SUBMIT_FOR_REVIEW":
203
+ // this state should not be persisted
204
+ window.dispatchEvent(
205
+ new CustomEvent("ampath-form-action", {
206
+ detail: {
207
+ formUuid: state.activeFormUuid,
208
+ patientUuid: state.forms[state.activeFormUuid].activePatientUuid,
209
+ action: "onSubmit",
210
+ },
211
+ })
212
+ );
213
+ return {
214
+ ...state,
215
+ forms: {
216
+ ...state.forms,
217
+ [state.activeFormUuid]: {
218
+ ...state.forms[state.activeFormUuid],
219
+ workflowState: "SUBMIT_FOR_REVIEW",
220
+ },
221
+ },
222
+ };
223
+ case "SUBMIT_FOR_COMPLETE":
224
+ // this state should not be persisted
225
+ window.dispatchEvent(
226
+ new CustomEvent("ampath-form-action", {
227
+ detail: {
228
+ formUuid: state.activeFormUuid,
229
+ patientUuid: state.forms[state.activeFormUuid].activePatientUuid,
230
+ action: "onSubmit",
231
+ },
232
+ })
233
+ );
234
+ return {
235
+ ...state,
236
+ forms: {
237
+ ...state.forms,
238
+ [state.activeFormUuid]: {
239
+ ...state.forms[state.activeFormUuid],
240
+ workflowState: "SUBMIT_FOR_COMPLETE",
241
+ },
242
+ },
243
+ };
244
+ case "GO_TO_REVIEW": {
245
+ const newState = {
246
+ ...state,
247
+ forms: {
248
+ ...state.forms,
249
+ [state.activeFormUuid]: {
250
+ ...state.forms[state.activeFormUuid],
251
+ activeEncounterUuid: null,
252
+ activePatientUuid: null,
253
+ workflowState: "REVIEW",
254
+ },
255
+ },
256
+ };
257
+ persistData(newState);
258
+ return newState;
259
+ }
260
+ case "DESTROY_SESSION": {
261
+ const { [state.activeFormUuid]: activeForm, ...formRest } = state.forms;
262
+ const newState = {
263
+ ...state,
264
+ forms: formRest,
265
+ activeFormUuid: null,
266
+ };
267
+ persistData(newState);
268
+ //eslint-disable-next-line
269
+ navigate({ to: "${openmrsSpaBase}/forms" });
270
+ return newState;
271
+ }
272
+ case "CLOSE_SESSION": {
273
+ const newState = {
274
+ ...state,
275
+ activeFormUuid: null,
276
+ };
277
+ persistData(newState);
278
+ //eslint-disable-next-line
279
+ navigate({ to: "${openmrsSpaBase}/forms" });
280
+ return newState;
281
+ }
282
+ default:
283
+ return state;
284
+ }
285
+ };
286
+
287
+ export default reducer;
@@ -0,0 +1,176 @@
1
+ import React, { useEffect, useMemo, useReducer } from "react";
2
+ import reducer from "./GroupFormWorkflowReducer";
3
+ import { useParams } from "react-router-dom";
4
+ import { Type, useSession } from "@openmrs/esm-framework";
5
+ import useGetSystemSetting from "../hooks/useGetSystemSetting";
6
+ interface ParamTypes {
7
+ formUuid?: string;
8
+ }
9
+
10
+ export interface GroupType {
11
+ id: string;
12
+ name: string;
13
+ members: Array<Type.Object>;
14
+ }
15
+ export interface MetaType {
16
+ sessionName: string;
17
+ sessionDate: string;
18
+ practitionerName: string;
19
+ sessionNotes: string;
20
+ }
21
+
22
+ const initialActions = {
23
+ setGroup: (group: GroupType) => undefined,
24
+ unsetGroup: () => undefined,
25
+ setSessionMeta: (meta: MetaType) => undefined,
26
+ openPatientSearch: () => undefined,
27
+ saveEncounter: (encounterUuid: string | number) => undefined,
28
+ editEncounter: (patientUuid: string | number) => undefined,
29
+ validateForNext: () => undefined,
30
+ validateForComplete: () => undefined,
31
+ updateVisitUuid: (visitUuid: string) => undefined,
32
+ submitForNext: (nextPatientUuid: string = null) => undefined,
33
+ submitForReview: () => undefined,
34
+ submitForComplete: () => undefined,
35
+ addPatientUuid: (patientUuid: string) => undefined,
36
+ removePatientUuid: (patientUuid: string) => undefined,
37
+ goToReview: () => undefined,
38
+ destroySession: () => undefined,
39
+ closeSession: () => undefined,
40
+ };
41
+
42
+ export const initialWorkflowState = {
43
+ // activeFormUuid and forms are the only two real values stored at state root level
44
+ activeFormUuid: null, // the corrently open form
45
+ forms: {}, // object containing all forms session data
46
+ // the following fields will be available in context but are not stored at the
47
+ // state root level, but refer to nested values for the current
48
+ // aciveFormUuid
49
+ workflowState: null, // pseudo field from state[activeFormUuid].workflowState
50
+ activePatientUuid: null, // pseudo field from state[activeFormUuid].activePatientUuid
51
+ activeEncounterUuid: null, // pseudo field from state[activeFormUuid].activeEncounterUuid
52
+ activeVisitUuid: null, // pseudo field from state[activeFormUuid].activeVisitUuid
53
+ patientUuids: [], // pseudo field from state[activeFormUuid].patientUuids
54
+ encounters: {}, // pseudo field from state[activeFormUuid].encounters
55
+ visits: {}, // pseudo field from state[activeFormUuid].visits
56
+ activeGroupUuid: null, // pseudo field from state[activeFormUuid].groupUuid
57
+ activeGroupName: null, // pseudo field from state[activeFormUuid].groupName
58
+ activeGroupMembers: [], // pseudo field from state[activeFormUuid].groupMembers
59
+ activeSessionMeta: {
60
+ sessionName: null,
61
+ practitionerName: null,
62
+ sessionDate: null,
63
+ sessionNotes: null,
64
+ },
65
+ groupVisitTypeUuid: null,
66
+ userUuid: null, // UUID of the user to which this workflow state belongs to
67
+ };
68
+
69
+ const GroupFormWorkflowContext = React.createContext({
70
+ ...initialWorkflowState,
71
+ ...initialActions,
72
+ });
73
+
74
+ const GroupFormWorkflowProvider = ({ children }) => {
75
+ const { user } = useSession();
76
+ const { formUuid } = useParams() as ParamTypes;
77
+ const activeFormUuid = formUuid.split("&")[0];
78
+ const systemSetting = useGetSystemSetting(
79
+ "@openmrs/esm-fast-data-entry-app.groupSessionVisitTypeUuid"
80
+ );
81
+ const groupVisitTypeUuid = systemSetting?.result?.data?.results?.[0]?.value;
82
+ const [state, dispatch] = useReducer(reducer, {
83
+ ...initialWorkflowState,
84
+ ...initialActions,
85
+ });
86
+
87
+ const actions = useMemo(
88
+ () => ({
89
+ initializeWorkflowState: ({ activeFormUuid }) =>
90
+ dispatch({
91
+ type: "INITIALIZE_WORKFLOW_STATE",
92
+ activeFormUuid,
93
+ userUuid: user.uuid,
94
+ }),
95
+ setGroup: (group) => dispatch({ type: "SET_GROUP", group }),
96
+ unsetGroup: () => dispatch({ type: "UNSET_GROUP" }),
97
+ setSessionMeta: (meta) => dispatch({ type: "SET_SESSION_META", meta }),
98
+ addPatientUuid: (patientUuid) =>
99
+ dispatch({ type: "ADD_PATIENT_UUID", patientUuid }),
100
+ removePatientUuid: (patientUuid) =>
101
+ dispatch({ type: "REMOVE_PATIENT_UUID", patientUuid }),
102
+ openPatientSearch: () => dispatch({ type: "OPEN_PATIENT_SEARCH" }),
103
+ saveEncounter: (encounterUuid) =>
104
+ dispatch({
105
+ type: "SAVE_ENCOUNTER",
106
+ encounterUuid,
107
+ }),
108
+ validateForNext: () => dispatch({ type: "VALIDATE_FOR_NEXT" }),
109
+ validateForComplete: () => dispatch({ type: "VALIDATE_FOR_COMPLETE" }),
110
+ updateVisitUuid: (visitUuid) =>
111
+ dispatch({ type: "UPDATE_VISIT_UUID", visitUuid }),
112
+ submitForNext: (nextPatientUuid) =>
113
+ dispatch({ type: "SUBMIT_FOR_NEXT", nextPatientUuid }),
114
+ submitForComplete: () => dispatch({ type: "SUBMIT_FOR_COMPLETE" }),
115
+ editEncounter: (patientUuid) =>
116
+ dispatch({ type: "EDIT_ENCOUNTER", patientUuid }),
117
+ goToReview: () => dispatch({ type: "GO_TO_REVIEW" }),
118
+ destroySession: () => dispatch({ type: "DESTROY_SESSION" }),
119
+ closeSession: () => dispatch({ type: "CLOSE_SESSION" }),
120
+ }),
121
+ [user]
122
+ );
123
+
124
+ // if formUuid isn't a part of state yet, grab it from the url params
125
+ // this is the entry into the workflow system
126
+ useEffect(() => {
127
+ if (state?.workflowState === null && activeFormUuid) {
128
+ actions.initializeWorkflowState({ activeFormUuid });
129
+ }
130
+ }, [activeFormUuid, state?.workflowState, actions]);
131
+
132
+ return (
133
+ <GroupFormWorkflowContext.Provider
134
+ value={{
135
+ groupVisitTypeUuid,
136
+ ...state,
137
+ ...actions,
138
+ workflowState:
139
+ state.forms?.[state.activeFormUuid]?.workflowState ??
140
+ initialWorkflowState.workflowState,
141
+ activePatientUuid:
142
+ state.forms?.[state.activeFormUuid]?.activePatientUuid ??
143
+ initialWorkflowState.activePatientUuid,
144
+ activeEncounterUuid:
145
+ state.forms?.[state.activeFormUuid]?.activeEncounterUuid ??
146
+ initialWorkflowState.activeEncounterUuid,
147
+ activeVisitUuid:
148
+ state.forms?.[state.activeFormUuid]?.activeVisitUuid ??
149
+ initialWorkflowState.activeVisitUuid,
150
+ patientUuids:
151
+ state.forms?.[state.activeFormUuid]?.patientUuids ??
152
+ initialWorkflowState.patientUuids,
153
+ encounters:
154
+ state.forms?.[state.activeFormUuid]?.encounters ??
155
+ initialWorkflowState.encounters,
156
+ activeGroupUuid:
157
+ state.forms?.[state.activeFormUuid]?.groupUuid ??
158
+ initialWorkflowState.activeGroupUuid,
159
+ activeGroupName:
160
+ state.forms?.[state.activeFormUuid]?.groupName ??
161
+ initialWorkflowState.activeGroupName,
162
+ activeGroupMembers:
163
+ state.forms?.[state.activeFormUuid]?.groupMembers ??
164
+ initialWorkflowState.activeGroupMembers,
165
+ activeSessionMeta:
166
+ state.forms?.[state.activeFormUuid]?.sessionMeta ??
167
+ initialWorkflowState.activeSessionMeta,
168
+ }}
169
+ >
170
+ {children}
171
+ </GroupFormWorkflowContext.Provider>
172
+ );
173
+ };
174
+
175
+ export default GroupFormWorkflowContext;
176
+ export { GroupFormWorkflowProvider };