@openmrs/esm-fast-data-entry-app 1.0.1-pre.10 → 1.0.1-pre.102

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 (118) hide show
  1. package/README.md +21 -2
  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/233.js.LICENSE.txt +9 -0
  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 +2 -0
  12. package/dist/294.js.LICENSE.txt +9 -0
  13. package/dist/294.js.map +1 -0
  14. package/dist/319.js +1 -0
  15. package/dist/327.js +1 -0
  16. package/dist/327.js.map +1 -0
  17. package/dist/409.js +2 -0
  18. package/dist/409.js.LICENSE.txt +27 -0
  19. package/dist/409.js.map +1 -0
  20. package/dist/415.js +1 -0
  21. package/dist/415.js.map +1 -0
  22. package/dist/559.js +1 -0
  23. package/dist/559.js.map +1 -0
  24. package/dist/574.js +1 -0
  25. package/dist/651.js +1 -0
  26. package/dist/651.js.map +1 -0
  27. package/dist/706.js +1 -0
  28. package/dist/706.js.map +1 -0
  29. package/dist/757.js +1 -0
  30. package/dist/788.js +1 -0
  31. package/dist/800.js +2 -0
  32. package/dist/800.js.LICENSE.txt +5 -0
  33. package/dist/800.js.map +1 -0
  34. package/dist/807.js +1 -0
  35. package/dist/820.js +1 -0
  36. package/dist/820.js.map +1 -0
  37. package/dist/833.js +1 -0
  38. package/dist/883.js +1 -0
  39. package/dist/883.js.map +1 -0
  40. package/dist/889.js +1 -0
  41. package/dist/889.js.map +1 -0
  42. package/dist/897.js +2 -0
  43. package/dist/897.js.LICENSE.txt +21 -0
  44. package/dist/897.js.map +1 -0
  45. package/dist/92.js +1 -0
  46. package/dist/92.js.map +1 -0
  47. package/dist/935.js +2 -0
  48. package/dist/935.js.LICENSE.txt +19 -0
  49. package/dist/935.js.map +1 -0
  50. package/dist/959.js +1 -0
  51. package/dist/959.js.map +1 -0
  52. package/dist/main.js +1 -0
  53. package/dist/main.js.map +1 -0
  54. package/dist/openmrs-esm-fast-data-entry-app.js +1 -1
  55. package/dist/openmrs-esm-fast-data-entry-app.js.buildmanifest.json +742 -0
  56. package/dist/openmrs-esm-fast-data-entry-app.js.map +1 -0
  57. package/dist/routes.json +1 -0
  58. package/jest.config.json +2 -1
  59. package/package.json +13 -10
  60. package/src/CancelModal.tsx +48 -0
  61. package/src/CompleteModal.tsx +46 -0
  62. package/src/FormBootstrap.tsx +18 -3
  63. package/src/add-group-modal/AddGroupModal.tsx +113 -34
  64. package/src/add-group-modal/styles.scss +14 -4
  65. package/src/config-schema.ts +22 -0
  66. package/src/context/FormWorkflowContext.tsx +13 -1
  67. package/src/context/FormWorkflowReducer.ts +13 -3
  68. package/src/context/GroupFormWorkflowContext.tsx +41 -6
  69. package/src/context/GroupFormWorkflowReducer.ts +170 -12
  70. package/src/form-entry-workflow/FormEntryWorkflow.tsx +67 -101
  71. package/src/form-entry-workflow/styles.scss +2 -1
  72. package/src/forms-page/FormsPage.tsx +8 -3
  73. package/src/forms-page/forms-table/FormsTable.tsx +11 -5
  74. package/src/group-form-entry-workflow/GroupFormEntryWorkflow.tsx +13 -400
  75. package/src/group-form-entry-workflow/GroupSessionWorkspace.tsx +247 -0
  76. package/src/group-form-entry-workflow/SessionDetailsForm.tsx +131 -0
  77. package/src/group-form-entry-workflow/SessionMetaWorkspace.tsx +107 -0
  78. package/src/group-form-entry-workflow/attendance-table/AttendanceTable.tsx +144 -0
  79. package/src/group-form-entry-workflow/attendance-table/index.ts +1 -0
  80. package/src/group-form-entry-workflow/{group-banner/GroupBanner.test.tsx → group-display-header/GroupDisplayHeader.test.tsx} +2 -2
  81. package/src/group-form-entry-workflow/{group-banner/GroupBanner.tsx → group-display-header/GroupDisplayHeader.tsx} +23 -5
  82. package/src/group-form-entry-workflow/group-display-header/index.ts +3 -0
  83. package/src/group-form-entry-workflow/group-search/CompactGroupResults.tsx +61 -28
  84. package/src/group-form-entry-workflow/group-search/CompactGroupSearch.tsx +5 -0
  85. package/src/group-form-entry-workflow/group-search/GroupSearch.tsx +65 -8
  86. package/src/group-form-entry-workflow/group-search/group-search.scss +8 -6
  87. package/src/group-form-entry-workflow/group-search-header/GroupSearchHeader.tsx +41 -10
  88. package/src/group-form-entry-workflow/styles.scss +12 -1
  89. package/src/hooks/index.ts +1 -0
  90. package/src/hooks/useGetPatient.ts +1 -1
  91. package/src/hooks/useGetPatients.ts +34 -0
  92. package/src/hooks/useGetSystemSetting.ts +38 -0
  93. package/src/hooks/usePostEndpoint.ts +76 -0
  94. package/src/hooks/useSearchEndpoint.ts +120 -0
  95. package/src/hooks/useStartVisit.ts +92 -0
  96. package/src/index.ts +13 -65
  97. package/src/patient-card/styles.scss +1 -0
  98. package/src/routes.json +24 -0
  99. package/tools/i18next-parser.config.js +93 -0
  100. package/translations/am.json +69 -0
  101. package/translations/en.json +29 -9
  102. package/translations/es.json +69 -0
  103. package/translations/fr.json +69 -0
  104. package/translations/he.json +69 -0
  105. package/translations/km.json +69 -0
  106. package/.editorconfig +0 -12
  107. package/.eslintignore +0 -2
  108. package/.eslintrc.js +0 -10
  109. package/.husky/pre-push +0 -1
  110. package/.prettierignore +0 -14
  111. package/.tx/config +0 -9
  112. package/.yarn/plugins/@yarnpkg/plugin-version.cjs +0 -550
  113. package/.yarn/versions/45b499b6.yml +0 -0
  114. package/src/group-form-entry-workflow/group-banner/index.ts +0 -3
  115. package/src/group-form-entry-workflow/group-search/mock-group-data.ts +0 -79
  116. package/src/group-form-entry-workflow/group-search/useGroupSearch.ts +0 -14
  117. package/src/hooks/usePostCohort.ts +0 -18
  118. /package/src/group-form-entry-workflow/{group-banner → group-display-header}/styles.scss +0 -0
@@ -0,0 +1,247 @@
1
+ import {
2
+ getGlobalStore,
3
+ useConfig,
4
+ useSession,
5
+ useStore,
6
+ } from "@openmrs/esm-framework";
7
+ import { Button } from "@carbon/react";
8
+ import React, { useCallback, useContext, useEffect, useState } from "react";
9
+ import PatientCard from "../patient-card/PatientCard";
10
+ import styles from "./styles.scss";
11
+ import { useTranslation } from "react-i18next";
12
+ import GroupFormWorkflowContext from "../context/GroupFormWorkflowContext";
13
+ import FormBootstrap from "../FormBootstrap";
14
+ import useStartVisit from "../hooks/useStartVisit";
15
+ import CompleteModal from "../CompleteModal";
16
+ import CancelModal from "../CancelModal";
17
+
18
+ const formStore = getGlobalStore("ampath-form-state");
19
+
20
+ const WorkflowNavigationButtons = () => {
21
+ const context = useContext(GroupFormWorkflowContext);
22
+ const {
23
+ activeFormUuid,
24
+ submitForNext,
25
+ patientUuids,
26
+ activePatientUuid,
27
+ workflowState,
28
+ } = context;
29
+ const store = useStore(formStore);
30
+ const formState = store[activeFormUuid];
31
+ const navigationDisabled =
32
+ (formState !== "ready" || workflowState !== "EDIT_FORM") &&
33
+ formState !== "readyWithValidationErrors";
34
+ const [cancelModalOpen, setCancelModalOpen] = useState(false);
35
+ const [completeModalOpen, setCompleteModalOpen] = useState(false);
36
+ const { t } = useTranslation();
37
+
38
+ const isLastPatient =
39
+ activePatientUuid === patientUuids[patientUuids.length - 1];
40
+
41
+ const handleClickNext = () => {
42
+ if (
43
+ workflowState === "EDIT_FORM" ||
44
+ formState === "readyWithValidationErrors"
45
+ ) {
46
+ submitForNext();
47
+ }
48
+ };
49
+
50
+ return (
51
+ <>
52
+ <div className={styles.rightPanelActionButtons}>
53
+ <Button
54
+ kind="primary"
55
+ onClick={handleClickNext}
56
+ disabled={navigationDisabled}
57
+ >
58
+ {isLastPatient
59
+ ? t("saveForm", "Save Form")
60
+ : t("nextPatient", "Next Patient")}
61
+ </Button>
62
+ <Button kind="secondary" onClick={() => setCompleteModalOpen(true)}>
63
+ {t("saveAndComplete", "Save & Complete")}
64
+ </Button>
65
+ <Button kind="tertiary" onClick={() => setCancelModalOpen(true)}>
66
+ {t("cancel", "Cancel")}
67
+ </Button>
68
+ </div>
69
+ <CancelModal
70
+ open={cancelModalOpen}
71
+ setOpen={setCancelModalOpen}
72
+ context={context}
73
+ />
74
+ <CompleteModal
75
+ open={completeModalOpen}
76
+ setOpen={setCompleteModalOpen}
77
+ context={context}
78
+ validateFirst={false}
79
+ />
80
+ </>
81
+ );
82
+ };
83
+
84
+ const GroupSessionWorkspace = () => {
85
+ const { groupSessionConcepts } = useConfig();
86
+ const { t } = useTranslation();
87
+ const {
88
+ patientUuids,
89
+ activePatientUuid,
90
+ encounters,
91
+ activeEncounterUuid,
92
+ activeVisitUuid,
93
+ activeFormUuid,
94
+ saveEncounter,
95
+ activeSessionMeta,
96
+ groupVisitTypeUuid,
97
+ updateVisitUuid,
98
+ submitForNext,
99
+ workflowState,
100
+ } = useContext(GroupFormWorkflowContext);
101
+
102
+ const { sessionLocation } = useSession();
103
+ const [encounter, setEncounter] = useState(null);
104
+ const [visit, setVisit] = useState(null);
105
+
106
+ const {
107
+ saveVisit,
108
+ updateEncounter,
109
+ success: visitSaveSuccess,
110
+ } = useStartVisit({
111
+ showSuccessNotification: false,
112
+ showErrorNotification: true,
113
+ });
114
+
115
+ // 0. user clicks "next patient" in WorkflowNavigationButtons
116
+ // which triggers submitForNext() if workflowState === "EDIT_FORM"
117
+
118
+ // 1. save the new visit uuid and start form submission
119
+ useEffect(() => {
120
+ if (
121
+ visitSaveSuccess &&
122
+ visitSaveSuccess.data.patient.uuid === activePatientUuid
123
+ ) {
124
+ setVisit(visitSaveSuccess.data);
125
+ // Update visit UUID on workflow
126
+ updateVisitUuid(visitSaveSuccess.data.uuid);
127
+ }
128
+ }, [
129
+ visitSaveSuccess,
130
+ updateVisitUuid,
131
+ activeVisitUuid,
132
+ activePatientUuid,
133
+ visit,
134
+ setVisit,
135
+ ]);
136
+
137
+ // 2. If there's no active visit, trigger the creation of a new one
138
+ const handleEncounterCreate = useCallback(
139
+ (payload) => {
140
+ // Create a visit with the same date as the encounter being saved
141
+ if (!activeVisitUuid) {
142
+ saveVisit({
143
+ patientUuid: activePatientUuid,
144
+ startDatetime: activeSessionMeta.sessionDate,
145
+ stopDatetime: activeSessionMeta.sessionDate,
146
+ visitType: groupVisitTypeUuid,
147
+ location: sessionLocation?.uuid,
148
+ });
149
+ }
150
+ const obsTime = new Date(activeSessionMeta.sessionDate);
151
+ payload.obs.forEach((item, index) => {
152
+ payload.obs[index] = {
153
+ ...item,
154
+ groupMembers: item.groupMembers?.map((mem) => ({
155
+ ...mem,
156
+ obsDatetime: obsTime.toISOString(),
157
+ })),
158
+ obsDatetime: obsTime.toISOString(),
159
+ };
160
+ });
161
+ // If this is a newly created encounter and visit, add session concepts to encounter payload.
162
+ if (!activeVisitUuid) {
163
+ Object.entries(groupSessionConcepts).forEach(([field, uuid]) => {
164
+ payload.obs.push({
165
+ concept: uuid,
166
+ value: activeSessionMeta?.[field],
167
+ });
168
+ });
169
+ }
170
+ payload.location = sessionLocation?.uuid;
171
+ payload.encounterDatetime = obsTime.toISOString();
172
+ },
173
+ [
174
+ activePatientUuid,
175
+ activeVisitUuid,
176
+ activeSessionMeta,
177
+ groupSessionConcepts,
178
+ groupVisitTypeUuid,
179
+ saveVisit,
180
+ sessionLocation,
181
+ ]
182
+ );
183
+
184
+ // 3. Update encounter so that it belongs to the created visit
185
+ useEffect(() => {
186
+ if (encounter && visit && encounter.patient?.uuid === visit.patient?.uuid) {
187
+ updateEncounter({ uuid: encounter.uuid, visit: visit.uuid });
188
+ }
189
+ }, [encounter, updateEncounter, visit]);
190
+
191
+ // 4. Once form has been posted, save the new encounter uuid so we can edit it later
192
+ const handlePostResponse = useCallback(
193
+ (encounter) => {
194
+ if (encounter && encounter.uuid) {
195
+ saveEncounter(encounter.uuid);
196
+ setEncounter(encounter);
197
+ }
198
+ },
199
+ [saveEncounter]
200
+ );
201
+
202
+ const switchPatient = useCallback(
203
+ (patientUuid) => {
204
+ submitForNext(patientUuid);
205
+ },
206
+ [submitForNext]
207
+ );
208
+
209
+ if (workflowState === "NEW_GROUP_SESSION") return null;
210
+
211
+ return (
212
+ <div className={styles.workspace}>
213
+ <div className={styles.formMainContent}>
214
+ <div className={styles.formContainer}>
215
+ <FormBootstrap
216
+ patientUuid={activePatientUuid}
217
+ encounterUuid={activeEncounterUuid}
218
+ {...{
219
+ formUuid: activeFormUuid,
220
+ handlePostResponse,
221
+ handleEncounterCreate,
222
+ }}
223
+ />
224
+ </div>
225
+ <div className={styles.rightPanel}>
226
+ <h4>{t("formsFilled", "Forms filled")}</h4>
227
+ <div className={styles.patientCardsSection}>
228
+ {patientUuids?.map((patientUuid) => (
229
+ <PatientCard
230
+ key={patientUuid}
231
+ {...{
232
+ patientUuid,
233
+ activePatientUuid,
234
+ editEncounter: switchPatient,
235
+ encounters,
236
+ }}
237
+ />
238
+ ))}
239
+ </div>
240
+ <WorkflowNavigationButtons />
241
+ </div>
242
+ </div>
243
+ </div>
244
+ );
245
+ };
246
+
247
+ export default GroupSessionWorkspace;
@@ -0,0 +1,131 @@
1
+ import {
2
+ Layer,
3
+ Tile,
4
+ TextInput,
5
+ TextArea,
6
+ DatePicker,
7
+ DatePickerInput,
8
+ } from "@carbon/react";
9
+ import React, { useContext } from "react";
10
+ import styles from "./styles.scss";
11
+ import { useTranslation } from "react-i18next";
12
+ import { Controller, useFormContext } from "react-hook-form";
13
+ import { AttendanceTable } from "./attendance-table";
14
+ import GroupFormWorkflowContext from "../context/GroupFormWorkflowContext";
15
+ import useGetPatients from "../hooks/useGetPatients";
16
+
17
+ const SessionDetailsForm = () => {
18
+ const { t } = useTranslation();
19
+ const {
20
+ register,
21
+ formState: { errors },
22
+ control,
23
+ } = useFormContext();
24
+
25
+ const { patientUuids } = useContext(GroupFormWorkflowContext);
26
+ const { patients, isLoading } = useGetPatients(patientUuids);
27
+
28
+ return (
29
+ <div>
30
+ {!isLoading && (
31
+ <div className={styles.formSection}>
32
+ <h4>{t("sessionDetails", "1. Session details")}</h4>
33
+ <div>
34
+ <p>
35
+ {t(
36
+ "allFieldsRequired",
37
+ "All fields are required unless marked optional"
38
+ )}
39
+ </p>
40
+ </div>
41
+ <Layer>
42
+ <Tile className={styles.formSectionTile}>
43
+ <Layer>
44
+ <div
45
+ style={{
46
+ display: "flex",
47
+ flexDirection: "column",
48
+ rowGap: "1.5rem",
49
+ }}
50
+ >
51
+ <TextInput
52
+ id="text"
53
+ type="text"
54
+ labelText={t("sessionName", "Session Name")}
55
+ {...register("sessionName", { required: true })}
56
+ invalid={errors.sessionName}
57
+ invalidText={"This field is required"}
58
+ />
59
+ <TextInput
60
+ id="text"
61
+ type="text"
62
+ labelText={t("practitionerName", "Practitioner Name")}
63
+ {...register("practitionerName", { required: true })}
64
+ invalid={errors.practitionerName}
65
+ invalidText={"This field is required"}
66
+ />
67
+ <Controller
68
+ name="sessionDate"
69
+ control={control}
70
+ rules={{ required: true }}
71
+ render={({ field }) => (
72
+ <DatePicker
73
+ datePickerType="single"
74
+ size="md"
75
+ maxDate={new Date()}
76
+ {...field}
77
+ >
78
+ <DatePickerInput
79
+ id="session-date"
80
+ labelText={t("sessionDate", "Session Date")}
81
+ placeholder="mm/dd/yyyy"
82
+ size="md"
83
+ invalid={errors.sessionDate}
84
+ invalidText={"This field is required"}
85
+ />
86
+ </DatePicker>
87
+ )}
88
+ />
89
+ <TextArea
90
+ id="text"
91
+ type="text"
92
+ labelText={t("sessionNotes", "Session Notes")}
93
+ {...register("sessionNotes", { required: true })}
94
+ invalid={errors.sessionNotes}
95
+ invalidText={"This field is required"}
96
+ />
97
+ </div>
98
+ </Layer>
99
+ </Tile>
100
+ </Layer>
101
+ <h4>{t("sessionParticipants", "2. Session participants")}</h4>
102
+ <div>
103
+ <p>
104
+ {t(
105
+ "markAbsentPatients",
106
+ "The patients in this group. Patients that are not present in the session should be marked as absent."
107
+ )}
108
+ </p>
109
+ </div>
110
+ <Layer>
111
+ <Tile className={styles.formSectionTile}>
112
+ <Layer>
113
+ <div
114
+ style={{
115
+ display: "flex",
116
+ flexDirection: "column",
117
+ rowGap: "1.5rem",
118
+ }}
119
+ >
120
+ <AttendanceTable patients={patients} />
121
+ </div>
122
+ </Layer>
123
+ </Tile>
124
+ </Layer>
125
+ </div>
126
+ )}
127
+ </div>
128
+ );
129
+ };
130
+
131
+ export default SessionDetailsForm;
@@ -0,0 +1,107 @@
1
+ import { Button } from "@carbon/react";
2
+ import React, { useContext, useEffect, useState } from "react";
3
+ import styles from "./styles.scss";
4
+ import { useTranslation } from "react-i18next";
5
+ import GroupFormWorkflowContext from "../context/GroupFormWorkflowContext";
6
+ import { FormProvider, useForm, useFormContext } from "react-hook-form";
7
+ import CancelModal from "../CancelModal";
8
+ import SessionDetailsForm from "./SessionDetailsForm";
9
+
10
+ const NewGroupWorkflowButtons = () => {
11
+ const { t } = useTranslation();
12
+ const context = useContext(GroupFormWorkflowContext);
13
+ const { workflowState, patientUuids } = context;
14
+ const [cancelModalOpen, setCancelModalOpen] = useState(false);
15
+ if (workflowState !== "NEW_GROUP_SESSION") return null;
16
+
17
+ return (
18
+ <>
19
+ <div className={styles.rightPanelActionButtons}>
20
+ <Button kind="secondary" type="submit" disabled={!patientUuids.length}>
21
+ {t("createNewSession", "Create New Session")}
22
+ </Button>
23
+ <Button
24
+ kind="tertiary"
25
+ onClick={() => {
26
+ setCancelModalOpen(true);
27
+ }}
28
+ >
29
+ {t("cancel", "Cancel")}
30
+ </Button>
31
+ </div>
32
+ <CancelModal
33
+ open={cancelModalOpen}
34
+ setOpen={setCancelModalOpen}
35
+ context={context}
36
+ />
37
+ </>
38
+ );
39
+ };
40
+
41
+ const GroupIdField = () => {
42
+ const { t } = useTranslation();
43
+ const {
44
+ register,
45
+ formState: { errors },
46
+ setValue,
47
+ } = useFormContext();
48
+ const { activeGroupUuid } = useContext(GroupFormWorkflowContext);
49
+
50
+ useEffect(() => {
51
+ if (activeGroupUuid) setValue("groupUuid", activeGroupUuid);
52
+ }, [activeGroupUuid, setValue]);
53
+
54
+ return (
55
+ <>
56
+ <input
57
+ hidden
58
+ {...register("groupUuid", {
59
+ value: activeGroupUuid,
60
+ required: t("chooseGroupError", "Please choose a group."),
61
+ })}
62
+ />
63
+ {errors.groupUuid && !activeGroupUuid && (
64
+ <div className={styles.formError}>
65
+ {errors.groupUuid.message as string}
66
+ </div>
67
+ )}
68
+ </>
69
+ );
70
+ };
71
+
72
+ const SessionMetaWorkspace = () => {
73
+ const { t } = useTranslation();
74
+ const { setSessionMeta, workflowState } = useContext(
75
+ GroupFormWorkflowContext
76
+ );
77
+ const methods = useForm();
78
+
79
+ const onSubmit = (data) => {
80
+ const { sessionDate, ...rest } = data;
81
+ setSessionMeta({ ...rest, sessionDate: sessionDate[0] });
82
+ };
83
+
84
+ if (workflowState !== "NEW_GROUP_SESSION") return null;
85
+
86
+ return (
87
+ <FormProvider {...methods}>
88
+ <form onSubmit={methods.handleSubmit(onSubmit)}>
89
+ <div className={styles.workspace}>
90
+ <div className={styles.formMainContent}>
91
+ <div className={styles.formContainer}>
92
+ <SessionDetailsForm />
93
+ </div>
94
+ <div className={styles.rightPanel}>
95
+ <h4>{t("newGroupSession", "New Group Session")}</h4>
96
+ <GroupIdField />
97
+ <hr style={{ width: "100%" }} />
98
+ <NewGroupWorkflowButtons />
99
+ </div>
100
+ </div>
101
+ </div>
102
+ </form>
103
+ </FormProvider>
104
+ );
105
+ };
106
+
107
+ export default SessionMetaWorkspace;
@@ -0,0 +1,144 @@
1
+ import React, { useCallback, useContext, useMemo, useState } from "react";
2
+ import { Edit } from "@carbon/react/icons";
3
+
4
+ import {
5
+ Checkbox,
6
+ SkeletonText,
7
+ Table,
8
+ TableHead,
9
+ TableRow,
10
+ TableHeader,
11
+ TableBody,
12
+ TableCell,
13
+ Button,
14
+ } from "@carbon/react";
15
+ import { useTranslation } from "react-i18next";
16
+ import GroupFormWorkflowContext from "../../context/GroupFormWorkflowContext";
17
+ import AddGroupModal from "../../add-group-modal/AddGroupModal";
18
+
19
+ const PatientRow = ({ patient }) => {
20
+ const { patientUuids, addPatientUuid, removePatientUuid } = useContext(
21
+ GroupFormWorkflowContext
22
+ );
23
+ const givenName = patient?.name?.[0]?.given?.[0];
24
+ const familyName = patient?.name?.[0]?.family;
25
+ const identifier = patient?.identifier?.[0]?.value;
26
+
27
+ const handleOnChange = (e, { checked }) => {
28
+ if (checked) {
29
+ addPatientUuid(patient.id);
30
+ } else {
31
+ removePatientUuid(patient.id);
32
+ }
33
+ };
34
+
35
+ if (!patient) {
36
+ return (
37
+ <TableRow>
38
+ <TableCell>
39
+ <SkeletonText />
40
+ </TableCell>
41
+ <TableCell>
42
+ <SkeletonText />
43
+ </TableCell>
44
+ <TableCell>
45
+ <Checkbox diabled />
46
+ </TableCell>
47
+ </TableRow>
48
+ );
49
+ }
50
+
51
+ return (
52
+ <TableRow>
53
+ <TableCell>
54
+ {patient.display ||
55
+ patient.displayName ||
56
+ [givenName, familyName].join(" ")}
57
+ </TableCell>
58
+ <TableCell>{identifier}</TableCell>
59
+ <TableCell>
60
+ <Checkbox
61
+ checked={patientUuids.includes(patient.id)}
62
+ labelText={patient.id}
63
+ hideLabel
64
+ id={`${identifier}-attendance-checkbox`}
65
+ onChange={handleOnChange}
66
+ />
67
+ </TableCell>
68
+ </TableRow>
69
+ );
70
+ };
71
+
72
+ const AttendanceTable = ({ patients }) => {
73
+ const { t } = useTranslation();
74
+ const { activeGroupUuid, activeGroupName, activeGroupMembers } = useContext(
75
+ GroupFormWorkflowContext
76
+ );
77
+
78
+ const [isOpen, setOpen] = useState(false);
79
+
80
+ const headers = [
81
+ t("name", "Name"),
82
+ t("identifier", "Patient ID"),
83
+ t("patientIsPresent", "Patient is present"),
84
+ ];
85
+
86
+ const handleCancel = useCallback(() => {
87
+ setOpen(false);
88
+ }, []);
89
+
90
+ const onPostSubmit = useCallback(() => {
91
+ setOpen(false);
92
+ }, []);
93
+
94
+ const newArr = useMemo(() => {
95
+ return activeGroupMembers.map(function (value) {
96
+ const patient = patients.find((patient) => patient.id === value);
97
+ return { uuid: value, ...patient };
98
+ });
99
+ }, [activeGroupMembers, patients]);
100
+
101
+ if (!activeGroupUuid) {
102
+ return <div>{t("selectGroupFirst", "Please select a group first")}</div>;
103
+ }
104
+
105
+ return (
106
+ <div>
107
+ <span style={{ flexGrow: 1 }} />
108
+ <Button kind="ghost" onClick={() => setOpen(true)}>
109
+ {t("editGroup", "Edit Group")}&nbsp;
110
+ <Edit size={20} />
111
+ </Button>
112
+ <AddGroupModal
113
+ {...{
114
+ cohortUuid: activeGroupUuid,
115
+ patients: newArr,
116
+ isCreate: false,
117
+ groupName: activeGroupName,
118
+ isOpen: isOpen,
119
+ handleCancel: handleCancel,
120
+ onPostSubmit: onPostSubmit,
121
+ }}
122
+ />
123
+ <Table>
124
+ <TableHead>
125
+ <TableRow>
126
+ {headers.map((header, index) => (
127
+ <TableHeader key={index}>{header}</TableHeader>
128
+ ))}
129
+ </TableRow>
130
+ </TableHead>
131
+ <TableBody>
132
+ {activeGroupMembers.map((patientUuid, index) => {
133
+ const patient = patients.find(
134
+ (patient) => patient.id === patientUuid
135
+ );
136
+ return <PatientRow patient={patient} key={index} />;
137
+ })}
138
+ </TableBody>
139
+ </Table>
140
+ </div>
141
+ );
142
+ };
143
+
144
+ export default AttendanceTable;
@@ -0,0 +1 @@
1
+ export { default as AttendanceTable } from "./AttendanceTable";
@@ -1,9 +1,9 @@
1
1
  import React from "react";
2
2
  import { render } from "@testing-library/react";
3
- import GroupBanner from "./GroupBanner";
3
+ import GroupDisplayHeader from "./GroupDisplayHeader";
4
4
 
5
5
  describe("PatientBanner", () => {
6
6
  it("renders placeholder information when no data is present", () => {
7
- render(<GroupBanner />);
7
+ render(<GroupDisplayHeader />);
8
8
  });
9
9
  });
@@ -1,12 +1,19 @@
1
1
  import React, { useContext } from "react";
2
- import { Events } from "@carbon/react/icons";
2
+ import { Button } from "@carbon/react";
3
+ import { Events, Close } from "@carbon/react/icons";
3
4
  import styles from "./styles.scss";
4
5
  import { useTranslation } from "react-i18next";
5
6
  import GroupFormWorkflowContext from "../../context/GroupFormWorkflowContext";
6
7
 
7
- const GroupBanner = () => {
8
- const { activeGroupName, activeGroupUuid, patientUuids, activeSessionMeta } =
9
- useContext(GroupFormWorkflowContext);
8
+ const GroupDisplayHeader = () => {
9
+ const {
10
+ activeGroupName,
11
+ activeGroupUuid,
12
+ patientUuids,
13
+ activeSessionMeta,
14
+ unsetGroup,
15
+ destroySession,
16
+ } = useContext(GroupFormWorkflowContext);
10
17
  const { t } = useTranslation();
11
18
 
12
19
  if (!activeGroupUuid) {
@@ -38,8 +45,19 @@ const GroupBanner = () => {
38
45
  </div>
39
46
  </div>
40
47
  )}
48
+ <span style={{ flexGrow: 1 }} />
49
+ <span>
50
+ <Button kind="ghost" onClick={() => unsetGroup()}>
51
+ {t("changeGroup", "Choose a different group")} <Close size={20} />
52
+ </Button>
53
+ </span>
54
+ <span>
55
+ <Button kind="ghost" onClick={() => destroySession()}>
56
+ {t("cancel", "Cancel")} <Close size={20} />
57
+ </Button>
58
+ </span>
41
59
  </div>
42
60
  );
43
61
  };
44
62
 
45
- export default GroupBanner;
63
+ export default GroupDisplayHeader;
@@ -0,0 +1,3 @@
1
+ import GroupDisplayHeader from "./GroupDisplayHeader";
2
+
3
+ export default GroupDisplayHeader;