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

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 (133) 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/c1451405.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 +99 -106
  15. package/src/FormBootstrap.tsx +151 -0
  16. package/src/Root.tsx +14 -3
  17. package/src/add-group-modal/AddGroupModal.tsx +262 -0
  18. package/src/add-group-modal/styles.scss +45 -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 +143 -0
  23. package/src/context/GroupFormWorkflowReducer.ts +296 -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-display-header/GroupDisplayHeader.test.tsx +9 -0
  52. package/src/group-form-entry-workflow/group-display-header/GroupDisplayHeader.tsx +71 -0
  53. package/src/group-form-entry-workflow/group-display-header/index.ts +3 -0
  54. package/src/group-form-entry-workflow/group-display-header/styles.scss +60 -0
  55. package/src/group-form-entry-workflow/group-search/CompactGroupResults.tsx +139 -0
  56. package/src/group-form-entry-workflow/group-search/CompactGroupSearch.tsx +68 -0
  57. package/src/group-form-entry-workflow/group-search/GroupSearch.tsx +150 -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 +96 -0
  61. package/src/group-form-entry-workflow/group-search-header/GroupSearchHeader.tsx +49 -0
  62. package/src/group-form-entry-workflow/group-search-header/index.ts +3 -0
  63. package/src/group-form-entry-workflow/group-search-header/styles.scss +20 -0
  64. package/src/group-form-entry-workflow/index.ts +3 -0
  65. package/src/group-form-entry-workflow/styles.scss +96 -0
  66. package/src/hooks/index.ts +7 -0
  67. package/src/hooks/useFormState.ts +23 -0
  68. package/src/hooks/useGetAllForms.ts +45 -0
  69. package/src/hooks/useGetEncounter.ts +21 -0
  70. package/src/hooks/useGetPatient.ts +23 -0
  71. package/src/hooks/useKeyPress.ts +31 -0
  72. package/src/hooks/usePostEndpoint.ts +70 -0
  73. package/src/hooks/useSearchEndpoint.ts +120 -0
  74. package/src/index.ts +20 -4
  75. package/src/patient-card/PatientCard.tsx +67 -0
  76. package/src/patient-card/index.ts +3 -0
  77. package/src/patient-card/styles.scss +45 -0
  78. package/translations/en.json +54 -4
  79. package/tsconfig.json +26 -23
  80. package/.eslintrc +0 -4
  81. package/.github/workflows/node.js.yml +0 -79
  82. package/.husky/pre-commit +0 -6
  83. package/dist/24.js +0 -3
  84. package/dist/24.js.LICENSE.txt +0 -16
  85. package/dist/24.js.map +0 -1
  86. package/dist/294.js +0 -3
  87. package/dist/294.js.LICENSE.txt +0 -14
  88. package/dist/294.js.map +0 -1
  89. package/dist/296.js +0 -2
  90. package/dist/296.js.map +0 -1
  91. package/dist/299.js +0 -2
  92. package/dist/299.js.map +0 -1
  93. package/dist/382.js +0 -3
  94. package/dist/382.js.LICENSE.txt +0 -8
  95. package/dist/382.js.map +0 -1
  96. package/dist/415.js +0 -2
  97. package/dist/415.js.map +0 -1
  98. package/dist/574.js +0 -1
  99. package/dist/595.js +0 -3
  100. package/dist/595.js.LICENSE.txt +0 -1
  101. package/dist/595.js.map +0 -1
  102. package/dist/69.js +0 -2
  103. package/dist/69.js.map +0 -1
  104. package/dist/735.js +0 -3
  105. package/dist/735.js.LICENSE.txt +0 -29
  106. package/dist/735.js.map +0 -1
  107. package/dist/777.js +0 -2
  108. package/dist/777.js.map +0 -1
  109. package/dist/860.js +0 -2
  110. package/dist/860.js.map +0 -1
  111. package/dist/906.js +0 -2
  112. package/dist/906.js.map +0 -1
  113. package/dist/openmrs-esm-fast-data-entry-app.js.buildmanifest.json +0 -369
  114. package/dist/openmrs-esm-fast-data-entry-app.js.map +0 -1
  115. package/dist/openmrs-esm-fast-data-entry-app.old +0 -2
  116. package/src/boxes/extensions/blue-box.tsx +0 -15
  117. package/src/boxes/extensions/box.scss +0 -23
  118. package/src/boxes/extensions/brand-box.tsx +0 -15
  119. package/src/boxes/extensions/red-box.tsx +0 -15
  120. package/src/boxes/slot/boxes.css +0 -23
  121. package/src/boxes/slot/boxes.tsx +0 -19
  122. package/src/forms/FormsRoot.tsx +0 -32
  123. package/src/forms/FormsTable.tsx +0 -64
  124. package/src/forms/mockData.ts +0 -43
  125. package/src/greeter/greeter.css +0 -4
  126. package/src/greeter/greeter.test.tsx +0 -29
  127. package/src/greeter/greeter.tsx +0 -25
  128. package/src/hello.css +0 -3
  129. package/src/hello.test.tsx +0 -45
  130. package/src/hello.tsx +0 -30
  131. package/src/patient-getter/patient-getter.resource.ts +0 -31
  132. package/src/patient-getter/patient-getter.test.tsx +0 -28
  133. 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,143 @@
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
+ unsetGroup: () => undefined,
24
+ setSessionMeta: (meta: MetaType) => undefined,
25
+ openPatientSearch: () => undefined,
26
+ saveEncounter: (encounterUuid: string | number) => undefined,
27
+ editEncounter: (patientUuid: string | number) => undefined,
28
+ submitForNext: () => undefined,
29
+ submitForReview: () => undefined,
30
+ submitForComplete: () => undefined,
31
+ goToReview: () => undefined,
32
+ destroySession: () => undefined,
33
+ closeSession: () => undefined,
34
+ };
35
+
36
+ export const initialWorkflowState = {
37
+ // activeFormUuid and forms are the only two real values stored at state root level
38
+ activeFormUuid: null, // the corrently open form
39
+ forms: {}, // object containing all forms session data
40
+ // the following fields will be available in context but are not stored at the
41
+ // state root level, but refer to nested values for the current
42
+ // aciveFormUuid
43
+ workflowState: null, // pseudo field from state[activeFormUuid].workflowState
44
+ activePatientUuid: null, // pseudo field from state[activeFormUuid].activePatientUuid
45
+ activeEncounterUuid: null, // pseudo field from state[activeFormUuid].encounterUuid
46
+ patientUuids: [], // pseudo field from state[activeFormUuid].patientUuids
47
+ encounters: {}, // pseudo field from state[activeFormUuid].encounters
48
+ activeGroupUuid: null, // pseudo field from state[activeFormUuid].groupUuid
49
+ activeGroupName: null, // pseudo field from state[activeFormUuid].groupname
50
+ activeSessionMeta: {
51
+ sessionName: null,
52
+ practitionerName: null,
53
+ sessionDate: null,
54
+ sessionNotes: null,
55
+ },
56
+ };
57
+
58
+ const GroupFormWorkflowContext = React.createContext({
59
+ ...initialWorkflowState,
60
+ ...initialActions,
61
+ });
62
+
63
+ const GroupFormWorkflowProvider = ({ children }) => {
64
+ const { formUuid } = useParams() as ParamTypes;
65
+ const activeFormUuid = formUuid.split("&")[0];
66
+ const [state, dispatch] = useReducer(reducer, {
67
+ ...initialWorkflowState,
68
+ ...initialActions,
69
+ });
70
+
71
+ const actions = useMemo(
72
+ () => ({
73
+ initializeWorkflowState: ({ activeFormUuid }) =>
74
+ dispatch({
75
+ type: "INITIALIZE_WORKFLOW_STATE",
76
+ activeFormUuid,
77
+ }),
78
+ setGroup: (group) => dispatch({ type: "SET_GROUP", group }),
79
+ unsetGroup: () => dispatch({ type: "UNSET_GROUP" }),
80
+ setSessionMeta: (meta) => dispatch({ type: "SET_SESSION_META", meta }),
81
+ openPatientSearch: () => dispatch({ type: "OPEN_PATIENT_SEARCH" }),
82
+ saveEncounter: (encounterUuid) =>
83
+ dispatch({
84
+ type: "SAVE_ENCOUNTER",
85
+ encounterUuid,
86
+ }),
87
+ submitForNext: () => dispatch({ type: "SUBMIT_FOR_NEXT" }),
88
+ submitForComplete: () => dispatch({ type: "SUBMIT_FOR_COMPLETE" }),
89
+ editEncounter: (patientUuid) =>
90
+ dispatch({ type: "EDIT_ENCOUNTER", patientUuid }),
91
+ goToReview: () => dispatch({ type: "GO_TO_REVIEW" }),
92
+ destroySession: () => dispatch({ type: "DESTROY_SESSION" }),
93
+ closeSession: () => dispatch({ type: "CLOSE_SESSION" }),
94
+ }),
95
+ []
96
+ );
97
+
98
+ // if formUuid isn't a part of state yet, grab it from the url params
99
+ // this is the entry into the workflow system
100
+ useEffect(() => {
101
+ if (state?.workflowState === null && activeFormUuid) {
102
+ actions.initializeWorkflowState({ activeFormUuid });
103
+ }
104
+ }, [activeFormUuid, state?.workflowState, actions]);
105
+
106
+ return (
107
+ <GroupFormWorkflowContext.Provider
108
+ value={{
109
+ ...state,
110
+ ...actions,
111
+ workflowState:
112
+ state.forms?.[state.activeFormUuid]?.workflowState ??
113
+ initialWorkflowState.workflowState,
114
+ activePatientUuid:
115
+ state.forms?.[state.activeFormUuid]?.activePatientUuid ??
116
+ initialWorkflowState.activePatientUuid,
117
+ activeEncounterUuid:
118
+ state.forms?.[state.activeFormUuid]?.activeEncounterUuid ??
119
+ initialWorkflowState.activeEncounterUuid,
120
+ patientUuids:
121
+ state.forms?.[state.activeFormUuid]?.patientUuids ??
122
+ initialWorkflowState.patientUuids,
123
+ encounters:
124
+ state.forms?.[state.activeFormUuid]?.encounters ??
125
+ initialWorkflowState.encounters,
126
+ activeGroupUuid:
127
+ state.forms?.[state.activeFormUuid]?.groupUuid ??
128
+ initialWorkflowState.activeGroupUuid,
129
+ activeGroupName:
130
+ state.forms?.[state.activeFormUuid]?.groupName ??
131
+ initialWorkflowState.activeGroupName,
132
+ activeSessionMeta:
133
+ state.forms?.[state.activeFormUuid]?.sessionMeta ??
134
+ initialWorkflowState.activeSessionMeta,
135
+ }}
136
+ >
137
+ {children}
138
+ </GroupFormWorkflowContext.Provider>
139
+ );
140
+ };
141
+
142
+ export default GroupFormWorkflowContext;
143
+ export { GroupFormWorkflowProvider };
@@ -0,0 +1,296 @@
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.uuid,
79
+ groupName: action.group.name,
80
+ patientUuids:
81
+ // this translation is not preferred
82
+ // the only reason we tollerate it here is beause it should be the only time
83
+ // we add cohort information to state
84
+ action.group.cohortMembers?.map(
85
+ (member) => member?.patient?.uuid
86
+ ) ?? [],
87
+ activePatientUuid: null,
88
+ activeEncounterUuid: null,
89
+ },
90
+ },
91
+ };
92
+ persistData(newState);
93
+ return newState;
94
+ }
95
+ case "UNSET_GROUP": {
96
+ const newState = {
97
+ ...state,
98
+ forms: {
99
+ ...state.forms,
100
+ [state.activeFormUuid]: {
101
+ ...state.forms[state.activeFormUuid],
102
+ groupUuid: null,
103
+ groupName: null,
104
+ patientUuids: [],
105
+ activePatientUuid: null,
106
+ activeEncounterUuid: null,
107
+ },
108
+ },
109
+ };
110
+ persistData(newState);
111
+ return newState;
112
+ }
113
+ case "SET_SESSION_META": {
114
+ // requires that group is already entered and contains patientUuids
115
+ const newState = {
116
+ ...state,
117
+ forms: {
118
+ ...state.forms,
119
+ [state.activeFormUuid]: {
120
+ ...state.forms[state.activeFormUuid],
121
+ sessionMeta: action.meta,
122
+ activePatientUuid:
123
+ state.forms[state.activeFormUuid].patientUuids?.[0],
124
+ activeEncounterUuid:
125
+ state.forms[state.activeFormUuid].encounters[
126
+ state.forms[state.activeFormUuid].patientUuids?.[0]
127
+ ] || null,
128
+ workflowState: "EDIT_FORM",
129
+ },
130
+ },
131
+ };
132
+ persistData(newState);
133
+ return newState;
134
+ }
135
+
136
+ case "SAVE_ENCOUNTER": {
137
+ const thisForm = state.forms[state.activeFormUuid];
138
+ if (thisForm.workflowState === "SUBMIT_FOR_COMPLETE") {
139
+ const { [state.activeFormUuid]: activeForm, ...formRest } = state.forms;
140
+ const newState = {
141
+ ...state,
142
+ forms: formRest,
143
+ activeFormUuid: null,
144
+ };
145
+ persistData(newState);
146
+ // eslint-disable-next-line
147
+ navigate({ to: "${openmrsSpaBase}/forms" });
148
+ return newState;
149
+ } else if (thisForm.workflowState === "SUBMIT_FOR_NEXT") {
150
+ const nextPatientUuid =
151
+ thisForm.patientUuids[
152
+ Math.min(
153
+ thisForm.patientUuids.indexOf(thisForm.activePatientUuid) + 1,
154
+ thisForm.patientUuids.length - 1
155
+ )
156
+ ];
157
+ const newState = {
158
+ ...state,
159
+ forms: {
160
+ ...state.forms,
161
+ [state.activeFormUuid]: {
162
+ ...thisForm,
163
+ encounters: {
164
+ ...thisForm.encounters,
165
+ [thisForm.activePatientUuid]: action.encounterUuid,
166
+ },
167
+ activePatientUuid: nextPatientUuid,
168
+ activeEncounterUuid: thisForm.encounters[nextPatientUuid] || null,
169
+ workflowState: "EDIT_FORM",
170
+ },
171
+ },
172
+ };
173
+ persistData(newState);
174
+ return newState;
175
+ } else return state;
176
+ }
177
+ case "EDIT_ENCOUNTER": {
178
+ const newState = {
179
+ ...state,
180
+ forms: {
181
+ ...state.forms,
182
+ [state.activeFormUuid]: {
183
+ ...state.forms[state.activeFormUuid],
184
+ activeEncounterUuid:
185
+ state.forms[state.activeFormUuid].encounters[action.patientUuid],
186
+ activePatientUuid: action.patientUuid,
187
+ workflowState: "EDIT_FORM",
188
+ },
189
+ },
190
+ };
191
+ persistData(newState);
192
+ return newState;
193
+ }
194
+ case "SUBMIT_FOR_NEXT":
195
+ // this state should not be persisted
196
+ window.dispatchEvent(
197
+ new CustomEvent("ampath-form-action", {
198
+ detail: {
199
+ formUuid: state.activeFormUuid,
200
+ patientUuid: state.forms[state.activeFormUuid].activePatientUuid,
201
+ action: "onSubmit",
202
+ },
203
+ })
204
+ );
205
+ return {
206
+ ...state,
207
+ forms: {
208
+ ...state.forms,
209
+ [state.activeFormUuid]: {
210
+ ...state.forms[state.activeFormUuid],
211
+ workflowState: "SUBMIT_FOR_NEXT",
212
+ },
213
+ },
214
+ };
215
+ case "SUBMIT_FOR_REVIEW":
216
+ // this state should not be persisted
217
+ window.dispatchEvent(
218
+ new CustomEvent("ampath-form-action", {
219
+ detail: {
220
+ formUuid: state.activeFormUuid,
221
+ patientUuid: state.forms[state.activeFormUuid].activePatientUuid,
222
+ action: "onSubmit",
223
+ },
224
+ })
225
+ );
226
+ return {
227
+ ...state,
228
+ forms: {
229
+ ...state.forms,
230
+ [state.activeFormUuid]: {
231
+ ...state.forms[state.activeFormUuid],
232
+ workflowState: "SUBMIT_FOR_REVIEW",
233
+ },
234
+ },
235
+ };
236
+ case "SUBMIT_FOR_COMPLETE":
237
+ // this state should not be persisted
238
+ window.dispatchEvent(
239
+ new CustomEvent("ampath-form-action", {
240
+ detail: {
241
+ formUuid: state.activeFormUuid,
242
+ patientUuid: state.forms[state.activeFormUuid].activePatientUuid,
243
+ action: "onSubmit",
244
+ },
245
+ })
246
+ );
247
+ return {
248
+ ...state,
249
+ forms: {
250
+ ...state.forms,
251
+ [state.activeFormUuid]: {
252
+ ...state.forms[state.activeFormUuid],
253
+ workflowState: "SUBMIT_FOR_COMPLETE",
254
+ },
255
+ },
256
+ };
257
+ case "GO_TO_REVIEW": {
258
+ const newState = {
259
+ ...state,
260
+ forms: {
261
+ ...state.forms,
262
+ [state.activeFormUuid]: {
263
+ ...state.forms[state.activeFormUuid],
264
+ activeEncounterUuid: null,
265
+ activePatientUuid: null,
266
+ workflowState: "REVIEW",
267
+ },
268
+ },
269
+ };
270
+ persistData(newState);
271
+ return newState;
272
+ }
273
+ case "DESTROY_SESSION": {
274
+ const { [state.activeFormUuid]: activeForm, ...formRest } = state.forms;
275
+ const newState = {
276
+ ...state,
277
+ forms: formRest,
278
+ activeFormUuid: null,
279
+ };
280
+ persistData(newState);
281
+ return newState;
282
+ }
283
+ case "CLOSE_SESSION": {
284
+ const newState = {
285
+ ...state,
286
+ activeFormUuid: null,
287
+ };
288
+ persistData(newState);
289
+ return newState;
290
+ }
291
+ default:
292
+ return state;
293
+ }
294
+ };
295
+
296
+ export default reducer;