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

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