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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/README.md +58 -12
  2. package/dist/153.js +1 -0
  3. package/dist/153.js.map +1 -0
  4. package/dist/233.js +2 -0
  5. package/dist/{382.js.LICENSE.txt → 233.js.LICENSE.txt} +3 -2
  6. package/dist/233.js.map +1 -0
  7. package/dist/262.js +1 -0
  8. package/dist/262.js.map +1 -0
  9. package/dist/279.js +1 -0
  10. package/dist/279.js.map +1 -0
  11. package/dist/294.js +1 -2
  12. package/dist/294.js.LICENSE.txt +2 -7
  13. package/dist/294.js.map +1 -1
  14. package/dist/327.js +1 -0
  15. package/dist/327.js.map +1 -0
  16. package/dist/409.js +2 -0
  17. package/dist/409.js.LICENSE.txt +27 -0
  18. package/dist/409.js.map +1 -0
  19. package/dist/415.js +1 -2
  20. package/dist/415.js.map +1 -1
  21. package/dist/559.js +1 -0
  22. package/dist/559.js.map +1 -0
  23. package/dist/574.js +1 -1
  24. package/dist/651.js +1 -0
  25. package/dist/651.js.map +1 -0
  26. package/dist/706.js +1 -0
  27. package/dist/706.js.map +1 -0
  28. package/dist/757.js +1 -0
  29. package/dist/800.js +2 -0
  30. package/dist/800.js.LICENSE.txt +5 -0
  31. package/dist/800.js.map +1 -0
  32. package/dist/820.js +1 -0
  33. package/dist/820.js.map +1 -0
  34. package/dist/883.js +1 -0
  35. package/dist/883.js.map +1 -0
  36. package/dist/889.js +1 -0
  37. package/dist/889.js.map +1 -0
  38. package/dist/897.js +2 -0
  39. package/dist/897.js.LICENSE.txt +21 -0
  40. package/dist/897.js.map +1 -0
  41. package/dist/92.js +1 -0
  42. package/dist/92.js.map +1 -0
  43. package/dist/935.js +2 -0
  44. package/dist/{735.js.LICENSE.txt → 935.js.LICENSE.txt} +6 -16
  45. package/dist/935.js.map +1 -0
  46. package/dist/959.js +1 -0
  47. package/dist/959.js.map +1 -0
  48. package/dist/main.js +1 -0
  49. package/dist/main.js.map +1 -0
  50. package/dist/openmrs-esm-fast-data-entry-app.js +1 -1
  51. package/dist/openmrs-esm-fast-data-entry-app.js.buildmanifest.json +374 -89
  52. package/dist/openmrs-esm-fast-data-entry-app.js.map +1 -1
  53. package/dist/routes.json +1 -0
  54. package/docs/config-icrc-forms.png +0 -0
  55. package/docs/config-other-forms.png +0 -0
  56. package/docs/configuring-form-categories.md +77 -0
  57. package/docs/fde-workflow.mov +0 -0
  58. package/docs/form-workflow-state-diagram.png +0 -0
  59. package/jest.config.json +21 -18
  60. package/package.json +100 -106
  61. package/src/CancelModal.tsx +48 -0
  62. package/src/CompleteModal.tsx +46 -0
  63. package/src/FormBootstrap.tsx +166 -0
  64. package/src/Root.tsx +14 -3
  65. package/src/add-group-modal/AddGroupModal.tsx +288 -0
  66. package/src/add-group-modal/styles.scss +45 -0
  67. package/src/config-schema.ts +85 -31
  68. package/src/context/FormWorkflowContext.tsx +126 -0
  69. package/src/context/FormWorkflowReducer.ts +287 -0
  70. package/src/context/GroupFormWorkflowContext.tsx +176 -0
  71. package/src/context/GroupFormWorkflowReducer.ts +430 -0
  72. package/src/empty-state/EmptyDataIllustration.tsx +51 -0
  73. package/src/empty-state/EmptyState.tsx +33 -0
  74. package/src/empty-state/styles.scss +55 -0
  75. package/src/form-entry-workflow/FormEntryWorkflow.tsx +196 -0
  76. package/src/form-entry-workflow/form-review-card/FormReviewCard.tsx +50 -0
  77. package/src/form-entry-workflow/form-review-card/index.ts +3 -0
  78. package/src/form-entry-workflow/form-review-card/styles.scss +39 -0
  79. package/src/form-entry-workflow/index.ts +3 -0
  80. package/src/form-entry-workflow/patient-banner/PatientBanner.test.tsx +9 -0
  81. package/src/form-entry-workflow/patient-banner/PatientBanner.tsx +86 -0
  82. package/src/form-entry-workflow/patient-banner/index.ts +3 -0
  83. package/src/form-entry-workflow/patient-banner/styles.scss +45 -0
  84. package/src/form-entry-workflow/patient-search-header/PatientSearchHeader.tsx +63 -0
  85. package/src/form-entry-workflow/patient-search-header/index.ts +3 -0
  86. package/src/form-entry-workflow/patient-search-header/styles.scss +22 -0
  87. package/src/form-entry-workflow/styles.scss +65 -0
  88. package/src/form-entry-workflow/workflow-review/WorkflowReview.tsx +35 -0
  89. package/src/form-entry-workflow/workflow-review/index.ts +3 -0
  90. package/src/form-entry-workflow/workflow-review/styles.scss +34 -0
  91. package/src/forms-app-menu-link.tsx +3 -2
  92. package/src/forms-page/FormsPage.tsx +134 -0
  93. package/src/forms-page/forms-table/FormsTable.tsx +137 -0
  94. package/src/forms-page/forms-table/index.ts +3 -0
  95. package/src/forms-page/forms-table/styles.scss +20 -0
  96. package/src/forms-page/index.ts +3 -0
  97. package/src/forms-page/styles.scss +11 -0
  98. package/src/group-form-entry-workflow/GroupFormEntryWorkflow.tsx +26 -0
  99. package/src/group-form-entry-workflow/GroupSessionWorkspace.tsx +247 -0
  100. package/src/group-form-entry-workflow/SessionDetailsForm.tsx +131 -0
  101. package/src/group-form-entry-workflow/SessionMetaWorkspace.tsx +107 -0
  102. package/src/group-form-entry-workflow/attendance-table/AttendanceTable.tsx +144 -0
  103. package/src/group-form-entry-workflow/attendance-table/index.ts +1 -0
  104. package/src/group-form-entry-workflow/group-display-header/GroupDisplayHeader.test.tsx +9 -0
  105. package/src/group-form-entry-workflow/group-display-header/GroupDisplayHeader.tsx +63 -0
  106. package/src/group-form-entry-workflow/group-display-header/index.ts +3 -0
  107. package/src/group-form-entry-workflow/group-display-header/styles.scss +60 -0
  108. package/src/group-form-entry-workflow/group-search/CompactGroupResults.tsx +139 -0
  109. package/src/group-form-entry-workflow/group-search/CompactGroupSearch.tsx +68 -0
  110. package/src/group-form-entry-workflow/group-search/GroupSearch.tsx +150 -0
  111. package/src/group-form-entry-workflow/group-search/compact-group-result.scss +64 -0
  112. package/src/group-form-entry-workflow/group-search/compact-group-search.scss +35 -0
  113. package/src/group-form-entry-workflow/group-search/group-search.scss +96 -0
  114. package/src/group-form-entry-workflow/group-search-header/GroupSearchHeader.tsx +73 -0
  115. package/src/group-form-entry-workflow/group-search-header/index.ts +3 -0
  116. package/src/group-form-entry-workflow/group-search-header/styles.scss +20 -0
  117. package/src/group-form-entry-workflow/index.ts +3 -0
  118. package/src/group-form-entry-workflow/styles.scss +97 -0
  119. package/src/hooks/index.ts +7 -0
  120. package/src/hooks/useFormState.ts +23 -0
  121. package/src/hooks/useGetAllForms.ts +45 -0
  122. package/src/hooks/useGetEncounter.ts +21 -0
  123. package/src/hooks/useGetPatient.ts +23 -0
  124. package/src/hooks/useGetPatients.ts +34 -0
  125. package/src/hooks/useGetSystemSetting.ts +38 -0
  126. package/src/hooks/useKeyPress.ts +31 -0
  127. package/src/hooks/usePostEndpoint.ts +76 -0
  128. package/src/hooks/useSearchEndpoint.ts +120 -0
  129. package/src/hooks/useStartVisit.ts +92 -0
  130. package/src/index.ts +26 -62
  131. package/src/patient-card/PatientCard.tsx +67 -0
  132. package/src/patient-card/index.ts +3 -0
  133. package/src/patient-card/styles.scss +46 -0
  134. package/src/routes.json +24 -0
  135. package/tools/i18next-parser.config.js +93 -0
  136. package/translations/en.json +69 -4
  137. package/translations/fr.json +50 -0
  138. package/tsconfig.json +26 -23
  139. package/.editorconfig +0 -12
  140. package/.eslintignore +0 -2
  141. package/.eslintrc +0 -4
  142. package/.github/workflows/node.js.yml +0 -79
  143. package/.husky/pre-commit +0 -6
  144. package/.husky/pre-push +0 -6
  145. package/.prettierignore +0 -14
  146. package/dist/24.js +0 -3
  147. package/dist/24.js.LICENSE.txt +0 -16
  148. package/dist/24.js.map +0 -1
  149. package/dist/296.js +0 -2
  150. package/dist/296.js.map +0 -1
  151. package/dist/299.js +0 -2
  152. package/dist/299.js.map +0 -1
  153. package/dist/382.js +0 -3
  154. package/dist/382.js.map +0 -1
  155. package/dist/595.js +0 -3
  156. package/dist/595.js.LICENSE.txt +0 -1
  157. package/dist/595.js.map +0 -1
  158. package/dist/69.js +0 -2
  159. package/dist/69.js.map +0 -1
  160. package/dist/735.js +0 -3
  161. package/dist/735.js.map +0 -1
  162. package/dist/777.js +0 -2
  163. package/dist/777.js.map +0 -1
  164. package/dist/860.js +0 -2
  165. package/dist/860.js.map +0 -1
  166. package/dist/906.js +0 -2
  167. package/dist/906.js.map +0 -1
  168. package/dist/openmrs-esm-fast-data-entry-app.old +0 -2
  169. package/src/boxes/extensions/blue-box.tsx +0 -15
  170. package/src/boxes/extensions/box.scss +0 -23
  171. package/src/boxes/extensions/brand-box.tsx +0 -15
  172. package/src/boxes/extensions/red-box.tsx +0 -15
  173. package/src/boxes/slot/boxes.css +0 -23
  174. package/src/boxes/slot/boxes.tsx +0 -19
  175. package/src/forms/FormsRoot.tsx +0 -32
  176. package/src/forms/FormsTable.tsx +0 -64
  177. package/src/forms/mockData.ts +0 -43
  178. package/src/greeter/greeter.css +0 -4
  179. package/src/greeter/greeter.test.tsx +0 -29
  180. package/src/greeter/greeter.tsx +0 -25
  181. package/src/hello.css +0 -3
  182. package/src/hello.test.tsx +0 -45
  183. package/src/hello.tsx +0 -30
  184. package/src/patient-getter/patient-getter.resource.ts +0 -31
  185. package/src/patient-getter/patient-getter.test.tsx +0 -28
  186. package/src/patient-getter/patient-getter.tsx +0 -28
@@ -0,0 +1,166 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import { detach, ExtensionSlot } from "@openmrs/esm-framework";
3
+ import useGetPatient from "./hooks/useGetPatient";
4
+
5
+ export interface Order {
6
+ uuid: string;
7
+ dateActivated: string;
8
+ dose: number;
9
+ doseUnits: {
10
+ uuid: string;
11
+ display: string;
12
+ };
13
+ orderNumber: number;
14
+ display: string;
15
+ drug: {
16
+ uuid: string;
17
+ name: string;
18
+ strength: string;
19
+ };
20
+ duration: number;
21
+ durationUnits: {
22
+ uuid: string;
23
+ display: string;
24
+ };
25
+ frequency: {
26
+ uuid: string;
27
+ display: string;
28
+ };
29
+ numRefills: number;
30
+ orderer: {
31
+ uuid: string;
32
+ person: {
33
+ uuid: string;
34
+ display: string;
35
+ };
36
+ };
37
+ orderType: {
38
+ uuid: string;
39
+ display: string;
40
+ };
41
+ route: {
42
+ uuid: string;
43
+ display: string;
44
+ };
45
+ auditInfo: {
46
+ dateVoided: string;
47
+ };
48
+ }
49
+
50
+ export interface Observation {
51
+ uuid: string;
52
+ concept: {
53
+ uuid: string;
54
+ display: string;
55
+ conceptClass: {
56
+ uuid: string;
57
+ display: string;
58
+ };
59
+ };
60
+ display: string;
61
+ groupMembers: null | Array<{
62
+ uuid: string;
63
+ concept: {
64
+ uuid: string;
65
+ display: string;
66
+ };
67
+ value: {
68
+ uuid: string;
69
+ display: string;
70
+ };
71
+ }>;
72
+ value: unknown;
73
+ obsDatetime: string;
74
+ }
75
+
76
+ export interface Encounter {
77
+ uuid: string;
78
+ encounterDatetime: string;
79
+ encounterProviders: Array<{
80
+ uuid: string;
81
+ display: string;
82
+ encounterRole: {
83
+ uuid: string;
84
+ display: string;
85
+ };
86
+ provider: {
87
+ uuid: string;
88
+ person: {
89
+ uuid: string;
90
+ display: string;
91
+ };
92
+ };
93
+ }>;
94
+ encounterType: {
95
+ uuid: string;
96
+ display: string;
97
+ };
98
+ obs: Array<Observation>;
99
+ orders: Array<Order>;
100
+ }
101
+ interface FormParams {
102
+ formUuid: string;
103
+ patientUuid: string;
104
+ visitUuid?: string;
105
+ visitTypeUuid?: string;
106
+ encounterUuid?: string;
107
+ showDiscardSubmitButtons?: boolean;
108
+ handlePostResponse?: (Encounter) => void;
109
+ handleEncounterCreate?: (Object) => void;
110
+ handleOnValidate?: (boolean) => void;
111
+ }
112
+
113
+ const FormBootstrap = ({
114
+ formUuid,
115
+ patientUuid,
116
+ visitUuid,
117
+ visitTypeUuid,
118
+ encounterUuid,
119
+ handlePostResponse,
120
+ handleEncounterCreate,
121
+ handleOnValidate,
122
+ }: FormParams) => {
123
+ const patient = useGetPatient(patientUuid);
124
+
125
+ useEffect(() => {
126
+ return () => detach("form-widget-slot", "form-widget-slot");
127
+ });
128
+
129
+ // FIXME This should not be necessary
130
+ const [showForm, setShowForm] = useState(true);
131
+
132
+ useEffect(() => {
133
+ if (patientUuid && formUuid && patient) {
134
+ setShowForm(false);
135
+ setTimeout(() => {
136
+ setShowForm(true);
137
+ });
138
+ }
139
+ }, [patientUuid, formUuid, patient]);
140
+
141
+ return (
142
+ <div>
143
+ {showForm && formUuid && patientUuid && patient && (
144
+ <ExtensionSlot
145
+ name="form-widget-slot"
146
+ state={{
147
+ view: "form",
148
+ formUuid,
149
+ visitUuid: visitUuid ?? "",
150
+ visitTypeUuid: visitTypeUuid ?? "",
151
+ patientUuid,
152
+ patient,
153
+ encounterUuid: encounterUuid ?? "",
154
+ closeWorkspace: () => undefined,
155
+ handlePostResponse,
156
+ handleEncounterCreate,
157
+ handleOnValidate,
158
+ showDiscardSubmitButtons: false,
159
+ }}
160
+ />
161
+ )}
162
+ </div>
163
+ );
164
+ };
165
+
166
+ export default FormBootstrap;
package/src/Root.tsx CHANGED
@@ -1,13 +1,24 @@
1
1
  import React from "react";
2
- import { BrowserRouter, Route } from "react-router-dom";
2
+ import { BrowserRouter, Route, Routes } from "react-router-dom";
3
3
  import { appPath } from "./constant";
4
- import FormsRoot from "./forms/FormsRoot";
4
+ const FormsPage = React.lazy(() => import("./forms-page"));
5
+ const FormEntryWorkflow = React.lazy(() => import("./form-entry-workflow"));
6
+ const GroupFormEntryWorkflow = React.lazy(
7
+ () => import("./group-form-entry-workflow")
8
+ );
5
9
 
6
10
  const Root = () => {
7
11
  return (
8
12
  <main>
9
13
  <BrowserRouter basename={appPath}>
10
- <Route path="/:tab?" component={FormsRoot} />
14
+ <Routes>
15
+ <Route path="/" element={<FormsPage />} />
16
+ <Route path="/form/:formUuid" element={<FormEntryWorkflow />} />
17
+ <Route
18
+ path="/groupform/:formUuid"
19
+ element={<GroupFormEntryWorkflow />}
20
+ />
21
+ </Routes>
11
22
  </BrowserRouter>
12
23
  </main>
13
24
  );
@@ -0,0 +1,288 @@
1
+ import React, {
2
+ useCallback,
3
+ useContext,
4
+ useEffect,
5
+ useMemo,
6
+ useState,
7
+ } from "react";
8
+ import {
9
+ ComposedModal,
10
+ Button,
11
+ ModalHeader,
12
+ ModalFooter,
13
+ ModalBody,
14
+ TextInput,
15
+ FormLabel,
16
+ } from "@carbon/react";
17
+ import { TrashCan } from "@carbon/react/icons";
18
+ import { useTranslation } from "react-i18next";
19
+ import { ExtensionSlot, showToast } from "@openmrs/esm-framework";
20
+ import styles from "./styles.scss";
21
+ import GroupFormWorkflowContext from "../context/GroupFormWorkflowContext";
22
+ import { usePostCohort } from "../hooks";
23
+
24
+ const MemExtension = React.memo(ExtensionSlot);
25
+
26
+ const buildPatientDisplay = (patient) => {
27
+ const givenName = patient?.name?.[0]?.given?.[0];
28
+ const familyName = patient?.name?.[0]?.family;
29
+ const identifier = patient?.identifier?.[0]?.value;
30
+
31
+ let display = identifier ? identifier + " - " : "";
32
+ display += (givenName || "") + " " + (familyName || "");
33
+ return display.replace(/\s+/g, " ");
34
+ };
35
+
36
+ const PatientRow = ({ patient, removePatient }) => {
37
+ const { t } = useTranslation();
38
+ const onClickHandler = useCallback(
39
+ () => removePatient(patient?.uuid),
40
+ [patient, removePatient]
41
+ );
42
+ const patientDisplay = useMemo(() => {
43
+ if (!patient) {
44
+ return "";
45
+ }
46
+
47
+ if (patient.display) {
48
+ return patient.display;
49
+ }
50
+
51
+ return buildPatientDisplay(patient);
52
+ }, [patient]);
53
+
54
+ return (
55
+ <li key={patient?.uuid} className={styles.patientRow}>
56
+ <span>
57
+ <Button
58
+ kind="tertiary"
59
+ size="sm"
60
+ hasIconOnly
61
+ onClick={onClickHandler}
62
+ renderIcon={TrashCan}
63
+ tooltipAlignment="start"
64
+ tooltipPosition="top"
65
+ iconDescription={t("remove", "Remove")}
66
+ />
67
+ </span>
68
+ <span className={styles.patientName}>{patientDisplay}</span>
69
+ </li>
70
+ );
71
+ };
72
+
73
+ const NewGroupForm = (props) => {
74
+ const {
75
+ name,
76
+ setName,
77
+ patientList,
78
+ updatePatientList,
79
+ errors,
80
+ validate,
81
+ removePatient,
82
+ } = props;
83
+ const { t } = useTranslation();
84
+
85
+ return (
86
+ <div
87
+ style={{
88
+ display: "flex",
89
+ flexDirection: "column",
90
+ rowGap: "1rem",
91
+ }}
92
+ >
93
+ <TextInput
94
+ labelText={t("newGroupName", "New Group Name")}
95
+ value={name}
96
+ onChange={(e) => setName(e.target.value)}
97
+ onBlur={() => validate("name")}
98
+ />
99
+ {errors?.name && (
100
+ <p className={styles.formError}>
101
+ {errors.name === "required"
102
+ ? t("groupNameError", "Please enter a group name.")
103
+ : errors.name}
104
+ </p>
105
+ )}
106
+ <FormLabel>
107
+ {patientList.length} {t("patientsInGroup", "Patients in group")}
108
+ </FormLabel>
109
+ {errors?.patientList && (
110
+ <p className={styles.formError}>
111
+ {t("noPatientError", "Please enter at least one patient.")}
112
+ </p>
113
+ )}
114
+ {!errors?.patientList && (
115
+ <ul className={styles.patientList}>
116
+ {patientList?.map((patient, index) => (
117
+ <PatientRow
118
+ patient={patient}
119
+ removePatient={removePatient}
120
+ key={index}
121
+ />
122
+ ))}
123
+ </ul>
124
+ )}
125
+
126
+ <FormLabel>Search for patients to add to group</FormLabel>
127
+ <div className={styles.searchBar}>
128
+ <MemExtension
129
+ extensionSlotName="patient-search-bar-slot"
130
+ state={{
131
+ selectPatientAction: updatePatientList,
132
+ buttonProps: {
133
+ kind: "secondary",
134
+ },
135
+ }}
136
+ />
137
+ </div>
138
+ </div>
139
+ );
140
+ };
141
+
142
+ const AddGroupModal = ({
143
+ patients = undefined,
144
+ isCreate = undefined,
145
+ groupName = "",
146
+ cohortUuid = undefined,
147
+ isOpen,
148
+ handleCancel,
149
+ onPostSubmit,
150
+ }) => {
151
+ const { setGroup } = useContext(GroupFormWorkflowContext);
152
+ const { t } = useTranslation();
153
+ const [errors, setErrors] = useState({});
154
+ const [name, setName] = useState(groupName);
155
+ const [patientList, setPatientList] = useState(patients || []);
156
+ const { post, result, error } = usePostCohort();
157
+
158
+ const removePatient = useCallback(
159
+ (patientUuid: string) =>
160
+ setPatientList((patientList) =>
161
+ patientList.filter((patient) => patient.uuid !== patientUuid)
162
+ ),
163
+ [setPatientList]
164
+ );
165
+
166
+ const validate = useCallback(
167
+ (field?: string | undefined) => {
168
+ let valid = true;
169
+ if (field) {
170
+ valid = field === "name" ? !!name : !!patientList.length;
171
+ setErrors((errors) => ({
172
+ ...errors,
173
+ [field]: valid ? null : "required",
174
+ }));
175
+ } else {
176
+ if (!name) {
177
+ setErrors((errors) => ({ ...errors, name: "required" }));
178
+ valid = false;
179
+ } else {
180
+ setErrors((errors) => ({ ...errors, name: null }));
181
+ }
182
+ if (!patientList.length) {
183
+ setErrors((errors) => ({ ...errors, patientList: "required" }));
184
+ valid = false;
185
+ } else {
186
+ setErrors((errors) => ({ ...errors, patientList: null }));
187
+ }
188
+ }
189
+ return valid;
190
+ },
191
+ [name, patientList.length]
192
+ );
193
+
194
+ const updatePatientList = useCallback(
195
+ (patient) => {
196
+ setPatientList((patientList) => {
197
+ if (!patientList.find((p) => p.uuid === patient.uuid)) {
198
+ return [...patientList, patient];
199
+ } else {
200
+ return patientList;
201
+ }
202
+ });
203
+ setErrors((errors) => ({ ...errors, patientList: null }));
204
+ },
205
+ [setPatientList]
206
+ );
207
+
208
+ const handleSubmit = () => {
209
+ if (validate()) {
210
+ post({
211
+ uuid: cohortUuid,
212
+ name: name,
213
+ cohortMembers: patientList.map((p) => ({ patient: p.uuid })),
214
+ });
215
+ if (onPostSubmit) {
216
+ onPostSubmit();
217
+ }
218
+ }
219
+ };
220
+
221
+ useEffect(() => {
222
+ if (result) {
223
+ setGroup({
224
+ ...result,
225
+ // the result doesn't come with cohortMembers.
226
+ // need to add it in based on our local state
227
+ cohortMembers: patientList.map((p) => ({ patient: { uuid: p.uuid } })),
228
+ });
229
+ }
230
+ }, [result, setGroup, patientList]);
231
+
232
+ useEffect(() => {
233
+ if (error) {
234
+ showToast({
235
+ kind: "error",
236
+ title: t("postError", "POST Error"),
237
+ description:
238
+ error.message ??
239
+ t("unknownPostError", "An unknown error occurred while saving data"),
240
+ });
241
+ if (error.fieldErrors) {
242
+ setErrors(
243
+ Object.fromEntries(
244
+ Object.entries(error.fieldErrors).map(([key, value]) => [
245
+ key,
246
+ value?.[0]?.message,
247
+ ])
248
+ )
249
+ );
250
+ }
251
+ }
252
+ }, [error, t]);
253
+
254
+ return (
255
+ <div className={styles.modal}>
256
+ <ComposedModal open={isOpen} onClose={handleCancel}>
257
+ <ModalHeader>
258
+ {isCreate
259
+ ? t("createNewGroup", "Create New Group")
260
+ : t("editGroup", "Edit Group")}
261
+ </ModalHeader>
262
+ <ModalBody>
263
+ <NewGroupForm
264
+ {...{
265
+ name,
266
+ setName,
267
+ patientList,
268
+ updatePatientList,
269
+ errors,
270
+ validate,
271
+ removePatient,
272
+ }}
273
+ />
274
+ </ModalBody>
275
+ <ModalFooter>
276
+ <Button kind="secondary" onClick={handleCancel}>
277
+ {t("cancel", "Cancel")}
278
+ </Button>
279
+ <Button kind="primary" onClick={handleSubmit}>
280
+ {isCreate ? t("createGroup", "Create Group") : t("save", "Save")}
281
+ </Button>
282
+ </ModalFooter>
283
+ </ComposedModal>
284
+ </div>
285
+ );
286
+ };
287
+
288
+ export default AddGroupModal;
@@ -0,0 +1,45 @@
1
+ @use '@carbon/styles/scss/spacing';
2
+ @use '@carbon/colors';
3
+
4
+ .modal {
5
+ :global(.cds--modal) {
6
+ z-index: 90;
7
+ }
8
+ :global(.cds--modal-container) {
9
+ height: 600px;
10
+ }
11
+ :global(.cds--modal-content) {
12
+ height: 100%;
13
+ }
14
+ }
15
+
16
+ .searchBar > div > div > div {
17
+ width: 100%
18
+ }
19
+
20
+ .formError {
21
+ color: red;
22
+ }
23
+
24
+ .patientRow {
25
+ display: flex;
26
+ align-items: center;
27
+ width: "100%";
28
+ &:nth-child(odd) {
29
+ background-color: colors.$gray-20;
30
+ }
31
+ }
32
+
33
+ .patientName {
34
+ flex-grow: 1;
35
+ padding-left: spacing.$spacing-05;
36
+ }
37
+
38
+ .loading {
39
+ display: flex;
40
+ height: 100%;
41
+ flex-direction: column;
42
+ justify-content: center;
43
+ align-items: center;
44
+ row-gap: spacing.$spacing-05;
45
+ }
@@ -1,46 +1,100 @@
1
- import { Type, validator } from "@openmrs/esm-framework";
1
+ import { Type } from "@openmrs/esm-framework";
2
2
 
3
3
  /**
4
- * This is the config schema. It expects a configuration object which
5
- * looks like this:
4
+ * This is the config schema.
6
5
  *
7
- * ```json
8
- * { "casualGreeting": true, "whoToGreet": ["Mom"] }
9
- * ```
10
- *
11
- * In OpenMRS Microfrontends, all config parameters are optional. Thus,
12
- * all elements must have a reasonable default. A good default is one
13
- * that works well with the reference application.
14
- *
15
- * To understand the schema below, please read the configuration system
16
- * documentation:
17
- * https://openmrs.github.io/openmrs-esm-core/#/main/config
18
- * Note especially the section "How do I make my module configurable?"
19
- * https://openmrs.github.io/openmrs-esm-core/#/main/config?id=im-developing-an-esm-module-how-do-i-make-it-configurable
20
- * and the Schema Reference
21
- * https://openmrs.github.io/openmrs-esm-core/#/main/config?id=schema-reference
22
6
  */
7
+
23
8
  export const configSchema = {
24
- casualGreeting: {
25
- _type: Type.Boolean,
26
- _default: false,
27
- _description: "Whether to use a casual greeting (or a formal one).",
28
- },
29
- whoToGreet: {
9
+ formCategories: {
30
10
  _type: Type.Array,
31
- _default: ["World"],
32
11
  _description:
33
- "Who should be greeted. Names will be separated by a comma and space.",
12
+ "Organize forms into categories. A form can belong to multiple categories.",
34
13
  _elements: {
35
- _type: Type.String,
14
+ name: {
15
+ _type: Type.String,
16
+ _description: "Category name",
17
+ },
18
+ forms: {
19
+ _type: Type.Array,
20
+ _description: "List of forms for this category.",
21
+ _elements: {
22
+ formUUID: {
23
+ _type: Type.UUID,
24
+ _description: "UUID of form",
25
+ },
26
+ name: {
27
+ _type: Type.String,
28
+ _description: "Name of form",
29
+ },
30
+ },
31
+ },
36
32
  },
37
- _validators: [
38
- validator((v) => v.length > 0, "At least one person must be greeted."),
33
+ _default: [
34
+ {
35
+ name: "ICRC Forms",
36
+ forms: [
37
+ {
38
+ formUUID: "0cefb866-110c-4f16-af58-560932a1db1f",
39
+ name: "Adult Triage",
40
+ },
41
+ ],
42
+ },
43
+ {
44
+ name: "Distress Scales",
45
+ forms: [
46
+ {
47
+ formUUID: "9f26aad4-244a-46ca-be49-1196df1a8c9a",
48
+ name: "POC Sample Form 1",
49
+ },
50
+ ],
51
+ },
39
52
  ],
40
53
  },
54
+ formCategoriesToShow: {
55
+ _type: Type.Array,
56
+ _description: "Forms to show by default on the forms app home page.",
57
+ _elements: {
58
+ _type: Type.String,
59
+ _description: "Name of category",
60
+ },
61
+ _default: ["ICRC Forms", "Distress Scales"],
62
+ },
63
+ groupSessionConcepts: {
64
+ sessionName: {
65
+ _type: Type.UUID,
66
+ _description: "UUID of concept for Session Name",
67
+ _default: "e2559620-900b-4f66-ae41-0b9c4adfb654",
68
+ },
69
+ sessionDate: {
70
+ _type: Type.UUID,
71
+ _description: "UUID of concept for Session Date",
72
+ _default: "ceaca505-6dff-4940-8a43-8c060a0924d7",
73
+ },
74
+ practitionerName: {
75
+ _type: Type.UUID,
76
+ _description: "UUID of concept for Practitioner Name",
77
+ _default: "f1a2d58c-1a0e-4148-931a-aac224649fdc",
78
+ },
79
+ sessionNotes: {
80
+ _type: Type.UUID,
81
+ _description: "UUID of concept for Session Notes",
82
+ _default: "fa8fedc0-c066-4da3-8dc1-2ad8621fc480",
83
+ },
84
+ },
85
+ };
86
+
87
+ export type Form = {
88
+ formUUID: Type.UUID;
89
+ name: Type.String;
90
+ };
91
+
92
+ export type Category = {
93
+ name: string;
94
+ forms: Array<Form>;
41
95
  };
42
96
 
43
97
  export type Config = {
44
- casualGreeting: boolean;
45
- whoToGreet: Array<String>;
98
+ formCategories: Array<Category>;
99
+ formCategoriesToShow: Array<string>;
46
100
  };