@openmrs/esm-fast-data-entry-app 1.0.1-pre.10 → 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.
- package/README.md +21 -2
- package/dist/153.js +1 -0
- package/dist/153.js.map +1 -0
- package/dist/233.js +2 -0
- package/dist/233.js.LICENSE.txt +9 -0
- package/dist/233.js.map +1 -0
- package/dist/262.js +1 -0
- package/dist/262.js.map +1 -0
- package/dist/279.js +1 -0
- package/dist/279.js.map +1 -0
- package/dist/294.js +2 -0
- package/dist/294.js.LICENSE.txt +9 -0
- package/dist/294.js.map +1 -0
- package/dist/327.js +1 -0
- package/dist/327.js.map +1 -0
- package/dist/409.js +2 -0
- package/dist/409.js.LICENSE.txt +27 -0
- package/dist/409.js.map +1 -0
- package/dist/415.js +1 -0
- package/dist/415.js.map +1 -0
- package/dist/559.js +1 -0
- package/dist/559.js.map +1 -0
- package/dist/574.js +1 -0
- package/dist/651.js +1 -0
- package/dist/651.js.map +1 -0
- package/dist/706.js +1 -0
- package/dist/706.js.map +1 -0
- package/dist/757.js +1 -0
- package/dist/800.js +2 -0
- package/dist/800.js.LICENSE.txt +5 -0
- package/dist/800.js.map +1 -0
- package/dist/820.js +1 -0
- package/dist/820.js.map +1 -0
- package/dist/883.js +1 -0
- package/dist/883.js.map +1 -0
- package/dist/889.js +1 -0
- package/dist/889.js.map +1 -0
- package/dist/897.js +2 -0
- package/dist/897.js.LICENSE.txt +21 -0
- package/dist/897.js.map +1 -0
- package/dist/92.js +1 -0
- package/dist/92.js.map +1 -0
- package/dist/935.js +2 -0
- package/dist/935.js.LICENSE.txt +19 -0
- package/dist/935.js.map +1 -0
- package/dist/959.js +1 -0
- package/dist/959.js.map +1 -0
- package/dist/main.js +1 -0
- package/dist/main.js.map +1 -0
- package/dist/openmrs-esm-fast-data-entry-app.js +1 -1
- package/dist/openmrs-esm-fast-data-entry-app.js.buildmanifest.json +654 -0
- package/dist/openmrs-esm-fast-data-entry-app.js.map +1 -0
- package/dist/routes.json +1 -0
- package/jest.config.json +2 -1
- package/package.json +13 -10
- package/src/CancelModal.tsx +48 -0
- package/src/CompleteModal.tsx +46 -0
- package/src/FormBootstrap.tsx +18 -3
- package/src/add-group-modal/AddGroupModal.tsx +113 -34
- package/src/add-group-modal/styles.scss +14 -4
- package/src/config-schema.ts +22 -0
- package/src/context/FormWorkflowContext.tsx +13 -1
- package/src/context/FormWorkflowReducer.ts +13 -3
- package/src/context/GroupFormWorkflowContext.tsx +41 -6
- package/src/context/GroupFormWorkflowReducer.ts +170 -12
- package/src/form-entry-workflow/FormEntryWorkflow.tsx +67 -101
- package/src/form-entry-workflow/styles.scss +2 -1
- package/src/forms-page/FormsPage.tsx +8 -3
- package/src/forms-page/forms-table/FormsTable.tsx +11 -5
- package/src/group-form-entry-workflow/GroupFormEntryWorkflow.tsx +13 -400
- package/src/group-form-entry-workflow/GroupSessionWorkspace.tsx +247 -0
- package/src/group-form-entry-workflow/SessionDetailsForm.tsx +131 -0
- package/src/group-form-entry-workflow/SessionMetaWorkspace.tsx +107 -0
- package/src/group-form-entry-workflow/attendance-table/AttendanceTable.tsx +144 -0
- package/src/group-form-entry-workflow/attendance-table/index.ts +1 -0
- package/src/group-form-entry-workflow/{group-banner/GroupBanner.test.tsx → group-display-header/GroupDisplayHeader.test.tsx} +2 -2
- package/src/group-form-entry-workflow/{group-banner/GroupBanner.tsx → group-display-header/GroupDisplayHeader.tsx} +23 -5
- package/src/group-form-entry-workflow/group-display-header/index.ts +3 -0
- package/src/group-form-entry-workflow/group-search/CompactGroupResults.tsx +61 -28
- package/src/group-form-entry-workflow/group-search/CompactGroupSearch.tsx +5 -0
- package/src/group-form-entry-workflow/group-search/GroupSearch.tsx +65 -8
- package/src/group-form-entry-workflow/group-search/group-search.scss +8 -6
- package/src/group-form-entry-workflow/group-search-header/GroupSearchHeader.tsx +41 -10
- package/src/group-form-entry-workflow/styles.scss +12 -1
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useGetPatient.ts +1 -1
- package/src/hooks/useGetPatients.ts +34 -0
- package/src/hooks/useGetSystemSetting.ts +38 -0
- package/src/hooks/usePostEndpoint.ts +76 -0
- package/src/hooks/useSearchEndpoint.ts +120 -0
- package/src/hooks/useStartVisit.ts +92 -0
- package/src/index.ts +13 -65
- package/src/patient-card/styles.scss +1 -0
- package/src/routes.json +24 -0
- package/tools/i18next-parser.config.js +93 -0
- package/translations/en.json +29 -9
- package/translations/fr.json +50 -0
- package/.editorconfig +0 -12
- package/.eslintignore +0 -2
- package/.eslintrc.js +0 -10
- package/.husky/pre-push +0 -1
- package/.prettierignore +0 -14
- package/.tx/config +0 -9
- package/.yarn/plugins/@yarnpkg/plugin-version.cjs +0 -550
- package/.yarn/versions/45b499b6.yml +0 -0
- package/src/group-form-entry-workflow/group-banner/index.ts +0 -3
- package/src/group-form-entry-workflow/group-search/mock-group-data.ts +0 -79
- package/src/group-form-entry-workflow/group-search/useGroupSearch.ts +0 -14
- package/src/hooks/usePostCohort.ts +0 -18
- /package/src/group-form-entry-workflow/{group-banner → group-display-header}/styles.scss +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openmrs/esm-fast-data-entry-app",
|
|
3
|
-
"version": "1.0.1-pre.
|
|
3
|
+
"version": "1.0.1-pre.101",
|
|
4
4
|
"license": "MPL-2.0",
|
|
5
5
|
"description": "An OpenMRS 3.x microfrontend",
|
|
6
6
|
"browser": "dist/openmrs-esm-fast-data-entry-app.js",
|
|
@@ -17,12 +17,8 @@
|
|
|
17
17
|
"test": "jest --config jest.config.json --passWithNoTests",
|
|
18
18
|
"verify": "concurrently 'yarn:lint' 'yarn:test' 'yarn:typescript'",
|
|
19
19
|
"coverage": "yarn test -- --coverage ",
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
"husky": {
|
|
23
|
-
"hooks": {
|
|
24
|
-
"pre-commit": "pretty-quick --staged && yarn verify"
|
|
25
|
-
}
|
|
20
|
+
"postinstall": "husky install",
|
|
21
|
+
"extract-translations": "i18next 'src/**/*.tsx' --config ./tools/i18next-parser.config.js"
|
|
26
22
|
},
|
|
27
23
|
"browserslist": [
|
|
28
24
|
"extends browserslist-config-openmrs"
|
|
@@ -45,6 +41,7 @@
|
|
|
45
41
|
"peerDependencies": {
|
|
46
42
|
"@carbon/react": "^1.9.0",
|
|
47
43
|
"@openmrs/esm-framework": "*",
|
|
44
|
+
"lodash-es": "4.x",
|
|
48
45
|
"react": "18.x",
|
|
49
46
|
"react-dom": "18.x",
|
|
50
47
|
"react-i18next": "11.x",
|
|
@@ -53,6 +50,7 @@
|
|
|
53
50
|
"devDependencies": {
|
|
54
51
|
"@carbon/react": "^1.9.0",
|
|
55
52
|
"@openmrs/esm-framework": "next",
|
|
53
|
+
"@swc-node/loader": "^1.3.5",
|
|
56
54
|
"@swc/core": "^1.2.245",
|
|
57
55
|
"@swc/jest": "^0.2.22",
|
|
58
56
|
"@testing-library/dom": "^7.20.0",
|
|
@@ -66,6 +64,7 @@
|
|
|
66
64
|
"@types/webpack-env": "^1.16.0",
|
|
67
65
|
"@typescript-eslint/parser": "^5.14.0",
|
|
68
66
|
"concurrently": "^6.2.0",
|
|
67
|
+
"css-loader": "^6.8.1",
|
|
69
68
|
"eslint": "^8.20.0",
|
|
70
69
|
"eslint-config-prettier": "^8.3.0",
|
|
71
70
|
"eslint-config-react-app": "^7.0.1",
|
|
@@ -76,6 +75,7 @@
|
|
|
76
75
|
"jest": "^28.1.3",
|
|
77
76
|
"jest-cli": "^28.1.3",
|
|
78
77
|
"jest-environment-jsdom": "^28.1.3",
|
|
78
|
+
"lodash-es": "^4.17.21",
|
|
79
79
|
"openmrs": "next",
|
|
80
80
|
"prettier": "^2.3.0",
|
|
81
81
|
"pretty-quick": "^3.1.0",
|
|
@@ -85,13 +85,16 @@
|
|
|
85
85
|
"react-router-dom": "^6.3.0",
|
|
86
86
|
"semver": "^7.3.7",
|
|
87
87
|
"swc-loader": "^0.2.3",
|
|
88
|
-
"swr": "^1.3.0",
|
|
89
88
|
"typescript": "^4.7.3",
|
|
90
|
-
"webpack": "^5.73.0"
|
|
89
|
+
"webpack": "^5.73.0",
|
|
90
|
+
"webpack-cli": "^5.1.4"
|
|
91
91
|
},
|
|
92
92
|
"packageManager": "yarn@3.2.2",
|
|
93
93
|
"dependencies": {
|
|
94
|
-
"
|
|
94
|
+
"i18next": "^21.10.0",
|
|
95
|
+
"i18next-parser": "^6.6.0",
|
|
96
|
+
"react-hook-form": "^7.34.2",
|
|
97
|
+
"swr": "^2.1.3"
|
|
95
98
|
},
|
|
96
99
|
"stableVersion": "1.0.0"
|
|
97
100
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Button,
|
|
3
|
+
ComposedModal,
|
|
4
|
+
ModalBody,
|
|
5
|
+
ModalFooter,
|
|
6
|
+
ModalHeader,
|
|
7
|
+
} from "@carbon/react";
|
|
8
|
+
import React from "react";
|
|
9
|
+
import { useTranslation } from "react-i18next";
|
|
10
|
+
|
|
11
|
+
const CancelModal = ({ open, setOpen, context }) => {
|
|
12
|
+
const { t } = useTranslation();
|
|
13
|
+
|
|
14
|
+
const onCancel = () => setOpen(false);
|
|
15
|
+
|
|
16
|
+
const onDiscard = async () => {
|
|
17
|
+
await context.destroySession();
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const onSaveAndClose = async () => {
|
|
21
|
+
await context.closeSession();
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<ComposedModal open={open}>
|
|
26
|
+
<ModalHeader>{t("areYouSure", "Are you sure?")}</ModalHeader>
|
|
27
|
+
<ModalBody>
|
|
28
|
+
{t(
|
|
29
|
+
"cancelExplanation",
|
|
30
|
+
"You will lose any unsaved changes on the current form. Do you want to discard the current session?"
|
|
31
|
+
)}
|
|
32
|
+
</ModalBody>
|
|
33
|
+
<ModalFooter>
|
|
34
|
+
<Button kind="secondary" onClick={onCancel}>
|
|
35
|
+
{t("cancel", "Cancel")}
|
|
36
|
+
</Button>
|
|
37
|
+
<Button kind="danger" onClick={onDiscard}>
|
|
38
|
+
{t("discard", "Discard")}
|
|
39
|
+
</Button>
|
|
40
|
+
<Button kind="primary" onClick={onSaveAndClose}>
|
|
41
|
+
{t("saveSession", "Save Session")}
|
|
42
|
+
</Button>
|
|
43
|
+
</ModalFooter>
|
|
44
|
+
</ComposedModal>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export default CancelModal;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Button,
|
|
3
|
+
ComposedModal,
|
|
4
|
+
ModalBody,
|
|
5
|
+
ModalFooter,
|
|
6
|
+
ModalHeader,
|
|
7
|
+
} from "@carbon/react";
|
|
8
|
+
import React from "react";
|
|
9
|
+
import { useTranslation } from "react-i18next";
|
|
10
|
+
|
|
11
|
+
const CompleteModal = ({ open, setOpen, context, validateFirst = false }) => {
|
|
12
|
+
const { t } = useTranslation();
|
|
13
|
+
|
|
14
|
+
const onCancel = () => setOpen(false);
|
|
15
|
+
|
|
16
|
+
const onComplete = () => {
|
|
17
|
+
if (validateFirst) {
|
|
18
|
+
context.validateForComplete();
|
|
19
|
+
} else {
|
|
20
|
+
context.submitForComplete();
|
|
21
|
+
}
|
|
22
|
+
setOpen(false);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<ComposedModal open={open}>
|
|
27
|
+
<ModalHeader>{t("areYouSure", "Are you sure?")}</ModalHeader>
|
|
28
|
+
<ModalBody>
|
|
29
|
+
{t(
|
|
30
|
+
"saveExplanation",
|
|
31
|
+
"Do you want to save the current form and exit the workflow?"
|
|
32
|
+
)}
|
|
33
|
+
</ModalBody>
|
|
34
|
+
<ModalFooter>
|
|
35
|
+
<Button kind="secondary" onClick={onCancel}>
|
|
36
|
+
{t("cancel", "Cancel")}
|
|
37
|
+
</Button>
|
|
38
|
+
<Button kind="primary" onClick={onComplete}>
|
|
39
|
+
{t("complete", "Complete")}
|
|
40
|
+
</Button>
|
|
41
|
+
</ModalFooter>
|
|
42
|
+
</ComposedModal>
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export default CompleteModal;
|
package/src/FormBootstrap.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useEffect } from "react";
|
|
1
|
+
import React, { useEffect, useState } from "react";
|
|
2
2
|
import { detach, ExtensionSlot } from "@openmrs/esm-framework";
|
|
3
3
|
import useGetPatient from "./hooks/useGetPatient";
|
|
4
4
|
|
|
@@ -107,6 +107,7 @@ interface FormParams {
|
|
|
107
107
|
showDiscardSubmitButtons?: boolean;
|
|
108
108
|
handlePostResponse?: (Encounter) => void;
|
|
109
109
|
handleEncounterCreate?: (Object) => void;
|
|
110
|
+
handleOnValidate?: (boolean) => void;
|
|
110
111
|
}
|
|
111
112
|
|
|
112
113
|
const FormBootstrap = ({
|
|
@@ -117,6 +118,7 @@ const FormBootstrap = ({
|
|
|
117
118
|
encounterUuid,
|
|
118
119
|
handlePostResponse,
|
|
119
120
|
handleEncounterCreate,
|
|
121
|
+
handleOnValidate,
|
|
120
122
|
}: FormParams) => {
|
|
121
123
|
const patient = useGetPatient(patientUuid);
|
|
122
124
|
|
|
@@ -124,11 +126,23 @@ const FormBootstrap = ({
|
|
|
124
126
|
return () => detach("form-widget-slot", "form-widget-slot");
|
|
125
127
|
});
|
|
126
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
|
+
|
|
127
141
|
return (
|
|
128
142
|
<div>
|
|
129
|
-
{formUuid && patientUuid && patient && (
|
|
143
|
+
{showForm && formUuid && patientUuid && patient && (
|
|
130
144
|
<ExtensionSlot
|
|
131
|
-
|
|
145
|
+
name="form-widget-slot"
|
|
132
146
|
state={{
|
|
133
147
|
view: "form",
|
|
134
148
|
formUuid,
|
|
@@ -140,6 +154,7 @@ const FormBootstrap = ({
|
|
|
140
154
|
closeWorkspace: () => undefined,
|
|
141
155
|
handlePostResponse,
|
|
142
156
|
handleEncounterCreate,
|
|
157
|
+
handleOnValidate,
|
|
143
158
|
showDiscardSubmitButtons: false,
|
|
144
159
|
}}
|
|
145
160
|
/>
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, {
|
|
2
|
+
useCallback,
|
|
3
|
+
useContext,
|
|
4
|
+
useEffect,
|
|
5
|
+
useMemo,
|
|
6
|
+
useState,
|
|
7
|
+
} from "react";
|
|
2
8
|
import {
|
|
3
9
|
ComposedModal,
|
|
4
10
|
Button,
|
|
@@ -8,30 +14,58 @@ import {
|
|
|
8
14
|
TextInput,
|
|
9
15
|
FormLabel,
|
|
10
16
|
} from "@carbon/react";
|
|
11
|
-
import {
|
|
17
|
+
import { TrashCan } from "@carbon/react/icons";
|
|
12
18
|
import { useTranslation } from "react-i18next";
|
|
13
|
-
import { ExtensionSlot } from "@openmrs/esm-framework";
|
|
19
|
+
import { ExtensionSlot, showToast } from "@openmrs/esm-framework";
|
|
14
20
|
import styles from "./styles.scss";
|
|
15
21
|
import GroupFormWorkflowContext from "../context/GroupFormWorkflowContext";
|
|
22
|
+
import { usePostCohort } from "../hooks";
|
|
16
23
|
|
|
17
24
|
const MemExtension = React.memo(ExtensionSlot);
|
|
18
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
|
+
|
|
19
36
|
const PatientRow = ({ patient, removePatient }) => {
|
|
20
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
|
+
|
|
21
54
|
return (
|
|
22
|
-
<li key={patient
|
|
23
|
-
<span className={styles.patientName}>{patient?.display}</span>
|
|
55
|
+
<li key={patient?.uuid} className={styles.patientRow}>
|
|
24
56
|
<span>
|
|
25
57
|
<Button
|
|
26
58
|
kind="tertiary"
|
|
27
59
|
size="sm"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
60
|
+
hasIconOnly
|
|
61
|
+
onClick={onClickHandler}
|
|
62
|
+
renderIcon={TrashCan}
|
|
63
|
+
tooltipAlignment="start"
|
|
64
|
+
tooltipPosition="top"
|
|
65
|
+
iconDescription={t("remove", "Remove")}
|
|
66
|
+
/>
|
|
34
67
|
</span>
|
|
68
|
+
<span className={styles.patientName}>{patientDisplay}</span>
|
|
35
69
|
</li>
|
|
36
70
|
);
|
|
37
71
|
};
|
|
@@ -64,17 +98,21 @@ const NewGroupForm = (props) => {
|
|
|
64
98
|
/>
|
|
65
99
|
{errors?.name && (
|
|
66
100
|
<p className={styles.formError}>
|
|
67
|
-
{
|
|
101
|
+
{errors.name === "required"
|
|
102
|
+
? t("groupNameError", "Please enter a group name.")
|
|
103
|
+
: errors.name}
|
|
68
104
|
</p>
|
|
69
105
|
)}
|
|
70
|
-
<FormLabel>
|
|
106
|
+
<FormLabel>
|
|
107
|
+
{patientList.length} {t("patientsInGroup", "Patients in group")}
|
|
108
|
+
</FormLabel>
|
|
71
109
|
{errors?.patientList && (
|
|
72
110
|
<p className={styles.formError}>
|
|
73
111
|
{t("noPatientError", "Please enter at least one patient.")}
|
|
74
112
|
</p>
|
|
75
113
|
)}
|
|
76
114
|
{!errors?.patientList && (
|
|
77
|
-
<ul>
|
|
115
|
+
<ul className={styles.patientList}>
|
|
78
116
|
{patientList?.map((patient, index) => (
|
|
79
117
|
<PatientRow
|
|
80
118
|
patient={patient}
|
|
@@ -92,7 +130,7 @@ const NewGroupForm = (props) => {
|
|
|
92
130
|
state={{
|
|
93
131
|
selectPatientAction: updatePatientList,
|
|
94
132
|
buttonProps: {
|
|
95
|
-
kind: "
|
|
133
|
+
kind: "secondary",
|
|
96
134
|
},
|
|
97
135
|
}}
|
|
98
136
|
/>
|
|
@@ -101,17 +139,21 @@ const NewGroupForm = (props) => {
|
|
|
101
139
|
);
|
|
102
140
|
};
|
|
103
141
|
|
|
104
|
-
const AddGroupModal = (
|
|
142
|
+
const AddGroupModal = ({
|
|
143
|
+
patients = undefined,
|
|
144
|
+
isCreate = undefined,
|
|
145
|
+
groupName = "",
|
|
146
|
+
cohortUuid = undefined,
|
|
147
|
+
isOpen,
|
|
148
|
+
handleCancel,
|
|
149
|
+
onPostSubmit,
|
|
150
|
+
}) => {
|
|
105
151
|
const { setGroup } = useContext(GroupFormWorkflowContext);
|
|
106
152
|
const { t } = useTranslation();
|
|
107
|
-
const [open, setOpen] = useState(false);
|
|
108
153
|
const [errors, setErrors] = useState({});
|
|
109
|
-
const [name, setName] = useState(
|
|
110
|
-
const [patientList, setPatientList] = useState([]);
|
|
111
|
-
|
|
112
|
-
const handleCancel = () => {
|
|
113
|
-
setOpen(false);
|
|
114
|
-
};
|
|
154
|
+
const [name, setName] = useState(groupName);
|
|
155
|
+
const [patientList, setPatientList] = useState(patients || []);
|
|
156
|
+
const { post, result, error } = usePostCohort();
|
|
115
157
|
|
|
116
158
|
const removePatient = useCallback(
|
|
117
159
|
(patientUuid: string) =>
|
|
@@ -165,21 +207,58 @@ const AddGroupModal = () => {
|
|
|
165
207
|
|
|
166
208
|
const handleSubmit = () => {
|
|
167
209
|
if (validate()) {
|
|
168
|
-
|
|
210
|
+
post({
|
|
211
|
+
uuid: cohortUuid,
|
|
212
|
+
name: name,
|
|
213
|
+
cohortMembers: patientList.map((p) => ({ patient: p.uuid })),
|
|
214
|
+
});
|
|
215
|
+
if (onPostSubmit) {
|
|
216
|
+
onPostSubmit();
|
|
217
|
+
}
|
|
169
218
|
}
|
|
170
219
|
};
|
|
171
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
|
+
|
|
172
254
|
return (
|
|
173
255
|
<div className={styles.modal}>
|
|
174
|
-
<
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
</Button>
|
|
181
|
-
<ComposedModal open={open} onClose={() => setOpen(false)}>
|
|
182
|
-
<ModalHeader>{t("createNewGroup", "Create New Group")}</ModalHeader>
|
|
256
|
+
<ComposedModal open={isOpen} onClose={handleCancel}>
|
|
257
|
+
<ModalHeader>
|
|
258
|
+
{isCreate
|
|
259
|
+
? t("createNewGroup", "Create New Group")
|
|
260
|
+
: t("editGroup", "Edit Group")}
|
|
261
|
+
</ModalHeader>
|
|
183
262
|
<ModalBody>
|
|
184
263
|
<NewGroupForm
|
|
185
264
|
{...{
|
|
@@ -198,7 +277,7 @@ const AddGroupModal = () => {
|
|
|
198
277
|
{t("cancel", "Cancel")}
|
|
199
278
|
</Button>
|
|
200
279
|
<Button kind="primary" onClick={handleSubmit}>
|
|
201
|
-
{t("createGroup", "Create Group")}
|
|
280
|
+
{isCreate ? t("createGroup", "Create Group") : t("save", "Save")}
|
|
202
281
|
</Button>
|
|
203
282
|
</ModalFooter>
|
|
204
283
|
</ComposedModal>
|
|
@@ -23,13 +23,23 @@
|
|
|
23
23
|
|
|
24
24
|
.patientRow {
|
|
25
25
|
display: flex;
|
|
26
|
+
align-items: center;
|
|
26
27
|
width: "100%";
|
|
28
|
+
&:nth-child(odd) {
|
|
29
|
+
background-color: colors.$gray-20;
|
|
30
|
+
}
|
|
27
31
|
}
|
|
28
32
|
|
|
29
33
|
.patientName {
|
|
30
34
|
flex-grow: 1;
|
|
31
|
-
padding: spacing.$spacing-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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;
|
|
35
45
|
}
|
package/src/config-schema.ts
CHANGED
|
@@ -60,6 +60,28 @@ export const configSchema = {
|
|
|
60
60
|
},
|
|
61
61
|
_default: ["ICRC Forms", "Distress Scales"],
|
|
62
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
|
+
},
|
|
63
85
|
};
|
|
64
86
|
|
|
65
87
|
export type Form = {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import React, { useEffect, useMemo, useReducer } from "react";
|
|
2
2
|
import reducer from "./FormWorkflowReducer";
|
|
3
3
|
import { useParams, useLocation } from "react-router-dom";
|
|
4
|
+
import useGetSystemSetting from "../hooks/useGetSystemSetting";
|
|
5
|
+
import { useSession } from "@openmrs/esm-framework";
|
|
4
6
|
interface ParamTypes {
|
|
5
7
|
formUuid?: string;
|
|
6
8
|
}
|
|
@@ -30,6 +32,8 @@ export const initialWorkflowState = {
|
|
|
30
32
|
activeEncounterUuid: null, // pseudo field from state[activeFormUuid].encounterUuid
|
|
31
33
|
patientUuids: [], // pseudo field from state[activeFormUuid].patientUuids
|
|
32
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
|
|
33
37
|
};
|
|
34
38
|
|
|
35
39
|
const FormWorkflowContext = React.createContext({
|
|
@@ -38,6 +42,7 @@ const FormWorkflowContext = React.createContext({
|
|
|
38
42
|
});
|
|
39
43
|
|
|
40
44
|
const FormWorkflowProvider = ({ children }) => {
|
|
45
|
+
const { user } = useSession();
|
|
41
46
|
const { formUuid } = useParams() as ParamTypes;
|
|
42
47
|
const activeFormUuid = formUuid.split("&")[0];
|
|
43
48
|
const { search } = useLocation();
|
|
@@ -46,6 +51,11 @@ const FormWorkflowProvider = ({ children }) => {
|
|
|
46
51
|
...initialWorkflowState,
|
|
47
52
|
...initialActions,
|
|
48
53
|
});
|
|
54
|
+
const systemSetting = useGetSystemSetting(
|
|
55
|
+
"@openmrs/esm-fast-data-entry-app.groupSessionVisitTypeUuid"
|
|
56
|
+
);
|
|
57
|
+
const singleSessionVisitTypeUuid =
|
|
58
|
+
systemSetting?.result?.data?.results?.[0]?.value;
|
|
49
59
|
|
|
50
60
|
const actions = useMemo(
|
|
51
61
|
() => ({
|
|
@@ -54,6 +64,7 @@ const FormWorkflowProvider = ({ children }) => {
|
|
|
54
64
|
type: "INITIALIZE_WORKFLOW_STATE",
|
|
55
65
|
activeFormUuid,
|
|
56
66
|
newPatientUuid,
|
|
67
|
+
userUuid: user.uuid,
|
|
57
68
|
}),
|
|
58
69
|
addPatient: (patientUuid) =>
|
|
59
70
|
dispatch({ type: "ADD_PATIENT", patientUuid }),
|
|
@@ -72,7 +83,7 @@ const FormWorkflowProvider = ({ children }) => {
|
|
|
72
83
|
destroySession: () => dispatch({ type: "DESTROY_SESSION" }),
|
|
73
84
|
closeSession: () => dispatch({ type: "CLOSE_SESSION" }),
|
|
74
85
|
}),
|
|
75
|
-
[]
|
|
86
|
+
[user]
|
|
76
87
|
);
|
|
77
88
|
|
|
78
89
|
// if formUuid isn't a part of state yet, grab it from the url params
|
|
@@ -103,6 +114,7 @@ const FormWorkflowProvider = ({ children }) => {
|
|
|
103
114
|
encounters:
|
|
104
115
|
state.forms?.[state.activeFormUuid]?.encounters ??
|
|
105
116
|
initialWorkflowState.encounters,
|
|
117
|
+
singleSessionVisitTypeUuid,
|
|
106
118
|
}}
|
|
107
119
|
>
|
|
108
120
|
{children}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { navigate } from "@openmrs/esm-framework";
|
|
2
2
|
import { initialWorkflowState } from "./FormWorkflowContext";
|
|
3
3
|
|
|
4
|
-
export const fdeWorkflowStorageVersion = "1.0
|
|
4
|
+
export const fdeWorkflowStorageVersion = "1.1.0";
|
|
5
5
|
export const fdeWorkflowStorageName = "openmrs:fastDataEntryWorkflowState";
|
|
6
6
|
const persistData = (data) => {
|
|
7
|
-
localStorage.setItem(
|
|
7
|
+
localStorage.setItem(
|
|
8
|
+
fdeWorkflowStorageName + ":" + data.userUuid,
|
|
9
|
+
JSON.stringify(data)
|
|
10
|
+
);
|
|
8
11
|
};
|
|
9
12
|
|
|
10
13
|
const initialFormState = {
|
|
@@ -18,7 +21,9 @@ const initialFormState = {
|
|
|
18
21
|
const reducer = (state, action) => {
|
|
19
22
|
switch (action.type) {
|
|
20
23
|
case "INITIALIZE_WORKFLOW_STATE": {
|
|
21
|
-
const savedData = localStorage.getItem(
|
|
24
|
+
const savedData = localStorage.getItem(
|
|
25
|
+
fdeWorkflowStorageName + ":" + action.userUuid
|
|
26
|
+
);
|
|
22
27
|
const savedDataObject = savedData ? JSON.parse(savedData) : {};
|
|
23
28
|
let newState: { [key: string]: unknown } = {};
|
|
24
29
|
const newPatient = action.newPatientUuid
|
|
@@ -69,6 +74,7 @@ const reducer = (state, action) => {
|
|
|
69
74
|
[action.activeFormUuid]: initialFormState,
|
|
70
75
|
},
|
|
71
76
|
activeFormUuid: action.activeFormUuid,
|
|
77
|
+
userUuid: action.userUuid,
|
|
72
78
|
};
|
|
73
79
|
}
|
|
74
80
|
persistData(newState);
|
|
@@ -259,6 +265,8 @@ const reducer = (state, action) => {
|
|
|
259
265
|
activeFormUuid: null,
|
|
260
266
|
};
|
|
261
267
|
persistData(newState);
|
|
268
|
+
//eslint-disable-next-line
|
|
269
|
+
navigate({ to: "${openmrsSpaBase}/forms" });
|
|
262
270
|
return newState;
|
|
263
271
|
}
|
|
264
272
|
case "CLOSE_SESSION": {
|
|
@@ -267,6 +275,8 @@ const reducer = (state, action) => {
|
|
|
267
275
|
activeFormUuid: null,
|
|
268
276
|
};
|
|
269
277
|
persistData(newState);
|
|
278
|
+
//eslint-disable-next-line
|
|
279
|
+
navigate({ to: "${openmrsSpaBase}/forms" });
|
|
270
280
|
return newState;
|
|
271
281
|
}
|
|
272
282
|
default:
|