@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.
- package/.eslintrc.js +10 -0
- package/.husky/pre-push +1 -6
- package/.tx/config +9 -0
- package/.yarn/plugins/@yarnpkg/plugin-version.cjs +550 -0
- package/.yarn/versions/c1451405.yml +0 -0
- package/README.md +39 -12
- package/dist/openmrs-esm-fast-data-entry-app.js +1 -1
- package/docs/config-icrc-forms.png +0 -0
- package/docs/config-other-forms.png +0 -0
- package/docs/configuring-form-categories.md +77 -0
- package/docs/fde-workflow.mov +0 -0
- package/docs/form-workflow-state-diagram.png +0 -0
- package/jest.config.json +20 -18
- package/package.json +99 -106
- package/src/FormBootstrap.tsx +151 -0
- package/src/Root.tsx +14 -3
- package/src/add-group-modal/AddGroupModal.tsx +262 -0
- package/src/add-group-modal/styles.scss +45 -0
- package/src/config-schema.ts +63 -31
- package/src/context/FormWorkflowContext.tsx +114 -0
- package/src/context/FormWorkflowReducer.ts +277 -0
- package/src/context/GroupFormWorkflowContext.tsx +143 -0
- package/src/context/GroupFormWorkflowReducer.ts +296 -0
- package/src/empty-state/EmptyDataIllustration.tsx +51 -0
- package/src/empty-state/EmptyState.tsx +33 -0
- package/src/empty-state/styles.scss +55 -0
- package/src/form-entry-workflow/FormEntryWorkflow.tsx +230 -0
- package/src/form-entry-workflow/form-review-card/FormReviewCard.tsx +50 -0
- package/src/form-entry-workflow/form-review-card/index.ts +3 -0
- package/src/form-entry-workflow/form-review-card/styles.scss +39 -0
- package/src/form-entry-workflow/index.ts +3 -0
- package/src/form-entry-workflow/patient-banner/PatientBanner.test.tsx +9 -0
- package/src/form-entry-workflow/patient-banner/PatientBanner.tsx +86 -0
- package/src/form-entry-workflow/patient-banner/index.ts +3 -0
- package/src/form-entry-workflow/patient-banner/styles.scss +45 -0
- package/src/form-entry-workflow/patient-search-header/PatientSearchHeader.tsx +63 -0
- package/src/form-entry-workflow/patient-search-header/index.ts +3 -0
- package/src/form-entry-workflow/patient-search-header/styles.scss +22 -0
- package/src/form-entry-workflow/styles.scss +64 -0
- package/src/form-entry-workflow/workflow-review/WorkflowReview.tsx +35 -0
- package/src/form-entry-workflow/workflow-review/index.ts +3 -0
- package/src/form-entry-workflow/workflow-review/styles.scss +34 -0
- package/src/forms-app-menu-link.tsx +3 -2
- package/src/forms-page/FormsPage.tsx +129 -0
- package/src/forms-page/forms-table/FormsTable.tsx +131 -0
- package/src/forms-page/forms-table/index.ts +3 -0
- package/src/forms-page/forms-table/styles.scss +20 -0
- package/src/forms-page/index.ts +3 -0
- package/src/forms-page/styles.scss +11 -0
- package/src/group-form-entry-workflow/GroupFormEntryWorkflow.tsx +413 -0
- package/src/group-form-entry-workflow/group-display-header/GroupDisplayHeader.test.tsx +9 -0
- package/src/group-form-entry-workflow/group-display-header/GroupDisplayHeader.tsx +71 -0
- package/src/group-form-entry-workflow/group-display-header/index.ts +3 -0
- package/src/group-form-entry-workflow/group-display-header/styles.scss +60 -0
- package/src/group-form-entry-workflow/group-search/CompactGroupResults.tsx +139 -0
- package/src/group-form-entry-workflow/group-search/CompactGroupSearch.tsx +68 -0
- package/src/group-form-entry-workflow/group-search/GroupSearch.tsx +150 -0
- package/src/group-form-entry-workflow/group-search/compact-group-result.scss +64 -0
- package/src/group-form-entry-workflow/group-search/compact-group-search.scss +35 -0
- package/src/group-form-entry-workflow/group-search/group-search.scss +96 -0
- package/src/group-form-entry-workflow/group-search-header/GroupSearchHeader.tsx +49 -0
- package/src/group-form-entry-workflow/group-search-header/index.ts +3 -0
- package/src/group-form-entry-workflow/group-search-header/styles.scss +20 -0
- package/src/group-form-entry-workflow/index.ts +3 -0
- package/src/group-form-entry-workflow/styles.scss +96 -0
- package/src/hooks/index.ts +7 -0
- package/src/hooks/useFormState.ts +23 -0
- package/src/hooks/useGetAllForms.ts +45 -0
- package/src/hooks/useGetEncounter.ts +21 -0
- package/src/hooks/useGetPatient.ts +23 -0
- package/src/hooks/useKeyPress.ts +31 -0
- package/src/hooks/usePostEndpoint.ts +70 -0
- package/src/hooks/useSearchEndpoint.ts +120 -0
- package/src/index.ts +20 -4
- package/src/patient-card/PatientCard.tsx +67 -0
- package/src/patient-card/index.ts +3 -0
- package/src/patient-card/styles.scss +45 -0
- package/translations/en.json +54 -4
- package/tsconfig.json +26 -23
- package/.eslintrc +0 -4
- package/.github/workflows/node.js.yml +0 -79
- package/.husky/pre-commit +0 -6
- package/dist/24.js +0 -3
- package/dist/24.js.LICENSE.txt +0 -16
- package/dist/24.js.map +0 -1
- package/dist/294.js +0 -3
- package/dist/294.js.LICENSE.txt +0 -14
- package/dist/294.js.map +0 -1
- package/dist/296.js +0 -2
- package/dist/296.js.map +0 -1
- package/dist/299.js +0 -2
- package/dist/299.js.map +0 -1
- package/dist/382.js +0 -3
- package/dist/382.js.LICENSE.txt +0 -8
- package/dist/382.js.map +0 -1
- package/dist/415.js +0 -2
- package/dist/415.js.map +0 -1
- package/dist/574.js +0 -1
- package/dist/595.js +0 -3
- package/dist/595.js.LICENSE.txt +0 -1
- package/dist/595.js.map +0 -1
- package/dist/69.js +0 -2
- package/dist/69.js.map +0 -1
- package/dist/735.js +0 -3
- package/dist/735.js.LICENSE.txt +0 -29
- package/dist/735.js.map +0 -1
- package/dist/777.js +0 -2
- package/dist/777.js.map +0 -1
- package/dist/860.js +0 -2
- package/dist/860.js.map +0 -1
- package/dist/906.js +0 -2
- package/dist/906.js.map +0 -1
- package/dist/openmrs-esm-fast-data-entry-app.js.buildmanifest.json +0 -369
- package/dist/openmrs-esm-fast-data-entry-app.js.map +0 -1
- package/dist/openmrs-esm-fast-data-entry-app.old +0 -2
- package/src/boxes/extensions/blue-box.tsx +0 -15
- package/src/boxes/extensions/box.scss +0 -23
- package/src/boxes/extensions/brand-box.tsx +0 -15
- package/src/boxes/extensions/red-box.tsx +0 -15
- package/src/boxes/slot/boxes.css +0 -23
- package/src/boxes/slot/boxes.tsx +0 -19
- package/src/forms/FormsRoot.tsx +0 -32
- package/src/forms/FormsTable.tsx +0 -64
- package/src/forms/mockData.ts +0 -43
- package/src/greeter/greeter.css +0 -4
- package/src/greeter/greeter.test.tsx +0 -29
- package/src/greeter/greeter.tsx +0 -25
- package/src/hello.css +0 -3
- package/src/hello.test.tsx +0 -45
- package/src/hello.tsx +0 -30
- package/src/patient-getter/patient-getter.resource.ts +0 -31
- package/src/patient-getter/patient-getter.test.tsx +0 -28
- 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;
|