@openmrs/esm-fast-data-entry-app 1.0.1-pre.17 → 1.0.1-pre.176
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/__mocks__/react-i18next.js +9 -14
- package/dist/101.js +1 -0
- package/dist/101.js.map +1 -0
- package/dist/132.js +1 -1
- package/dist/143.js +1 -0
- package/dist/143.js.map +1 -0
- package/dist/188.js +1 -0
- package/dist/188.js.map +1 -0
- package/dist/197.js +1 -0
- package/dist/219.js +1 -0
- package/dist/219.js.map +1 -0
- package/dist/221.js +1 -0
- package/dist/221.js.map +1 -0
- package/dist/259.js +1 -0
- package/dist/259.js.map +1 -0
- package/dist/29.js +2 -0
- package/dist/29.js.LICENSE.txt +3 -0
- package/dist/29.js.map +1 -0
- package/dist/300.js +1 -0
- package/dist/31.js +2 -0
- package/dist/{569.js.LICENSE.txt → 31.js.LICENSE.txt} +9 -6
- package/dist/31.js.map +1 -0
- package/dist/326.js +1 -0
- package/dist/326.js.map +1 -0
- package/dist/335.js +1 -0
- package/dist/367.js +1 -0
- package/dist/367.js.map +1 -0
- package/dist/480.js +1 -0
- package/dist/491.js +1 -0
- package/dist/491.js.map +1 -0
- package/dist/540.js +2 -0
- package/dist/540.js.map +1 -0
- package/dist/55.js +1 -0
- package/dist/564.js +1 -0
- package/dist/564.js.map +1 -0
- package/dist/602.js +1 -0
- package/dist/602.js.map +1 -0
- package/dist/626.js +2 -0
- package/dist/626.js.LICENSE.txt +9 -0
- package/dist/626.js.map +1 -0
- package/dist/652.js +1 -0
- package/dist/685.js +1 -0
- package/dist/685.js.map +1 -0
- package/dist/773.js +2 -0
- package/dist/{68.js.LICENSE.txt → 773.js.LICENSE.txt} +13 -2
- package/dist/773.js.map +1 -0
- package/dist/91.js +1 -0
- package/dist/91.js.map +1 -0
- package/dist/961.js +2 -0
- package/dist/961.js.map +1 -0
- package/dist/99.js +1 -0
- package/dist/99.js.map +1 -0
- package/dist/main.js +1 -1
- 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 +403 -136
- 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 +41 -38
- package/prettier.config.js +8 -0
- package/src/CancelModal.tsx +42 -0
- package/src/CompleteModal.tsx +35 -0
- package/src/FormBootstrap.tsx +39 -11
- package/src/Root.tsx +7 -12
- package/src/add-group-modal/AddGroupModal.tsx +107 -120
- package/src/add-group-modal/styles.scss +7 -3
- package/src/config-schema.ts +77 -16
- package/src/constant.ts +1 -1
- package/src/context/FormWorkflowContext.tsx +31 -32
- package/src/context/FormWorkflowReducer.ts +53 -67
- package/src/context/GroupFormWorkflowContext.tsx +56 -44
- package/src/context/GroupFormWorkflowReducer.ts +177 -68
- package/src/declarations.d.ts +4 -0
- package/src/empty-state/EmptyDataIllustration.tsx +4 -16
- package/src/empty-state/EmptyState.tsx +8 -13
- package/src/empty-state/styles.scss +14 -14
- package/src/form-entry-workflow/FormEntryWorkflow.tsx +78 -124
- package/src/form-entry-workflow/form-review-card/FormReviewCard.tsx +7 -7
- package/src/form-entry-workflow/form-review-card/index.ts +1 -1
- package/src/form-entry-workflow/form-review-card/styles.scss +9 -11
- package/src/form-entry-workflow/index.ts +1 -1
- package/src/form-entry-workflow/patient-banner/PatientBanner.test.tsx +5 -5
- package/src/form-entry-workflow/patient-banner/PatientBanner.tsx +14 -27
- package/src/form-entry-workflow/patient-banner/index.ts +1 -1
- package/src/form-entry-workflow/patient-banner/styles.scss +11 -12
- package/src/form-entry-workflow/patient-search-header/PatientSearchHeader.tsx +19 -28
- package/src/form-entry-workflow/patient-search-header/index.ts +1 -1
- package/src/form-entry-workflow/patient-search-header/styles.scss +13 -10
- package/src/form-entry-workflow/styles.scss +13 -14
- package/src/form-entry-workflow/workflow-review/WorkflowReview.tsx +13 -11
- package/src/form-entry-workflow/workflow-review/index.ts +1 -1
- package/src/form-entry-workflow/workflow-review/styles.scss +0 -4
- package/src/forms-app-menu-link.tsx +4 -6
- package/src/forms-page/FormsPage.tsx +24 -47
- package/src/forms-page/forms-table/FormsTable.tsx +33 -47
- package/src/forms-page/forms-table/index.ts +1 -1
- package/src/forms-page/forms-table/styles.scss +4 -5
- package/src/forms-page/index.ts +1 -1
- package/src/forms-page/styles.scss +3 -5
- package/src/group-form-entry-workflow/GroupFormEntryWorkflow.tsx +15 -402
- package/src/group-form-entry-workflow/GroupSessionWorkspace.tsx +207 -0
- package/src/group-form-entry-workflow/SessionDetailsForm.tsx +154 -0
- package/src/group-form-entry-workflow/SessionMetaWorkspace.tsx +99 -0
- package/src/group-form-entry-workflow/attendance-table/AttendanceTable.tsx +130 -0
- package/src/group-form-entry-workflow/attendance-table/index.ts +1 -0
- package/src/group-form-entry-workflow/configurable-questions/ConfigurableQuestionsSection.tsx +41 -0
- package/src/group-form-entry-workflow/group-display-header/GroupDisplayHeader.test.tsx +5 -5
- package/src/group-form-entry-workflow/group-display-header/GroupDisplayHeader.tsx +14 -30
- package/src/group-form-entry-workflow/group-display-header/index.ts +1 -1
- package/src/group-form-entry-workflow/group-display-header/styles.scss +20 -20
- package/src/group-form-entry-workflow/group-search/CompactGroupResults.tsx +24 -35
- package/src/group-form-entry-workflow/group-search/CompactGroupSearch.tsx +13 -15
- package/src/group-form-entry-workflow/group-search/GroupSearch.tsx +22 -38
- package/src/group-form-entry-workflow/group-search/compact-group-result.scss +16 -17
- package/src/group-form-entry-workflow/group-search/compact-group-search.scss +7 -8
- package/src/group-form-entry-workflow/group-search/group-search.scss +20 -23
- package/src/group-form-entry-workflow/group-search-header/GroupSearchHeader.tsx +41 -18
- package/src/group-form-entry-workflow/group-search-header/index.ts +1 -1
- package/src/group-form-entry-workflow/group-search-header/styles.scss +8 -8
- package/src/group-form-entry-workflow/index.ts +1 -1
- package/src/group-form-entry-workflow/styles.scss +15 -17
- package/src/hooks/index.ts +7 -6
- package/src/hooks/useForm.ts +56 -0
- package/src/hooks/useFormState.ts +3 -3
- package/src/hooks/useGetAllForms.ts +7 -15
- package/src/hooks/useGetEncounter.ts +3 -3
- package/src/hooks/useGetPatient.ts +3 -3
- package/src/hooks/useGetPatients.ts +32 -0
- package/src/hooks/useGetSystemSetting.ts +36 -0
- package/src/hooks/useKeyPress.ts +5 -5
- package/src/hooks/usePostEndpoint.ts +16 -10
- package/src/hooks/useSearchEndpoint.ts +23 -40
- package/src/hooks/useStartVisit.ts +82 -0
- package/src/index.ts +12 -76
- package/src/patient-card/PatientCard.tsx +8 -20
- package/src/patient-card/index.ts +1 -1
- package/src/patient-card/styles.scss +3 -4
- package/src/routes.json +24 -0
- package/src/setup-tests.ts +1 -1
- package/src/types.ts +20 -0
- package/tools/i18next-parser.config.js +93 -0
- package/translations/am.json +75 -0
- package/translations/ar.json +75 -0
- package/translations/en.json +32 -11
- package/translations/es.json +75 -0
- package/translations/fr.json +75 -0
- package/translations/he.json +75 -0
- package/translations/km.json +75 -0
- package/turbo.json +18 -0
- package/webpack.config.js +1 -1
- package/dist/247.js +0 -1
- package/dist/255.js +0 -1
- package/dist/294.js +0 -2
- package/dist/32.js +0 -1
- package/dist/327.js +0 -1
- package/dist/403.js +0 -2
- package/dist/403.js.LICENSE.txt +0 -14
- package/dist/553.js +0 -2
- package/dist/553.js.LICENSE.txt +0 -14
- package/dist/569.js +0 -2
- package/dist/574.js +0 -1
- package/dist/595.js +0 -2
- package/dist/595.js.LICENSE.txt +0 -1
- package/dist/617.js +0 -1
- package/dist/68.js +0 -2
- package/dist/776.js +0 -1
- package/dist/804.js +0 -1
- package/dist/820.js +0 -1
- package/dist/906.js +0 -1
- package/dist/935.js +0 -2
- package/dist/openmrs-esm-fast-data-entry-app.old +0 -1
- package/src/declarations.d.tsx +0 -2
- /package/dist/{294.js.LICENSE.txt → 540.js.LICENSE.txt} +0 -0
- /package/dist/{935.js.LICENSE.txt → 961.js.LICENSE.txt} +0 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { Layer, Tile, TextInput, TextArea, DatePicker, DatePickerInput } from '@carbon/react';
|
|
2
|
+
import React, { useContext } from 'react';
|
|
3
|
+
import { useConfig } from '@openmrs/esm-framework';
|
|
4
|
+
import { useParams } from 'react-router-dom';
|
|
5
|
+
import styles from './styles.scss';
|
|
6
|
+
import { useTranslation } from 'react-i18next';
|
|
7
|
+
import { Controller, useFormContext } from 'react-hook-form';
|
|
8
|
+
import { AttendanceTable } from './attendance-table';
|
|
9
|
+
import GroupFormWorkflowContext from '../context/GroupFormWorkflowContext';
|
|
10
|
+
import useGetPatients from '../hooks/useGetPatients';
|
|
11
|
+
import ConfigurableQuestionsSection from './configurable-questions/ConfigurableQuestionsSection';
|
|
12
|
+
import useSpecificQuestions from '../hooks/useForm';
|
|
13
|
+
|
|
14
|
+
interface ParamTypes {
|
|
15
|
+
formUuid?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const SessionDetailsForm = () => {
|
|
19
|
+
const { specificQuestions } = useConfig();
|
|
20
|
+
const { formUuid } = useParams() as ParamTypes;
|
|
21
|
+
const { questions } = useSpecificQuestions(formUuid, specificQuestions);
|
|
22
|
+
|
|
23
|
+
const { t } = useTranslation();
|
|
24
|
+
const {
|
|
25
|
+
register,
|
|
26
|
+
formState: { errors },
|
|
27
|
+
control,
|
|
28
|
+
} = useFormContext();
|
|
29
|
+
|
|
30
|
+
const { activeGroupMembers } = useContext(GroupFormWorkflowContext);
|
|
31
|
+
const { patients, isLoading } = useGetPatients(activeGroupMembers);
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div>
|
|
35
|
+
{!isLoading && (
|
|
36
|
+
<div className={styles.formSection}>
|
|
37
|
+
<h4>{t('sessionDetails', '1. Session details')}</h4>
|
|
38
|
+
<div>
|
|
39
|
+
<p>{t('allFieldsRequired', 'All fields are required unless marked optional')}</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={t('requiredField', '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={t('requiredField', 'This field is required')}
|
|
66
|
+
/>
|
|
67
|
+
<Controller
|
|
68
|
+
name="sessionDate"
|
|
69
|
+
control={control}
|
|
70
|
+
rules={{ required: true }}
|
|
71
|
+
render={({ field }) => (
|
|
72
|
+
<DatePicker datePickerType="single" size="md" maxDate={new Date()} {...field}>
|
|
73
|
+
<DatePickerInput
|
|
74
|
+
id="session-date"
|
|
75
|
+
labelText={t('sessionDate', 'Session Date')}
|
|
76
|
+
placeholder="mm/dd/yyyy"
|
|
77
|
+
size="md"
|
|
78
|
+
invalid={errors.sessionDate}
|
|
79
|
+
invalidText={t('requiredField', 'This field is required')}
|
|
80
|
+
/>
|
|
81
|
+
</DatePicker>
|
|
82
|
+
)}
|
|
83
|
+
/>
|
|
84
|
+
<TextArea
|
|
85
|
+
id="text"
|
|
86
|
+
type="text"
|
|
87
|
+
labelText={t('sessionNotes', 'Session Notes')}
|
|
88
|
+
{...register('sessionNotes', { required: true })}
|
|
89
|
+
invalid={errors.sessionNotes}
|
|
90
|
+
invalidText={t('requiredField', 'This field is required')}
|
|
91
|
+
/>
|
|
92
|
+
</div>
|
|
93
|
+
</Layer>
|
|
94
|
+
</Tile>
|
|
95
|
+
</Layer>
|
|
96
|
+
<h4>{t('sessionParticipants', '2. Session participants')}</h4>
|
|
97
|
+
<div>
|
|
98
|
+
<p>
|
|
99
|
+
{t(
|
|
100
|
+
'markAbsentPatients',
|
|
101
|
+
'The patients in this group. Patients that are not present in the session should be marked as absent.',
|
|
102
|
+
)}
|
|
103
|
+
</p>
|
|
104
|
+
</div>
|
|
105
|
+
<Layer>
|
|
106
|
+
<Tile className={styles.formSectionTile}>
|
|
107
|
+
<Layer>
|
|
108
|
+
<div
|
|
109
|
+
style={{
|
|
110
|
+
display: 'flex',
|
|
111
|
+
flexDirection: 'column',
|
|
112
|
+
rowGap: '1.5rem',
|
|
113
|
+
}}
|
|
114
|
+
>
|
|
115
|
+
<AttendanceTable patients={patients} />
|
|
116
|
+
</div>
|
|
117
|
+
</Layer>
|
|
118
|
+
</Tile>
|
|
119
|
+
</Layer>
|
|
120
|
+
{questions?.length > 0 ? (
|
|
121
|
+
<>
|
|
122
|
+
<h4>{t('sessionSpecificDetails', '3. Specific details')}</h4>
|
|
123
|
+
<div>
|
|
124
|
+
<p>
|
|
125
|
+
{t(
|
|
126
|
+
'sessionSpecificDetailsDescription',
|
|
127
|
+
'They will be mapped to form responses for all patients as pre-filled data.',
|
|
128
|
+
)}
|
|
129
|
+
</p>
|
|
130
|
+
</div>
|
|
131
|
+
<Layer>
|
|
132
|
+
<Tile className={styles.formSectionTile}>
|
|
133
|
+
<Layer>
|
|
134
|
+
<div
|
|
135
|
+
style={{
|
|
136
|
+
display: 'flex',
|
|
137
|
+
flexDirection: 'column',
|
|
138
|
+
rowGap: '1.5rem',
|
|
139
|
+
}}
|
|
140
|
+
>
|
|
141
|
+
<ConfigurableQuestionsSection register={register} specificQuestions={questions} />
|
|
142
|
+
</div>
|
|
143
|
+
</Layer>
|
|
144
|
+
</Tile>
|
|
145
|
+
</Layer>
|
|
146
|
+
</>
|
|
147
|
+
) : null}
|
|
148
|
+
</div>
|
|
149
|
+
)}
|
|
150
|
+
</div>
|
|
151
|
+
);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
export default SessionDetailsForm;
|
|
@@ -0,0 +1,99 @@
|
|
|
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 open={cancelModalOpen} setOpen={setCancelModalOpen} context={context} />
|
|
33
|
+
</>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const GroupIdField = () => {
|
|
38
|
+
const { t } = useTranslation();
|
|
39
|
+
const {
|
|
40
|
+
register,
|
|
41
|
+
formState: { errors },
|
|
42
|
+
setValue,
|
|
43
|
+
} = useFormContext();
|
|
44
|
+
const { activeGroupUuid } = useContext(GroupFormWorkflowContext);
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (activeGroupUuid) setValue('groupUuid', activeGroupUuid);
|
|
48
|
+
}, [activeGroupUuid, setValue]);
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<>
|
|
52
|
+
<input
|
|
53
|
+
hidden
|
|
54
|
+
{...register('groupUuid', {
|
|
55
|
+
value: activeGroupUuid,
|
|
56
|
+
required: t('chooseGroupError', 'Please choose a group.'),
|
|
57
|
+
})}
|
|
58
|
+
/>
|
|
59
|
+
{errors.groupUuid && !activeGroupUuid && (
|
|
60
|
+
<div className={styles.formError}>{errors.groupUuid.message as string}</div>
|
|
61
|
+
)}
|
|
62
|
+
</>
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const SessionMetaWorkspace = () => {
|
|
67
|
+
const { t } = useTranslation();
|
|
68
|
+
const { setSessionMeta, workflowState } = useContext(GroupFormWorkflowContext);
|
|
69
|
+
const methods = useForm();
|
|
70
|
+
|
|
71
|
+
const onSubmit = (data) => {
|
|
72
|
+
const { sessionDate, ...rest } = data;
|
|
73
|
+
setSessionMeta({ ...rest, sessionDate: sessionDate[0] });
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
if (workflowState !== 'NEW_GROUP_SESSION') return null;
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<FormProvider {...methods}>
|
|
80
|
+
<form onSubmit={methods.handleSubmit(onSubmit)}>
|
|
81
|
+
<div className={styles.workspace}>
|
|
82
|
+
<div className={styles.formMainContent}>
|
|
83
|
+
<div className={styles.formContainer}>
|
|
84
|
+
<SessionDetailsForm />
|
|
85
|
+
</div>
|
|
86
|
+
<div className={styles.rightPanel}>
|
|
87
|
+
<h4>{t('newGroupSession', 'New Group Session')}</h4>
|
|
88
|
+
<GroupIdField />
|
|
89
|
+
<hr style={{ width: '100%' }} />
|
|
90
|
+
<NewGroupWorkflowButtons />
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
</form>
|
|
95
|
+
</FormProvider>
|
|
96
|
+
);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export default SessionMetaWorkspace;
|
|
@@ -0,0 +1,130 @@
|
|
|
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(GroupFormWorkflowContext);
|
|
21
|
+
const givenName = patient?.name?.[0]?.given?.[0];
|
|
22
|
+
const familyName = patient?.name?.[0]?.family;
|
|
23
|
+
const identifier = patient?.identifier?.[0]?.value;
|
|
24
|
+
|
|
25
|
+
const handleOnChange = (e, { checked }) => {
|
|
26
|
+
if (checked) {
|
|
27
|
+
addPatientUuid(patient.id);
|
|
28
|
+
} else {
|
|
29
|
+
removePatientUuid(patient.id);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
if (!patient) {
|
|
34
|
+
return (
|
|
35
|
+
<TableRow>
|
|
36
|
+
<TableCell>
|
|
37
|
+
<SkeletonText />
|
|
38
|
+
</TableCell>
|
|
39
|
+
<TableCell>
|
|
40
|
+
<SkeletonText />
|
|
41
|
+
</TableCell>
|
|
42
|
+
<TableCell>
|
|
43
|
+
<Checkbox diabled />
|
|
44
|
+
</TableCell>
|
|
45
|
+
</TableRow>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<TableRow>
|
|
51
|
+
<TableCell>{patient.display || patient.displayName || [givenName, familyName].join(' ')}</TableCell>
|
|
52
|
+
<TableCell>{identifier}</TableCell>
|
|
53
|
+
<TableCell>
|
|
54
|
+
<Checkbox
|
|
55
|
+
checked={patientUuids.includes(patient.id)}
|
|
56
|
+
labelText={patient.id}
|
|
57
|
+
hideLabel
|
|
58
|
+
id={`${identifier}-attendance-checkbox`}
|
|
59
|
+
onChange={handleOnChange}
|
|
60
|
+
/>
|
|
61
|
+
</TableCell>
|
|
62
|
+
</TableRow>
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const AttendanceTable = ({ patients }) => {
|
|
67
|
+
const { t } = useTranslation();
|
|
68
|
+
const { activeGroupUuid, activeGroupName, activeGroupMembers } = useContext(GroupFormWorkflowContext);
|
|
69
|
+
|
|
70
|
+
const [isOpen, setOpen] = useState(false);
|
|
71
|
+
|
|
72
|
+
const headers = [t('name', 'Name'), t('identifier', 'Patient ID'), t('patientIsPresent', 'Patient is present')];
|
|
73
|
+
|
|
74
|
+
const onPostCancel = useCallback(() => {
|
|
75
|
+
setOpen(false);
|
|
76
|
+
}, []);
|
|
77
|
+
|
|
78
|
+
const onPostSubmit = useCallback(() => {
|
|
79
|
+
setOpen(false);
|
|
80
|
+
}, []);
|
|
81
|
+
|
|
82
|
+
const newArr = useMemo(() => {
|
|
83
|
+
return activeGroupMembers.map(function (value) {
|
|
84
|
+
const patient = patients.find((patient) => patient.id === value);
|
|
85
|
+
return { uuid: value, ...patient };
|
|
86
|
+
});
|
|
87
|
+
}, [activeGroupMembers, patients]);
|
|
88
|
+
|
|
89
|
+
if (!activeGroupUuid) {
|
|
90
|
+
return <div>{t('selectGroupFirst', 'Please select a group first')}</div>;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<div>
|
|
95
|
+
<span style={{ flexGrow: 1 }} />
|
|
96
|
+
<Button kind="ghost" onClick={() => setOpen(true)}>
|
|
97
|
+
{t('editGroup', 'Edit Group')}
|
|
98
|
+
<Edit size={20} />
|
|
99
|
+
</Button>
|
|
100
|
+
<AddGroupModal
|
|
101
|
+
{...{
|
|
102
|
+
cohortUuid: activeGroupUuid,
|
|
103
|
+
patients: newArr,
|
|
104
|
+
isCreate: false,
|
|
105
|
+
groupName: activeGroupName,
|
|
106
|
+
isOpen: isOpen,
|
|
107
|
+
onPostCancel: onPostCancel,
|
|
108
|
+
onPostSubmit: onPostSubmit,
|
|
109
|
+
}}
|
|
110
|
+
/>
|
|
111
|
+
<Table>
|
|
112
|
+
<TableHead>
|
|
113
|
+
<TableRow>
|
|
114
|
+
{headers.map((header, index) => (
|
|
115
|
+
<TableHeader key={index}>{header}</TableHeader>
|
|
116
|
+
))}
|
|
117
|
+
</TableRow>
|
|
118
|
+
</TableHead>
|
|
119
|
+
<TableBody>
|
|
120
|
+
{activeGroupMembers.map((patientUuid, index) => {
|
|
121
|
+
const patient = patients.find((patient) => patient.id === patientUuid);
|
|
122
|
+
return <PatientRow patient={patient} key={index} />;
|
|
123
|
+
})}
|
|
124
|
+
</TableBody>
|
|
125
|
+
</Table>
|
|
126
|
+
</div>
|
|
127
|
+
);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export default AttendanceTable;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as AttendanceTable } from './AttendanceTable';
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { TextInput, Select, SelectItem } from '@carbon/react';
|
|
3
|
+
import { type FieldValues, type UseFormRegister } from 'react-hook-form';
|
|
4
|
+
import { type SpecificQuestion } from '../../types';
|
|
5
|
+
|
|
6
|
+
interface ConfigurableQuestionsSectionProps {
|
|
7
|
+
specificQuestions: Array<SpecificQuestion>;
|
|
8
|
+
register?: UseFormRegister<FieldValues>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const ConfigurableQuestionsSection: React.FC<ConfigurableQuestionsSectionProps> = ({ register, specificQuestions }) => {
|
|
12
|
+
return (
|
|
13
|
+
<>
|
|
14
|
+
{specificQuestions?.map((specificQuestion) => (
|
|
15
|
+
<div key={specificQuestion.question.id}>
|
|
16
|
+
{specificQuestion?.answers?.length > 0 ? (
|
|
17
|
+
<Select
|
|
18
|
+
{...register(specificQuestion.question.id, { required: false })}
|
|
19
|
+
id={specificQuestion.question.id}
|
|
20
|
+
labelText={specificQuestion.question.display}
|
|
21
|
+
>
|
|
22
|
+
<SelectItem value="" text="" />
|
|
23
|
+
{specificQuestion.answers.map((answer) => (
|
|
24
|
+
<SelectItem key={answer.value} value={answer.value} text={answer.display} />
|
|
25
|
+
))}
|
|
26
|
+
</Select>
|
|
27
|
+
) : (
|
|
28
|
+
<TextInput
|
|
29
|
+
id={specificQuestion.question.id}
|
|
30
|
+
{...register(specificQuestion.question.id, { required: false })}
|
|
31
|
+
type="text"
|
|
32
|
+
labelText={specificQuestion.question.display}
|
|
33
|
+
/>
|
|
34
|
+
)}
|
|
35
|
+
</div>
|
|
36
|
+
))}
|
|
37
|
+
</>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export default ConfigurableQuestionsSection;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import React from
|
|
2
|
-
import { render } from
|
|
3
|
-
import GroupDisplayHeader from
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render } from '@testing-library/react';
|
|
3
|
+
import GroupDisplayHeader from './GroupDisplayHeader';
|
|
4
4
|
|
|
5
|
-
describe(
|
|
6
|
-
it(
|
|
5
|
+
describe('PatientBanner', () => {
|
|
6
|
+
it('renders placeholder information when no data is present', () => {
|
|
7
7
|
render(<GroupDisplayHeader />);
|
|
8
8
|
});
|
|
9
9
|
});
|
|
@@ -1,20 +1,13 @@
|
|
|
1
|
-
import React, { useContext } from
|
|
2
|
-
import { Button } from
|
|
3
|
-
import { Events, Close } from
|
|
4
|
-
import styles from
|
|
5
|
-
import { useTranslation } from
|
|
6
|
-
import GroupFormWorkflowContext from
|
|
7
|
-
import { navigate } from "@openmrs/esm-framework";
|
|
1
|
+
import React, { useContext } from 'react';
|
|
2
|
+
import { Button } from '@carbon/react';
|
|
3
|
+
import { Events, Close } from '@carbon/react/icons';
|
|
4
|
+
import styles from './styles.scss';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import GroupFormWorkflowContext from '../../context/GroupFormWorkflowContext';
|
|
8
7
|
|
|
9
8
|
const GroupDisplayHeader = () => {
|
|
10
|
-
const {
|
|
11
|
-
|
|
12
|
-
activeGroupUuid,
|
|
13
|
-
patientUuids,
|
|
14
|
-
activeSessionMeta,
|
|
15
|
-
unsetGroup,
|
|
16
|
-
destroySession,
|
|
17
|
-
} = useContext(GroupFormWorkflowContext);
|
|
9
|
+
const { activeGroupName, activeGroupUuid, patientUuids, activeSessionMeta, unsetGroup, destroySession } =
|
|
10
|
+
useContext(GroupFormWorkflowContext);
|
|
18
11
|
const { t } = useTranslation();
|
|
19
12
|
|
|
20
13
|
if (!activeGroupUuid) {
|
|
@@ -32,36 +25,27 @@ const GroupDisplayHeader = () => {
|
|
|
32
25
|
</div>
|
|
33
26
|
<div className={styles.groupInfoRow}>
|
|
34
27
|
<span>
|
|
35
|
-
{patientUuids.length} {t(
|
|
28
|
+
{patientUuids.length} {t('members', 'members')}
|
|
36
29
|
</span>
|
|
37
30
|
</div>
|
|
38
31
|
</div>
|
|
39
32
|
{activeSessionMeta?.sessionNotes && (
|
|
40
33
|
<div className={styles.groupMeataContent}>
|
|
41
34
|
<div className={`${styles.groupInfoRow} ${styles.sessionNotesLabel}`}>
|
|
42
|
-
{t(
|
|
43
|
-
</div>
|
|
44
|
-
<div className={styles.groupInfoRow}>
|
|
45
|
-
{activeSessionMeta.sessionNotes}
|
|
35
|
+
{t('sessionNotes', 'Session Notes')}
|
|
46
36
|
</div>
|
|
37
|
+
<div className={styles.groupInfoRow}>{activeSessionMeta.sessionNotes}</div>
|
|
47
38
|
</div>
|
|
48
39
|
)}
|
|
49
40
|
<span style={{ flexGrow: 1 }} />
|
|
50
41
|
<span>
|
|
51
42
|
<Button kind="ghost" onClick={() => unsetGroup()}>
|
|
52
|
-
{t(
|
|
43
|
+
{t('changeGroup', 'Choose a different group')} <Close size={20} />
|
|
53
44
|
</Button>
|
|
54
45
|
</span>
|
|
55
46
|
<span>
|
|
56
|
-
<Button
|
|
57
|
-
|
|
58
|
-
onClick={() => {
|
|
59
|
-
destroySession();
|
|
60
|
-
// eslint-disable-next-line
|
|
61
|
-
navigate({ to: "${openmrsSpaBase}/forms" });
|
|
62
|
-
}}
|
|
63
|
-
>
|
|
64
|
-
{t("cancel", "Cancel")} <Close size={20} />
|
|
47
|
+
<Button kind="ghost" onClick={() => destroySession()}>
|
|
48
|
+
{t('cancel', 'Cancel')} <Close size={20} />
|
|
65
49
|
</Button>
|
|
66
50
|
</span>
|
|
67
51
|
</div>
|
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
@use '@carbon/
|
|
2
|
-
@use '@carbon/
|
|
3
|
-
@
|
|
1
|
+
@use '@carbon/colors';
|
|
2
|
+
@use '@carbon/layout';
|
|
3
|
+
@use '@carbon/type';
|
|
4
4
|
|
|
5
5
|
.container {
|
|
6
|
-
height:
|
|
6
|
+
height: layout.$spacing-11;
|
|
7
7
|
display: flex;
|
|
8
8
|
align-items: center;
|
|
9
|
-
background-color:
|
|
10
|
-
border-top: 0.0125rem solid
|
|
11
|
-
border-bottom: 0.0125rem solid
|
|
12
|
-
padding: 0
|
|
9
|
+
background-color: colors.$white-0;
|
|
10
|
+
border-top: 0.0125rem solid colors.$gray-20;
|
|
11
|
+
border-bottom: 0.0125rem solid colors.$gray-20;
|
|
12
|
+
padding: 0 layout.$spacing-05;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
.photoPlaceholder {
|
|
16
|
-
height:
|
|
17
|
-
width:
|
|
16
|
+
height: layout.$spacing-09;
|
|
17
|
+
width: layout.$spacing-09;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
.groupAvatar {
|
|
21
|
-
width:
|
|
22
|
-
height:
|
|
23
|
-
margin:
|
|
21
|
+
width: layout.$spacing-09;
|
|
22
|
+
height: layout.$spacing-09;
|
|
23
|
+
margin: layout.$spacing-03 layout.$spacing-05 layout.$spacing-03 0;
|
|
24
24
|
border-radius: 1px;
|
|
25
25
|
}
|
|
26
26
|
|
|
@@ -30,29 +30,29 @@
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
.groupInfoContent {
|
|
33
|
-
margin-left:
|
|
34
|
-
margin-right:
|
|
33
|
+
margin-left: layout.$spacing-05;
|
|
34
|
+
margin-right: layout.$spacing-10;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
.groupInfoRow {
|
|
38
38
|
display: flex;
|
|
39
39
|
align-items: center;
|
|
40
40
|
& > button {
|
|
41
|
-
min-height:
|
|
41
|
+
min-height: layout.$spacing-07;
|
|
42
42
|
}
|
|
43
43
|
@include type.type-style('body-compact-02');
|
|
44
|
-
color:
|
|
44
|
+
color: colors.$gray-70;
|
|
45
45
|
column-gap: 0.8rem;
|
|
46
46
|
|
|
47
47
|
}
|
|
48
48
|
.sessionNotesLabel {
|
|
49
49
|
@include type.type-style('label-01');
|
|
50
|
-
margin-bottom:
|
|
50
|
+
margin-bottom: layout.$spacing-01
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
.groupEditBtn {
|
|
54
|
-
color:
|
|
55
|
-
margin:
|
|
54
|
+
color: colors.$gray-100;
|
|
55
|
+
margin: layout.$spacing-03;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
.groupMetaContent {
|