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

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