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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/README.md +58 -12
  2. package/__mocks__/react-i18next.js +9 -14
  3. package/dist/101.js +1 -0
  4. package/dist/101.js.map +1 -0
  5. package/dist/132.js +1 -0
  6. package/dist/188.js +1 -0
  7. package/dist/188.js.map +1 -0
  8. package/dist/197.js +1 -0
  9. package/dist/219.js +1 -0
  10. package/dist/219.js.map +1 -0
  11. package/dist/221.js +1 -0
  12. package/dist/221.js.map +1 -0
  13. package/dist/259.js +1 -0
  14. package/dist/259.js.map +1 -0
  15. package/dist/29.js +2 -0
  16. package/dist/29.js.LICENSE.txt +3 -0
  17. package/dist/29.js.map +1 -0
  18. package/dist/300.js +1 -0
  19. package/dist/326.js +1 -0
  20. package/dist/326.js.map +1 -0
  21. package/dist/335.js +1 -0
  22. package/dist/367.js +1 -0
  23. package/dist/367.js.map +1 -0
  24. package/dist/480.js +1 -0
  25. package/dist/540.js +2 -0
  26. package/dist/{382.js.LICENSE.txt → 540.js.LICENSE.txt} +3 -2
  27. package/dist/540.js.map +1 -0
  28. package/dist/55.js +1 -0
  29. package/dist/564.js +1 -0
  30. package/dist/564.js.map +1 -0
  31. package/dist/602.js +1 -0
  32. package/dist/602.js.map +1 -0
  33. package/dist/626.js +2 -0
  34. package/dist/{294.js.LICENSE.txt → 626.js.LICENSE.txt} +3 -8
  35. package/dist/626.js.map +1 -0
  36. package/dist/652.js +1 -0
  37. package/dist/685.js +1 -0
  38. package/dist/685.js.map +1 -0
  39. package/dist/773.js +2 -0
  40. package/dist/773.js.LICENSE.txt +32 -0
  41. package/dist/773.js.map +1 -0
  42. package/dist/893.js +1 -0
  43. package/dist/893.js.map +1 -0
  44. package/dist/91.js +1 -0
  45. package/dist/91.js.map +1 -0
  46. package/dist/941.js +2 -0
  47. package/dist/941.js.LICENSE.txt +30 -0
  48. package/dist/941.js.map +1 -0
  49. package/dist/961.js +2 -0
  50. package/dist/{735.js.LICENSE.txt → 961.js.LICENSE.txt} +6 -16
  51. package/dist/961.js.map +1 -0
  52. package/dist/99.js +1 -0
  53. package/dist/99.js.map +1 -0
  54. package/dist/991.js +1 -0
  55. package/dist/991.js.map +1 -0
  56. package/dist/main.js +1 -0
  57. package/dist/main.js.map +1 -0
  58. package/dist/openmrs-esm-fast-data-entry-app.js +1 -1
  59. package/dist/openmrs-esm-fast-data-entry-app.js.buildmanifest.json +537 -95
  60. package/dist/openmrs-esm-fast-data-entry-app.js.map +1 -1
  61. package/dist/routes.json +1 -0
  62. package/docs/config-icrc-forms.png +0 -0
  63. package/docs/config-other-forms.png +0 -0
  64. package/docs/configuring-form-categories.md +77 -0
  65. package/docs/fde-workflow.mov +0 -0
  66. package/docs/form-workflow-state-diagram.png +0 -0
  67. package/jest.config.json +21 -18
  68. package/package.json +101 -106
  69. package/prettier.config.js +8 -0
  70. package/src/CancelModal.tsx +42 -0
  71. package/src/CompleteModal.tsx +35 -0
  72. package/src/FormBootstrap.tsx +179 -0
  73. package/src/Root.tsx +11 -5
  74. package/src/add-group-modal/AddGroupModal.tsx +249 -0
  75. package/src/add-group-modal/styles.scss +49 -0
  76. package/src/config-schema.ts +124 -31
  77. package/src/constant.ts +1 -1
  78. package/src/context/FormWorkflowContext.tsx +113 -0
  79. package/src/context/FormWorkflowReducer.ts +263 -0
  80. package/src/context/GroupFormWorkflowContext.tsx +155 -0
  81. package/src/context/GroupFormWorkflowReducer.ts +405 -0
  82. package/src/declarations.d.ts +4 -0
  83. package/src/empty-state/EmptyDataIllustration.tsx +39 -0
  84. package/src/empty-state/EmptyState.tsx +28 -0
  85. package/src/empty-state/styles.scss +55 -0
  86. package/src/form-entry-workflow/FormEntryWorkflow.tsx +184 -0
  87. package/src/form-entry-workflow/form-review-card/FormReviewCard.tsx +50 -0
  88. package/src/form-entry-workflow/form-review-card/index.ts +3 -0
  89. package/src/form-entry-workflow/form-review-card/styles.scss +37 -0
  90. package/src/form-entry-workflow/index.ts +3 -0
  91. package/src/form-entry-workflow/patient-banner/PatientBanner.test.tsx +9 -0
  92. package/src/form-entry-workflow/patient-banner/PatientBanner.tsx +73 -0
  93. package/src/form-entry-workflow/patient-banner/index.ts +3 -0
  94. package/src/form-entry-workflow/patient-banner/styles.scss +44 -0
  95. package/src/form-entry-workflow/patient-search-header/PatientSearchHeader.tsx +54 -0
  96. package/src/form-entry-workflow/patient-search-header/index.ts +3 -0
  97. package/src/form-entry-workflow/patient-search-header/styles.scss +25 -0
  98. package/src/form-entry-workflow/styles.scss +63 -0
  99. package/src/form-entry-workflow/workflow-review/WorkflowReview.tsx +37 -0
  100. package/src/form-entry-workflow/workflow-review/index.ts +3 -0
  101. package/src/form-entry-workflow/workflow-review/styles.scss +30 -0
  102. package/src/forms-app-menu-link.tsx +6 -7
  103. package/src/forms-page/FormsPage.tsx +106 -0
  104. package/src/forms-page/forms-table/FormsTable.tsx +117 -0
  105. package/src/forms-page/forms-table/index.ts +3 -0
  106. package/src/forms-page/forms-table/styles.scss +19 -0
  107. package/src/forms-page/index.ts +3 -0
  108. package/src/forms-page/styles.scss +9 -0
  109. package/src/group-form-entry-workflow/GroupFormEntryWorkflow.tsx +26 -0
  110. package/src/group-form-entry-workflow/GroupSessionWorkspace.tsx +207 -0
  111. package/src/group-form-entry-workflow/SessionDetailsForm.tsx +154 -0
  112. package/src/group-form-entry-workflow/SessionMetaWorkspace.tsx +99 -0
  113. package/src/group-form-entry-workflow/attendance-table/AttendanceTable.tsx +130 -0
  114. package/src/group-form-entry-workflow/attendance-table/index.ts +1 -0
  115. package/src/group-form-entry-workflow/configurable-questions/ConfigurableQuestionsSection.tsx +41 -0
  116. package/src/group-form-entry-workflow/group-display-header/GroupDisplayHeader.test.tsx +9 -0
  117. package/src/group-form-entry-workflow/group-display-header/GroupDisplayHeader.tsx +55 -0
  118. package/src/group-form-entry-workflow/group-display-header/index.ts +3 -0
  119. package/src/group-form-entry-workflow/group-display-header/styles.scss +60 -0
  120. package/src/group-form-entry-workflow/group-search/CompactGroupResults.tsx +128 -0
  121. package/src/group-form-entry-workflow/group-search/CompactGroupSearch.tsx +66 -0
  122. package/src/group-form-entry-workflow/group-search/GroupSearch.tsx +134 -0
  123. package/src/group-form-entry-workflow/group-search/compact-group-result.scss +63 -0
  124. package/src/group-form-entry-workflow/group-search/compact-group-search.scss +34 -0
  125. package/src/group-form-entry-workflow/group-search/group-search.scss +93 -0
  126. package/src/group-form-entry-workflow/group-search-header/GroupSearchHeader.tsx +72 -0
  127. package/src/group-form-entry-workflow/group-search-header/index.ts +3 -0
  128. package/src/group-form-entry-workflow/group-search-header/styles.scss +20 -0
  129. package/src/group-form-entry-workflow/index.ts +3 -0
  130. package/src/group-form-entry-workflow/styles.scss +94 -0
  131. package/src/hooks/index.ts +8 -0
  132. package/src/hooks/useForm.ts +56 -0
  133. package/src/hooks/useFormState.ts +23 -0
  134. package/src/hooks/useGetAllForms.ts +37 -0
  135. package/src/hooks/useGetEncounter.ts +21 -0
  136. package/src/hooks/useGetPatient.ts +23 -0
  137. package/src/hooks/useGetPatients.ts +32 -0
  138. package/src/hooks/useGetSystemSetting.ts +36 -0
  139. package/src/hooks/useKeyPress.ts +31 -0
  140. package/src/hooks/usePostEndpoint.ts +76 -0
  141. package/src/hooks/useSearchEndpoint.ts +103 -0
  142. package/src/hooks/useStartVisit.ts +82 -0
  143. package/src/index.ts +18 -66
  144. package/src/patient-card/PatientCard.tsx +55 -0
  145. package/src/patient-card/index.ts +3 -0
  146. package/src/patient-card/styles.scss +44 -0
  147. package/src/routes.json +24 -0
  148. package/src/setup-tests.ts +1 -1
  149. package/src/types.ts +20 -0
  150. package/tools/i18next-parser.config.js +93 -0
  151. package/translations/am.json +75 -0
  152. package/translations/ar.json +75 -0
  153. package/translations/en.json +75 -4
  154. package/translations/es.json +75 -0
  155. package/translations/fr.json +75 -0
  156. package/translations/he.json +75 -0
  157. package/translations/km.json +75 -0
  158. package/tsconfig.json +26 -23
  159. package/turbo.json +18 -0
  160. package/webpack.config.js +1 -1
  161. package/.editorconfig +0 -12
  162. package/.eslintignore +0 -2
  163. package/.eslintrc +0 -4
  164. package/.github/workflows/node.js.yml +0 -79
  165. package/.husky/pre-commit +0 -6
  166. package/.husky/pre-push +0 -6
  167. package/.prettierignore +0 -14
  168. package/dist/24.js +0 -3
  169. package/dist/24.js.LICENSE.txt +0 -16
  170. package/dist/24.js.map +0 -1
  171. package/dist/294.js +0 -3
  172. package/dist/294.js.map +0 -1
  173. package/dist/296.js +0 -2
  174. package/dist/296.js.map +0 -1
  175. package/dist/299.js +0 -2
  176. package/dist/299.js.map +0 -1
  177. package/dist/382.js +0 -3
  178. package/dist/382.js.map +0 -1
  179. package/dist/415.js +0 -2
  180. package/dist/415.js.map +0 -1
  181. package/dist/574.js +0 -1
  182. package/dist/595.js +0 -3
  183. package/dist/595.js.LICENSE.txt +0 -1
  184. package/dist/595.js.map +0 -1
  185. package/dist/69.js +0 -2
  186. package/dist/69.js.map +0 -1
  187. package/dist/735.js +0 -3
  188. package/dist/735.js.map +0 -1
  189. package/dist/777.js +0 -2
  190. package/dist/777.js.map +0 -1
  191. package/dist/860.js +0 -2
  192. package/dist/860.js.map +0 -1
  193. package/dist/906.js +0 -2
  194. package/dist/906.js.map +0 -1
  195. package/dist/openmrs-esm-fast-data-entry-app.old +0 -2
  196. package/src/boxes/extensions/blue-box.tsx +0 -15
  197. package/src/boxes/extensions/box.scss +0 -23
  198. package/src/boxes/extensions/brand-box.tsx +0 -15
  199. package/src/boxes/extensions/red-box.tsx +0 -15
  200. package/src/boxes/slot/boxes.css +0 -23
  201. package/src/boxes/slot/boxes.tsx +0 -19
  202. package/src/declarations.d.tsx +0 -2
  203. package/src/forms/FormsRoot.tsx +0 -32
  204. package/src/forms/FormsTable.tsx +0 -64
  205. package/src/forms/mockData.ts +0 -43
  206. package/src/greeter/greeter.css +0 -4
  207. package/src/greeter/greeter.test.tsx +0 -29
  208. package/src/greeter/greeter.tsx +0 -25
  209. package/src/hello.css +0 -3
  210. package/src/hello.test.tsx +0 -45
  211. package/src/hello.tsx +0 -30
  212. package/src/patient-getter/patient-getter.resource.ts +0 -31
  213. package/src/patient-getter/patient-getter.test.tsx +0 -28
  214. package/src/patient-getter/patient-getter.tsx +0 -28
@@ -0,0 +1,184 @@
1
+ import { ExtensionSlot, useSession } from '@openmrs/esm-framework';
2
+ import { Button } from '@carbon/react';
3
+ import React, { useCallback, useContext, useEffect, useState } from 'react';
4
+ import FormBootstrap from '../FormBootstrap';
5
+ import PatientCard from '../patient-card/PatientCard';
6
+ import styles from './styles.scss';
7
+ import PatientSearchHeader from './patient-search-header';
8
+ import { useTranslation } from 'react-i18next';
9
+ import { v4 as uuid } from 'uuid';
10
+ import FormWorkflowContext, { FormWorkflowProvider } from '../context/FormWorkflowContext';
11
+ import WorkflowReview from './workflow-review';
12
+ import PatientBanner from './patient-banner';
13
+ import CompleteModal from '../CompleteModal';
14
+ import CancelModal from '../CancelModal';
15
+ import useStartVisit from '../hooks/useStartVisit';
16
+
17
+ const WorkflowNavigationButtons = () => {
18
+ const context = useContext(FormWorkflowContext);
19
+ const { workflowState, destroySession } = context;
20
+ const [cancelModalOpen, setCancelModalOpen] = useState(false);
21
+ const [completeModalOpen, setCompleteModalOpen] = useState(false);
22
+ const { t } = useTranslation();
23
+
24
+ if (!workflowState) return null;
25
+
26
+ return (
27
+ <>
28
+ <div className={styles.rightPanelActionButtons}>
29
+ <Button
30
+ kind="secondary"
31
+ onClick={workflowState === 'NEW_PATIENT' ? () => destroySession() : () => setCompleteModalOpen(true)}
32
+ >
33
+ {t('saveAndComplete', 'Save & Complete')}
34
+ </Button>
35
+ <Button kind="tertiary" onClick={() => setCancelModalOpen(true)}>
36
+ {t('cancel', 'Cancel')}
37
+ </Button>
38
+ </div>
39
+ <CancelModal open={cancelModalOpen} setOpen={setCancelModalOpen} context={context} />
40
+ <CompleteModal open={completeModalOpen} setOpen={setCompleteModalOpen} context={context} />
41
+ </>
42
+ );
43
+ };
44
+
45
+ const FormWorkspace = () => {
46
+ const {
47
+ patientUuids,
48
+ activePatientUuid,
49
+ activeEncounterUuid,
50
+ saveEncounter,
51
+ activeFormUuid,
52
+ editEncounter,
53
+ encounters,
54
+ singleSessionVisitTypeUuid,
55
+ } = useContext(FormWorkflowContext);
56
+ const { t } = useTranslation();
57
+
58
+ const [encounter, setEncounter] = useState(null);
59
+ const [visit, setVisit] = useState(null);
60
+ const { sessionLocation } = useSession();
61
+
62
+ const { updateEncounter, success: visitSaveSuccess } = useStartVisit({
63
+ showSuccessNotification: false,
64
+ showErrorNotification: true,
65
+ });
66
+
67
+ const handlePostResponse = (encounter) => {
68
+ if (encounter && encounter.uuid) {
69
+ saveEncounter(encounter.uuid);
70
+ setEncounter(encounter);
71
+ }
72
+ };
73
+
74
+ useEffect(() => {
75
+ if (encounter && visit) {
76
+ // Update encounter so that it belongs to the created visit
77
+ updateEncounter({ uuid: encounter.uuid, visit: visit.uuid });
78
+ }
79
+ }, [encounter, visit, updateEncounter]);
80
+
81
+ useEffect(() => {
82
+ if (visitSaveSuccess) {
83
+ setVisit(visitSaveSuccess.data);
84
+ }
85
+ }, [visitSaveSuccess]);
86
+
87
+ const handleEncounterCreate = useCallback(
88
+ (payload) => {
89
+ payload.location = sessionLocation?.uuid;
90
+ payload.encounterDatetime = payload.encounterDatetime ? payload.encounterDatetime : new Date().toISOString();
91
+ // Create a visit with the same date as the encounter being saved
92
+ const visitStartDatetime = new Date(payload.encounterDatetime);
93
+ const visitStopDatetime = new Date(payload.encounterDatetime);
94
+ const visitInfo = {
95
+ startDatetime: visitStartDatetime,
96
+ stopDatetime: visitStopDatetime,
97
+ uuid: uuid(),
98
+ patient: {
99
+ uuid: activePatientUuid,
100
+ },
101
+ location: {
102
+ uuid: sessionLocation?.uuid,
103
+ },
104
+ visitType: {
105
+ uuid: singleSessionVisitTypeUuid,
106
+ },
107
+ };
108
+
109
+ payload.visit = visitInfo;
110
+ },
111
+ [activePatientUuid, singleSessionVisitTypeUuid, sessionLocation],
112
+ );
113
+
114
+ return (
115
+ <div className={styles.workspace}>
116
+ {!patientUuids.length && (
117
+ <div className={styles.selectPatientMessage}>{t('selectPatientFirst', 'Please select a patient first')}</div>
118
+ )}
119
+ {!!patientUuids.length && (
120
+ <div className={styles.formMainContent}>
121
+ <div className={styles.formContainer}>
122
+ <FormBootstrap
123
+ patientUuid={activePatientUuid}
124
+ encounterUuid={activeEncounterUuid}
125
+ {...{
126
+ formUuid: activeFormUuid,
127
+ handlePostResponse,
128
+ handleEncounterCreate,
129
+ }}
130
+ />
131
+ </div>
132
+ <div className={styles.rightPanel}>
133
+ <h4>{t('formsFilled', 'Forms filled')}</h4>
134
+ <div className={styles.patientCardsSection}>
135
+ {patientUuids.map((patientUuid) => (
136
+ <PatientCard
137
+ key={patientUuid}
138
+ {...{
139
+ patientUuid,
140
+ activePatientUuid,
141
+ editEncounter,
142
+ encounters,
143
+ }}
144
+ />
145
+ ))}
146
+ </div>
147
+ <WorkflowNavigationButtons />
148
+ </div>
149
+ </div>
150
+ )}
151
+ </div>
152
+ );
153
+ };
154
+
155
+ const FormEntryWorkflow = () => {
156
+ const { workflowState } = useContext(FormWorkflowContext);
157
+ return (
158
+ <>
159
+ <div className={styles.breadcrumbsContainer}>
160
+ <ExtensionSlot extensionSlotName="breadcrumbs-slot" />
161
+ </div>
162
+ {workflowState === 'REVIEW' && <WorkflowReview />}
163
+ {workflowState !== 'REVIEW' && (
164
+ <>
165
+ <PatientSearchHeader />
166
+ <PatientBanner />
167
+ <div className={styles.workspaceWrapper}>
168
+ <FormWorkspace />
169
+ </div>
170
+ </>
171
+ )}
172
+ </>
173
+ );
174
+ };
175
+
176
+ const FormEntryWorkflowWrapper = () => {
177
+ return (
178
+ <FormWorkflowProvider>
179
+ <FormEntryWorkflow />
180
+ </FormWorkflowProvider>
181
+ );
182
+ };
183
+
184
+ export default FormEntryWorkflowWrapper;
@@ -0,0 +1,50 @@
1
+ import { Accordion, AccordionItem, Button } from '@carbon/react';
2
+ import React, { useContext } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import FormWorkflowContext from '../../context/FormWorkflowContext';
5
+ import { useGetPatient, useGetEncounter } from '../../hooks';
6
+ import styles from './styles.scss';
7
+
8
+ const FormReviewCard = ({ patientUuid }) => {
9
+ const { encounters, editEncounter } = useContext(FormWorkflowContext);
10
+ const patient = useGetPatient(patientUuid);
11
+ const givenName = patient?.name?.[0]?.given?.[0];
12
+ const familyName = patient?.name?.[0]?.family;
13
+ const identifier = patient?.identifier?.[0]?.value;
14
+ const encounterUuid = encounters?.[patientUuid];
15
+ const { encounter } = useGetEncounter(encounterUuid);
16
+ const { t } = useTranslation();
17
+
18
+ return (
19
+ <div className={styles.formReviewCard}>
20
+ <Accordion align="start">
21
+ <AccordionItem
22
+ title={
23
+ <>
24
+ <span className={styles.identifier}>{identifier}</span>
25
+ <span className={styles.displayName}>
26
+ {givenName} {familyName}
27
+ </span>
28
+ </>
29
+ }
30
+ className={styles.accordionItem}
31
+ >
32
+ {encounter && encounter?.obs && encounter.obs?.length && (
33
+ <div className={styles.dataField}>
34
+ <ul>
35
+ {encounter.obs.map((obs, index) => (
36
+ <li key={index}>{obs.display}</li>
37
+ ))}
38
+ </ul>
39
+ </div>
40
+ )}
41
+ <Button kind="primary" onClick={() => editEncounter(patientUuid)}>
42
+ {t('goToForm', 'Go To Form')}
43
+ </Button>
44
+ </AccordionItem>
45
+ </Accordion>
46
+ </div>
47
+ );
48
+ };
49
+
50
+ export default FormReviewCard;
@@ -0,0 +1,3 @@
1
+ import FormReviewCard from './FormReviewCard';
2
+
3
+ export default FormReviewCard;
@@ -0,0 +1,37 @@
1
+ @use '@carbon/colors';
2
+ @use '@carbon/layout';
3
+ @use '@carbon/type';
4
+
5
+ .formReviewCard {
6
+ background-color: colors.$white-0;
7
+ padding: layout.$spacing-02;
8
+ }
9
+
10
+ .formReviewCard :global(.cds--accordion) :global(.cds--accordion__item) {
11
+ border: none;
12
+ }
13
+
14
+ .formReviewCard :global(.cds--accordion__title) {
15
+ display: flex;
16
+ align-items: baseline;
17
+ column-gap: layout.$spacing-05;
18
+ }
19
+
20
+ .formReviewCard :global(.cds--accordion__content) {
21
+ padding: layout.$spacing-03;
22
+ }
23
+
24
+ .dataField {
25
+ @include type.type-style('code-02');
26
+ background-color: colors.$gray-10;
27
+ padding: layout.$spacing-03;
28
+ }
29
+
30
+ .displayName {
31
+ @include type.type-style('heading-02');
32
+ font-weight: bold;
33
+ }
34
+
35
+ .identifier {
36
+ @include type.type-style('body-compact-01')
37
+ }
@@ -0,0 +1,3 @@
1
+ import FormEntryWorkflow from './FormEntryWorkflow';
2
+
3
+ export default FormEntryWorkflow;
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import { render } from '@testing-library/react';
3
+ import PatientBanner from './PatientBanner';
4
+
5
+ describe('PatientBanner', () => {
6
+ it('renders placeholder information when no data is present', () => {
7
+ render(<PatientBanner />);
8
+ });
9
+ });
@@ -0,0 +1,73 @@
1
+ import { age, ExtensionSlot } from '@openmrs/esm-framework';
2
+ import { SkeletonPlaceholder, SkeletonText } from '@carbon/react';
3
+ import React, { useContext } from 'react';
4
+ import styles from './styles.scss';
5
+ import { useTranslation } from 'react-i18next';
6
+ import useGetPatient from '../../hooks/useGetPatient';
7
+ import FormWorkflowContext from '../../context/FormWorkflowContext';
8
+
9
+ const SkeletonPatientInfo = () => {
10
+ return (
11
+ <div className={styles.container}>
12
+ <SkeletonPlaceholder className={styles.photoPlaceholder} />
13
+ <div className={styles.patientInfoContent}>
14
+ <div className={styles.patientInfoRow}>
15
+ <SkeletonText width="7rem" lineCount={1} />
16
+ </div>
17
+ <div className={styles.patientInfoRow}>
18
+ <span>
19
+ <SkeletonText width="1rem" lineCount={1} />
20
+ </span>
21
+ <span>&middot;</span>
22
+ <span>
23
+ <SkeletonText width="1rem" lineCount={1} />
24
+ </span>
25
+ <span>&middot;</span>
26
+ <span>
27
+ <SkeletonText width="1rem" lineCount={1} />
28
+ </span>
29
+ </div>
30
+ </div>
31
+ </div>
32
+ );
33
+ };
34
+
35
+ const PatientBanner = () => {
36
+ const { activePatientUuid, workflowState } = useContext(FormWorkflowContext);
37
+ const patient = useGetPatient(activePatientUuid);
38
+ const { t } = useTranslation();
39
+ const patientName = `${patient?.name?.[0].given?.join(' ')} ${patient?.name?.[0]?.family}`;
40
+
41
+ const patientPhotoSlotState = React.useMemo(
42
+ () => ({ patientUuid: patient?.id, patientName, size: 'small' }),
43
+ [patient?.id, patientName],
44
+ );
45
+
46
+ if (workflowState === 'NEW_PATIENT') return null;
47
+
48
+ if (!patient) {
49
+ return <SkeletonPatientInfo />;
50
+ }
51
+
52
+ return (
53
+ <div className={styles.container}>
54
+ <ExtensionSlot extensionSlotName="patient-photo-slot" state={patientPhotoSlotState} />
55
+ <div className={styles.patientInfoContent}>
56
+ <div className={styles.patientInfoRow}>
57
+ <span className={styles.patientName}>{patientName}</span>
58
+ </div>
59
+ <div className={styles.patientInfoRow}>
60
+ <span>{(patient.gender ?? t('unknown', 'Unknown')).replace(/^\w/, (c) => c.toUpperCase())}</span>
61
+ <span>&middot;</span>
62
+ <span>{age(patient.birthDate)}</span>
63
+ <span>&middot;</span>
64
+ <span>
65
+ {patient.identifier.length ? patient.identifier.map((identifier) => identifier.value).join(', ') : '--'}
66
+ </span>
67
+ </div>
68
+ </div>
69
+ </div>
70
+ );
71
+ };
72
+
73
+ export default PatientBanner;
@@ -0,0 +1,3 @@
1
+ import PatientBanner from './PatientBanner';
2
+
3
+ export default PatientBanner;
@@ -0,0 +1,44 @@
1
+ @use '@carbon/colors';
2
+ @use '@carbon/layout';
3
+ @use '@carbon/type';
4
+
5
+ .container {
6
+ height: layout.$spacing-11;
7
+ display: flex;
8
+ align-items: center;
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
+ }
14
+
15
+ .photoPlaceholder {
16
+ height: 48px;
17
+ width: 48px;
18
+ }
19
+
20
+ .patientName {
21
+ @include type.type-style('heading-03');
22
+ font-weight: 600;
23
+ }
24
+
25
+ .patientInfoContent {
26
+ width: 100%;
27
+ margin-left: 1rem;
28
+ }
29
+
30
+ .patientInfoRow {
31
+ display: flex;
32
+ align-items: center;
33
+ & > button {
34
+ min-height: 2rem;
35
+ }
36
+ @include type.type-style('body-compact-02');
37
+ color: colors.$gray-70;
38
+ column-gap: 0.8rem;
39
+ }
40
+
41
+ .patientEditBtn {
42
+ color: colors.$gray-100;
43
+ margin: layout.$spacing-03;
44
+ }
@@ -0,0 +1,54 @@
1
+ import { Add, Close } from '@carbon/react/icons';
2
+ import { ExtensionSlot, interpolateUrl, navigate } from '@openmrs/esm-framework';
3
+ import { Button } from '@carbon/react';
4
+ import React, { useContext } from 'react';
5
+ import { Link } from 'react-router-dom';
6
+ import FormWorkflowContext from '../../context/FormWorkflowContext';
7
+ import styles from './styles.scss';
8
+ import { useTranslation } from 'react-i18next';
9
+
10
+ const PatientSearchHeader = () => {
11
+ const { addPatient, workflowState, activeFormUuid } = useContext(FormWorkflowContext);
12
+ const handleSelectPatient = (patient) => {
13
+ addPatient(patient);
14
+ };
15
+ const { t } = useTranslation();
16
+
17
+ if (workflowState !== 'NEW_PATIENT') return null;
18
+
19
+ const afterUrl = encodeURIComponent(`\${openmrsSpaBase}/forms/form/${activeFormUuid}?patientUuid=\${patientUuid}`);
20
+ const patientRegistrationUrl = interpolateUrl(`\${openmrsSpaBase}/patient-registration?afterUrl=${afterUrl}`);
21
+
22
+ return (
23
+ <div className={styles.searchHeaderContainer}>
24
+ <span className={styles.padded}>{t('nextPatient', 'Next patient')}:</span>
25
+ <span className={styles.searchBarWrapper}>
26
+ <ExtensionSlot
27
+ name="patient-search-bar-slot"
28
+ state={{
29
+ selectPatientAction: handleSelectPatient,
30
+ buttonProps: {
31
+ kind: 'primary',
32
+ },
33
+ }}
34
+ />
35
+ </span>
36
+ <span className={styles.padded}>{t('or', 'or')}</span>
37
+ <span>
38
+ <Button onClick={() => navigate({ to: patientRegistrationUrl })}>
39
+ {t('createNewPatient', 'Create new patient')} <Add size={20} />
40
+ </Button>
41
+ </span>
42
+ <span style={{ flexGrow: 1 }} />
43
+ <span>
44
+ <Link to="../">
45
+ <Button kind="ghost">
46
+ {t('cancel', 'Cancel')} <Close size={20} />
47
+ </Button>
48
+ </Link>
49
+ </span>
50
+ </div>
51
+ );
52
+ };
53
+
54
+ export default PatientSearchHeader;
@@ -0,0 +1,3 @@
1
+ import PatientSearchHeader from './PatientSearchHeader';
2
+
3
+ export default PatientSearchHeader;
@@ -0,0 +1,25 @@
1
+ @use '@carbon/colors';
2
+ @use '@carbon/layout';
3
+ @use '@carbon/type';
4
+
5
+ .searchHeaderContainer {
6
+ height: layout.$spacing-11;
7
+ display: flex;
8
+ align-items: center;
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
+ }
14
+
15
+ .searchBarWrapper {
16
+ min-width: 35rem;
17
+ }
18
+
19
+ .searchBarWrapper > div button {
20
+ height: auto;
21
+ }
22
+
23
+ .padded {
24
+ padding: layout.$spacing-05;
25
+ }
@@ -0,0 +1,63 @@
1
+ @use '@carbon/colors';
2
+ @use '@carbon/layout';
3
+ @use '@carbon/type';
4
+
5
+ .breadcrumbsContainer > div > div > nav {
6
+ background-color: colors.$white-0;
7
+ padding: layout.$spacing-04 layout.$spacing-05;
8
+ height: layout.$spacing-08;
9
+ }
10
+
11
+ .workspaceWrapper {
12
+ display: flex;
13
+ justify-content: center;
14
+ }
15
+
16
+ .workspace {
17
+ width: 1100px;
18
+ }
19
+
20
+ .selectPatientMessage {
21
+ @include type.type-style('heading-03');
22
+ margin: layout.$spacing-07;
23
+ text-align: center;
24
+ }
25
+
26
+ .formMainContent {
27
+ display: flex;
28
+ text-align: center;
29
+ margin-top: layout.$spacing-05;
30
+ column-gap: layout.$spacing-05;
31
+ }
32
+
33
+ .formContainer {
34
+ flex-grow: 1;
35
+ max-height: calc(100vh - 14rem);
36
+ overflow-y: scroll;
37
+ text-align: left;
38
+ }
39
+
40
+ .formContainer :global(.cds--form-item) :global(.question-area) {
41
+ max-width: 100%;
42
+ }
43
+
44
+ .rightPanel {
45
+ min-width: 13rem;
46
+ text-align: left;
47
+ overflow-y: scroll;
48
+ }
49
+
50
+ .patientCardsSection {
51
+ margin: layout.$spacing-05 0;
52
+ border-bottom: 1px solid colors.$gray-10;
53
+ }
54
+
55
+ .rightPanelActionButtons {
56
+ display: flex;
57
+ flex-direction: column;
58
+ row-gap: layout.$spacing-03;
59
+ & button {
60
+ width: 100%;
61
+ text-decoration: "none";
62
+ }
63
+ }
@@ -0,0 +1,37 @@
1
+ import { Button } from '@carbon/react';
2
+ import React, { useContext } from 'react';
3
+ import { useNavigate } from 'react-router-dom';
4
+ import FormWorkflowContext from '../../context/FormWorkflowContext';
5
+ import FormReviewCard from '../form-review-card';
6
+ import styles from './styles.scss';
7
+ import { useTranslation } from 'react-i18next';
8
+
9
+ const WorkflowReview = () => {
10
+ const { patientUuids } = useContext(FormWorkflowContext);
11
+ const { t } = useTranslation();
12
+ const navigate = useNavigate();
13
+ return (
14
+ <div className={styles.workspaceWrapper}>
15
+ <div className={styles.workspace}>
16
+ <div className={styles.leftPanel}>
17
+ <h4>{t('review', 'Review')}</h4>
18
+ <div className={styles.navButtons}>
19
+ <Button kind="primary" onClick={() => navigate('/')}>
20
+ {t('save&close', 'Save & Close')}
21
+ </Button>
22
+ <Button kind="tertiary" onClick={() => navigate('/')}>
23
+ {t('cancel', 'Cancel')}
24
+ </Button>
25
+ </div>
26
+ </div>
27
+ <div className={styles.formContainer}>
28
+ {patientUuids.map((patientUuid) => (
29
+ <FormReviewCard patientUuid={patientUuid} key={patientUuid} />
30
+ ))}
31
+ </div>
32
+ </div>
33
+ </div>
34
+ );
35
+ };
36
+
37
+ export default WorkflowReview;
@@ -0,0 +1,3 @@
1
+ import WorkflowReview from './WorkflowReview';
2
+
3
+ export default WorkflowReview;
@@ -0,0 +1,30 @@
1
+ .workspaceWrapper {
2
+ display: flex;
3
+ justify-content: center;
4
+ }
5
+
6
+ .workspace {
7
+ display: flex;
8
+ width: 800px;
9
+ margin-top: 1rem;
10
+ column-gap: 1rem;
11
+ }
12
+
13
+ .leftPanel {
14
+ width: 13rem;
15
+ overflow-y: scroll;
16
+ }
17
+
18
+ .navButtons {
19
+ margin-top: 1rem;
20
+ display: flex;
21
+ flex-direction: column;
22
+ row-gap: 0.5rem;
23
+ }
24
+
25
+ .formContainer {
26
+ flex-grow: 1;
27
+ max-height: calc(100vh - 14rem);
28
+ overflow-y: scroll;
29
+ row-gap: 1rem;
30
+ }
@@ -1,12 +1,11 @@
1
- import React from "react";
2
- import { useTranslation } from "react-i18next";
3
- import { ConfigurableLink } from "@openmrs/esm-framework";
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { ConfigurableLink } from '@openmrs/esm-framework';
4
4
 
5
- export default function OfflineToolsAppMenuLink() {
5
+ export default function FormsAppMenuLink() {
6
6
  const { t } = useTranslation();
7
7
  return (
8
- <ConfigurableLink to="${openmrsSpaBase}/forms">
9
- {t("formsAppMenuLink", "Forms")}
10
- </ConfigurableLink>
8
+ // eslint-disable-next-line
9
+ <ConfigurableLink to="${openmrsSpaBase}/forms">{t('formsAppMenuLink', 'Fast Data Entry')}</ConfigurableLink>
11
10
  );
12
11
  }