@openmrs/esm-fast-data-entry-app 1.0.0-pre.59 → 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 (193) hide show
  1. package/README.md +21 -2
  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 -1
  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/{536.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/{935.js.LICENSE.txt → 961.js.LICENSE.txt} +6 -10
  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 +500 -122
  60. package/dist/openmrs-esm-fast-data-entry-app.js.map +1 -0
  61. package/dist/routes.json +1 -0
  62. package/jest.config.json +21 -18
  63. package/package.json +59 -62
  64. package/prettier.config.js +8 -0
  65. package/src/CancelModal.tsx +42 -0
  66. package/src/CompleteModal.tsx +35 -0
  67. package/src/FormBootstrap.tsx +45 -10
  68. package/src/Root.tsx +11 -9
  69. package/src/add-group-modal/AddGroupModal.tsx +249 -0
  70. package/src/add-group-modal/styles.scss +49 -0
  71. package/src/config-schema.ts +77 -16
  72. package/src/constant.ts +1 -1
  73. package/src/context/FormWorkflowContext.tsx +32 -33
  74. package/src/context/FormWorkflowReducer.ts +53 -67
  75. package/src/context/GroupFormWorkflowContext.tsx +155 -0
  76. package/src/context/GroupFormWorkflowReducer.ts +405 -0
  77. package/src/declarations.d.ts +4 -0
  78. package/src/empty-state/EmptyDataIllustration.tsx +4 -16
  79. package/src/empty-state/EmptyState.tsx +16 -17
  80. package/src/empty-state/styles.scss +14 -14
  81. package/src/form-entry-workflow/FormEntryWorkflow.tsx +89 -125
  82. package/src/{form-review-card → form-entry-workflow/form-review-card}/FormReviewCard.tsx +7 -7
  83. package/src/form-entry-workflow/form-review-card/index.ts +3 -0
  84. package/src/form-entry-workflow/form-review-card/styles.scss +37 -0
  85. package/src/form-entry-workflow/index.ts +1 -1
  86. package/src/form-entry-workflow/patient-banner/PatientBanner.test.tsx +9 -0
  87. package/src/{patient-banner → form-entry-workflow/patient-banner}/PatientBanner.tsx +14 -27
  88. package/src/form-entry-workflow/patient-banner/index.ts +3 -0
  89. package/src/form-entry-workflow/patient-banner/styles.scss +44 -0
  90. package/src/form-entry-workflow/patient-search-header/PatientSearchHeader.tsx +54 -0
  91. package/src/form-entry-workflow/patient-search-header/index.ts +3 -0
  92. package/src/form-entry-workflow/patient-search-header/styles.scss +25 -0
  93. package/src/form-entry-workflow/styles.scss +16 -16
  94. package/src/form-entry-workflow/workflow-review/WorkflowReview.tsx +37 -0
  95. package/src/form-entry-workflow/workflow-review/index.ts +3 -0
  96. package/src/{workflow-review → form-entry-workflow/workflow-review}/styles.scss +0 -4
  97. package/src/forms-app-menu-link.tsx +5 -7
  98. package/src/forms-page/FormsPage.tsx +48 -37
  99. package/src/forms-page/forms-table/FormsTable.tsx +117 -0
  100. package/src/forms-page/forms-table/index.ts +3 -0
  101. package/src/forms-page/forms-table/styles.scss +19 -0
  102. package/src/forms-page/index.ts +1 -1
  103. package/src/forms-page/styles.scss +3 -5
  104. package/src/group-form-entry-workflow/GroupFormEntryWorkflow.tsx +26 -0
  105. package/src/group-form-entry-workflow/GroupSessionWorkspace.tsx +207 -0
  106. package/src/group-form-entry-workflow/SessionDetailsForm.tsx +154 -0
  107. package/src/group-form-entry-workflow/SessionMetaWorkspace.tsx +99 -0
  108. package/src/group-form-entry-workflow/attendance-table/AttendanceTable.tsx +130 -0
  109. package/src/group-form-entry-workflow/attendance-table/index.ts +1 -0
  110. package/src/group-form-entry-workflow/configurable-questions/ConfigurableQuestionsSection.tsx +41 -0
  111. package/src/group-form-entry-workflow/group-display-header/GroupDisplayHeader.test.tsx +9 -0
  112. package/src/group-form-entry-workflow/group-display-header/GroupDisplayHeader.tsx +55 -0
  113. package/src/group-form-entry-workflow/group-display-header/index.ts +3 -0
  114. package/src/group-form-entry-workflow/group-display-header/styles.scss +60 -0
  115. package/src/group-form-entry-workflow/group-search/CompactGroupResults.tsx +128 -0
  116. package/src/group-form-entry-workflow/group-search/CompactGroupSearch.tsx +66 -0
  117. package/src/group-form-entry-workflow/group-search/GroupSearch.tsx +134 -0
  118. package/src/group-form-entry-workflow/group-search/compact-group-result.scss +63 -0
  119. package/src/group-form-entry-workflow/group-search/compact-group-search.scss +34 -0
  120. package/src/group-form-entry-workflow/group-search/group-search.scss +93 -0
  121. package/src/group-form-entry-workflow/group-search-header/GroupSearchHeader.tsx +72 -0
  122. package/src/group-form-entry-workflow/group-search-header/index.ts +3 -0
  123. package/src/group-form-entry-workflow/group-search-header/styles.scss +20 -0
  124. package/src/group-form-entry-workflow/index.ts +3 -0
  125. package/src/group-form-entry-workflow/styles.scss +94 -0
  126. package/src/hooks/index.ts +7 -5
  127. package/src/hooks/useForm.ts +56 -0
  128. package/src/hooks/useFormState.ts +3 -3
  129. package/src/hooks/useGetAllForms.ts +7 -15
  130. package/src/hooks/useGetEncounter.ts +3 -3
  131. package/src/hooks/useGetPatient.ts +3 -3
  132. package/src/hooks/useGetPatients.ts +32 -0
  133. package/src/hooks/useGetSystemSetting.ts +36 -0
  134. package/src/hooks/useKeyPress.ts +31 -0
  135. package/src/hooks/usePostEndpoint.ts +76 -0
  136. package/src/hooks/useSearchEndpoint.ts +103 -0
  137. package/src/hooks/useStartVisit.ts +82 -0
  138. package/src/index.ts +12 -72
  139. package/src/patient-card/PatientCard.tsx +10 -20
  140. package/src/patient-card/index.ts +1 -1
  141. package/src/patient-card/styles.scss +8 -8
  142. package/src/routes.json +24 -0
  143. package/src/setup-tests.ts +1 -1
  144. package/src/types.ts +20 -0
  145. package/tools/i18next-parser.config.js +93 -0
  146. package/translations/am.json +75 -0
  147. package/translations/ar.json +75 -0
  148. package/translations/en.json +57 -2
  149. package/translations/es.json +75 -0
  150. package/translations/fr.json +75 -0
  151. package/translations/he.json +75 -0
  152. package/translations/km.json +75 -0
  153. package/turbo.json +18 -0
  154. package/webpack.config.js +1 -1
  155. package/.editorconfig +0 -12
  156. package/.eslintignore +0 -2
  157. package/.eslintrc.js +0 -10
  158. package/.github/pull_request_template.md +0 -18
  159. package/.github/workflows/node.js.yml +0 -121
  160. package/.husky/pre-push +0 -1
  161. package/.prettierignore +0 -14
  162. package/dist/187.js +0 -1
  163. package/dist/247.js +0 -1
  164. package/dist/294.js +0 -2
  165. package/dist/312.js +0 -1
  166. package/dist/412.js +0 -1
  167. package/dist/536.js +0 -2
  168. package/dist/574.js +0 -1
  169. package/dist/592.js +0 -1
  170. package/dist/595.js +0 -2
  171. package/dist/595.js.LICENSE.txt +0 -1
  172. package/dist/776.js +0 -1
  173. package/dist/804.js +0 -1
  174. package/dist/880.js +0 -2
  175. package/dist/880.js.LICENSE.txt +0 -20
  176. package/dist/906.js +0 -1
  177. package/dist/935.js +0 -2
  178. package/dist/990.js +0 -1
  179. package/dist/openmrs-esm-fast-data-entry-app.old +0 -1
  180. package/src/declarations.d.tsx +0 -2
  181. package/src/form-review-card/index.ts +0 -3
  182. package/src/form-review-card/styles.scss +0 -38
  183. package/src/forms-table/FormsTable.tsx +0 -123
  184. package/src/forms-table/index.ts +0 -3
  185. package/src/forms-table/styles.scss +0 -20
  186. package/src/patient-banner/PatientBanner.test.tsx +0 -9
  187. package/src/patient-banner/index.ts +0 -3
  188. package/src/patient-banner/styles.scss +0 -44
  189. package/src/patient-search-header/PatientSearchHeader.tsx +0 -61
  190. package/src/patient-search-header/index.ts +0 -3
  191. package/src/patient-search-header/styles.scss +0 -21
  192. package/src/workflow-review/WorkflowReview.tsx +0 -35
  193. package/src/workflow-review/index.ts +0 -3
@@ -0,0 +1,66 @@
1
+ import React, { useState } from 'react';
2
+ import { type GroupType } from '../../context/GroupFormWorkflowContext';
3
+ import styles from './compact-group-search.scss';
4
+ import GroupSearch from './GroupSearch';
5
+ import { Button, Search } from '@carbon/react';
6
+ import { useTranslation } from 'react-i18next';
7
+ import debounce from 'lodash-es/debounce';
8
+
9
+ interface CompactGroupSearchProps {
10
+ selectGroupAction?: (group: GroupType) => void;
11
+ }
12
+
13
+ const CompactGroupSearch: React.FC<CompactGroupSearchProps> = ({ selectGroupAction }) => {
14
+ const { t } = useTranslation();
15
+ const [query, setQuery] = useState('');
16
+ const [dropdownShown, setDropdownShown] = useState(false);
17
+
18
+ const onGroupSelect = (group) => {
19
+ selectGroupAction(group);
20
+ setDropdownShown(false);
21
+ setQuery('');
22
+ };
23
+
24
+ const handleSearchChange = (e) => {
25
+ debounce((q) => {
26
+ setDropdownShown(!!e.length);
27
+ setQuery(q);
28
+ }, 300);
29
+ setQuery(e);
30
+ if (e.length) {
31
+ setDropdownShown(true);
32
+ } else {
33
+ setDropdownShown(false);
34
+ }
35
+ };
36
+
37
+ return (
38
+ <div className={styles.patientSearchBar}>
39
+ <div className={styles.searchArea}>
40
+ <Search
41
+ autoFocus
42
+ className={styles.patientSearchInput}
43
+ closeButtonLabelText={t('clearSearch', 'Clear')}
44
+ labelText=""
45
+ onChange={(event) => {
46
+ handleSearchChange(event.target.value);
47
+ }}
48
+ onClear={() => undefined}
49
+ placeholder={t('searchForGroup', 'Search for a group by name')}
50
+ size="sm"
51
+ value={query}
52
+ />
53
+ <Button kind="secondary" size="sm">
54
+ {t('search', 'Search')}
55
+ </Button>
56
+ </div>
57
+ {dropdownShown && (
58
+ <div className={styles.floatingSearchResultsContainer}>
59
+ <GroupSearch query={query} selectGroupAction={onGroupSelect} />
60
+ </div>
61
+ )}
62
+ </div>
63
+ );
64
+ };
65
+
66
+ export default CompactGroupSearch;
@@ -0,0 +1,134 @@
1
+ import React, { useCallback, useRef } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { Layer, Tile, Loading } from '@carbon/react';
4
+ import styles from './group-search.scss';
5
+ import { EmptyDataIllustration } from '../../empty-state/EmptyDataIllustration';
6
+ import CompactGroupResults, { SearchResultSkeleton } from './CompactGroupResults';
7
+ import { type GroupType } from '../../context/GroupFormWorkflowContext';
8
+ import { useSearchCohortInfinite } from '../../hooks/useSearchEndpoint';
9
+
10
+ interface GroupSearchProps {
11
+ query: string;
12
+ selectGroupAction?: (group: GroupType) => void;
13
+ }
14
+
15
+ const GroupSearch: React.FC<GroupSearchProps> = ({ query = '', selectGroupAction }) => {
16
+ const { t } = useTranslation();
17
+ const {
18
+ isLoading,
19
+ data: results,
20
+ error,
21
+ loadingNewData,
22
+ setPage,
23
+ hasMore,
24
+ totalResults,
25
+ } = useSearchCohortInfinite({
26
+ searchTerm: query,
27
+ searching: !!query,
28
+ parameters: {
29
+ v: 'full',
30
+ },
31
+ });
32
+
33
+ const lastItem = useRef(null);
34
+ const observer = useRef(null);
35
+ const loadingRef = useCallback(
36
+ (node) => {
37
+ if (loadingNewData) {
38
+ return;
39
+ }
40
+ if (observer.current) {
41
+ observer.current.disconnect();
42
+ }
43
+ observer.current = new IntersectionObserver(
44
+ (entries) => {
45
+ if (entries[0].isIntersecting && hasMore) {
46
+ setPage((page) => page + 1);
47
+ }
48
+ },
49
+ {
50
+ threshold: 0.75,
51
+ },
52
+ );
53
+ if (node) {
54
+ observer.current.observe(node);
55
+ }
56
+ },
57
+ [loadingNewData, hasMore, setPage],
58
+ );
59
+
60
+ if (error) {
61
+ return (
62
+ <div className={styles.searchResults}>
63
+ <Layer>
64
+ <Tile className={styles.emptySearchResultsTile}>
65
+ <EmptyDataIllustration />
66
+ <div>
67
+ <p className={styles.errorMessage}>{t('error', 'Error')}</p>
68
+ <p className={styles.errorCopy}>
69
+ {t(
70
+ 'errorCopy',
71
+ 'Sorry, there was an error. You can try to reload this page, or contact the site administrator and quote the error code above.',
72
+ )}
73
+ </p>
74
+ </div>
75
+ </Tile>
76
+ </Layer>
77
+ </div>
78
+ );
79
+ }
80
+
81
+ if (isLoading) {
82
+ return (
83
+ <div className={styles.searchResultsContainer}>
84
+ <SearchResultSkeleton />
85
+ <SearchResultSkeleton />
86
+ <SearchResultSkeleton />
87
+ <SearchResultSkeleton />
88
+ <SearchResultSkeleton />
89
+ </div>
90
+ );
91
+ }
92
+
93
+ if (results?.length === 0) {
94
+ return (
95
+ <div className={styles.searchResults}>
96
+ <Layer>
97
+ <Tile className={styles.emptySearchResultsTile}>
98
+ <EmptyDataIllustration />
99
+ <p className={styles.emptyResultText}>{t('noGroupsFoundMessage', 'Sorry, no groups have been found')}</p>
100
+ <p className={styles.actionText}>
101
+ <span>{t('trySearchWithPatientUniqueID', "Try searching with the cohort's description")}</span>
102
+ <br />
103
+ <span>{t('orLabelName', 'OR label name')}</span>
104
+ </p>
105
+ </Tile>
106
+ </Layer>
107
+ </div>
108
+ );
109
+ }
110
+
111
+ return (
112
+ <div className={styles.searchResultsContainer}>
113
+ <div
114
+ className={styles.searchResults}
115
+ style={{
116
+ maxHeight: '22rem',
117
+ }}
118
+ >
119
+ <p className={styles.resultsText}>
120
+ {totalResults} {t('searchResultsText', 'search result(s)')}
121
+ </p>
122
+ <CompactGroupResults groups={results} selectGroupAction={selectGroupAction} lastRef={lastItem} />
123
+ <div ref={lastItem}>
124
+ <div className={styles.lastItem} ref={loadingRef}>
125
+ {hasMore && <Loading withOverlay={false} small />}
126
+ {!hasMore && <p>{t('noMoreResults', 'End of search results')}</p>}
127
+ </div>
128
+ </div>
129
+ </div>
130
+ </div>
131
+ );
132
+ };
133
+
134
+ export default GroupSearch;
@@ -0,0 +1,63 @@
1
+ @use '@carbon/colors';
2
+ @use '@carbon/layout';
3
+ @use '@carbon/type';
4
+
5
+ .patientSearchResult {
6
+ text-decoration: none;
7
+ display: flex;
8
+ align-items: center;
9
+ border-bottom: 1px solid colors.$gray-20;
10
+ padding: 0 layout.$spacing-04;
11
+
12
+ &:hover,
13
+ &:focus {
14
+ background-color: colors.$gray-10;
15
+ }
16
+ }
17
+
18
+ .patientSearchResultSelected {
19
+ background-color: colors.$gray-20;
20
+ }
21
+
22
+ .patientBanner {
23
+ display: flex;
24
+ }
25
+
26
+ .patientName {
27
+ @include type.type-style('heading-02');
28
+ }
29
+
30
+ .patientAvatar {
31
+ width: layout.$spacing-09;
32
+ height: layout.$spacing-09;
33
+ margin: layout.$spacing-03 layout.$spacing-05 layout.$spacing-03 0;
34
+ border-radius: 1px;
35
+ }
36
+
37
+ .patientInfo {
38
+ width: 100%;
39
+ display: flex;
40
+ flex-flow: column wrap;
41
+ margin: layout.$spacing-05;
42
+ cursor: pointer;
43
+ }
44
+
45
+ .demographics {
46
+ margin-top: layout.$spacing-03;
47
+ @include type.type-style('body-compact-02');
48
+ color: colors.$gray-70;
49
+ }
50
+
51
+ .identifiers {
52
+ @include type.type-style('body-compact-02');
53
+ color: colors.$gray-50;
54
+ }
55
+
56
+ .actionsContainer {
57
+ padding-top: layout.$spacing-03;
58
+ margin-top: layout.$spacing-05;
59
+ }
60
+
61
+ .middot {
62
+ margin: 0 layout.$spacing-03;
63
+ }
@@ -0,0 +1,34 @@
1
+ @use '@carbon/colors';
2
+ @use '@carbon/layout';
3
+ @use '@carbon/type';
4
+
5
+ .patientSearchBar {
6
+ width: 50vw;
7
+ position: relative;
8
+ }
9
+
10
+ .floatingSearchResultsContainer {
11
+ position: absolute;
12
+ overflow-y: auto;
13
+ box-shadow: 0 layout.$spacing-03 layout.$spacing-05 colors.$gray-20;
14
+ z-index: 99;
15
+ border: 0 1px 1px 1px solid colors.$gray-20;
16
+ width: 100%;
17
+ background-color: colors.$white-0;
18
+ }
19
+
20
+ .searchArea {
21
+ width: inherit;
22
+ display: flex;
23
+ justify-content: center;
24
+ align-items: center;
25
+ border: 1px solid colors.$gray-50;
26
+ }
27
+
28
+ .patientSearchInput {
29
+ border: none;
30
+ }
31
+
32
+ .patientSearchInput input:focus {
33
+ outline: 1px solid 1px solid colors.$orange-40;
34
+ }
@@ -0,0 +1,93 @@
1
+ @use '@carbon/colors';
2
+ @use '@carbon/layout';
3
+ @use '@carbon/type';
4
+
5
+ .searchResultsContainer {
6
+ width: 100%;
7
+ background-color: colors.$white-0;
8
+
9
+ a {
10
+ text-decoration: none;
11
+ @include type.type-style('heading-02');
12
+ color: colors.$gray-70;
13
+ margin: 0rem;
14
+ }
15
+ }
16
+
17
+ :global(.omrs-breakpoint-lt-desktop) .searchResultsContainer {
18
+ top: 6.25rem;
19
+ }
20
+
21
+ :global(.omrs-breakpoint-gt-tablet) .searchResultsContainer {
22
+ padding: 0;
23
+ top: layout.$spacing-09;
24
+ }
25
+
26
+ .searchResults {
27
+ width: 100%;
28
+ }
29
+
30
+
31
+ .searchTerm {
32
+ @include type.type-style('heading-03');
33
+ margin-top: 0.375rem;
34
+ }
35
+
36
+ .resultsText {
37
+ @include type.type-style('label-01');
38
+ color: colors.$gray-70;
39
+ line-height: layout.$spacing-05;
40
+ margin: layout.$spacing-03 layout.$spacing-05;
41
+ }
42
+
43
+ .helperText {
44
+ color: var(--omrs-color-ink-medium-contrast);
45
+ margin-left: 2.375rem;
46
+ }
47
+
48
+ .emptyResultText {
49
+ @include type.type-style('heading-compact-01');
50
+ color: colors.$gray-70;
51
+ margin-top: layout.$spacing-05;
52
+ margin-bottom: 0.313rem;
53
+ }
54
+
55
+ .actionText {
56
+ @include type.type-style('body-01');
57
+ color: colors.$gray-70;
58
+ }
59
+
60
+ .pagination {
61
+ display: flex;
62
+ justify-content: space-evenly;
63
+ padding: 4.688rem 0 layout.$spacing-06;
64
+ }
65
+
66
+ .emptySearchResultsTile {
67
+ text-align: center;
68
+ margin-top: layout.$spacing-05;
69
+ padding: layout.$spacing-09 0rem;
70
+ }
71
+
72
+ :global(.omrs-breakpoint-gt-tablet) .emptySearchResultsTile {
73
+ margin: layout.$spacing-05;
74
+ }
75
+
76
+ .errorMessage {
77
+ @include type.type-style('heading-compact-02');
78
+ margin-top: 2.25rem;
79
+ margin-bottom: layout.$spacing-03;
80
+ }
81
+
82
+ .errorCopy {
83
+ margin-bottom: layout.$spacing-03;
84
+ @include type.type-style('body-01');
85
+ color: colors.$gray-70;
86
+ }
87
+
88
+ .lastItem {
89
+ padding: layout.$spacing-05;
90
+ display: flex;
91
+ justify-content: center;
92
+ align-items: center;
93
+ }
@@ -0,0 +1,72 @@
1
+ import { Close, Add } from '@carbon/react/icons';
2
+ import { Button } from '@carbon/react';
3
+ import React, { useCallback, useContext, useState } from 'react';
4
+ import GroupFormWorkflowContext from '../../context/GroupFormWorkflowContext';
5
+ import styles from './styles.scss';
6
+ import { useTranslation } from 'react-i18next';
7
+ import CompactGroupSearch from '../group-search/CompactGroupSearch';
8
+ import AddGroupModal from '../../add-group-modal/AddGroupModal';
9
+
10
+ const GroupSearchHeader = () => {
11
+ const { t } = useTranslation();
12
+ const { activeGroupUuid, setGroup, destroySession } = useContext(GroupFormWorkflowContext);
13
+ const [isOpen, setOpen] = useState(false);
14
+ const handleSelectGroup = (group) => {
15
+ group.cohortMembers.sort((a, b) => {
16
+ const aName = a?.patient?.person?.names?.[0]?.display;
17
+ const bName = b?.patient?.person?.names?.[0]?.display;
18
+ return aName.localeCompare(bName, undefined, { sensitivity: 'base' });
19
+ });
20
+ setGroup(group);
21
+ };
22
+
23
+ const handleCancel = useCallback(() => {
24
+ setOpen(false);
25
+ }, []);
26
+
27
+ const onPostSubmit = useCallback(() => {
28
+ setOpen(false);
29
+ }, []);
30
+
31
+ const handleOpenClick = useCallback(() => {
32
+ setOpen(true);
33
+ }, []);
34
+
35
+ if (activeGroupUuid) return null;
36
+
37
+ return (
38
+ <div className={styles.searchHeaderContainer}>
39
+ <span className={styles.padded}>{t('findGroup', 'Find group')}:</span>
40
+ <span className={styles.searchBarWrapper}>
41
+ <CompactGroupSearch selectGroupAction={handleSelectGroup} />
42
+ </span>
43
+ <span className={styles.padded}>{t('or', 'or')}</span>
44
+ <span>
45
+ <Button onClick={handleOpenClick} renderIcon={Add} iconDescription="Add">
46
+ {t('createNewGroup', 'Create New Group')}
47
+ </Button>
48
+ <AddGroupModal
49
+ {...{
50
+ isCreate: true,
51
+ isOpen: isOpen,
52
+ onPostCancel: handleCancel,
53
+ onPostSubmit: onPostSubmit,
54
+ }}
55
+ />
56
+ </span>
57
+ <span style={{ flexGrow: 1 }} />
58
+ <span>
59
+ <Button
60
+ kind="ghost"
61
+ onClick={() => {
62
+ destroySession();
63
+ }}
64
+ >
65
+ {t('cancel', 'Cancel')} <Close size={20} />
66
+ </Button>
67
+ </span>
68
+ </div>
69
+ );
70
+ };
71
+
72
+ export default GroupSearchHeader;
@@ -0,0 +1,3 @@
1
+ import GroupSearchHeader from './GroupSearchHeader';
2
+
3
+ export default GroupSearchHeader;
@@ -0,0 +1,20 @@
1
+ @use '@carbon/colors';
2
+ @use '@carbon/layout';
3
+
4
+ .searchHeaderContainer {
5
+ height: layout.$spacing-11;
6
+ display: flex;
7
+ align-items: center;
8
+ background-color: colors.$white-0;
9
+ border-top: 0.0125rem solid colors.$gray-20;
10
+ border-bottom: 0.0125rem solid colors.$gray-20;
11
+ padding: 0 layout.$spacing-05;
12
+ }
13
+
14
+ .searchBarWrapper {
15
+ min-width: 35rem;
16
+ }
17
+
18
+ .padded {
19
+ padding: layout.$spacing-05;
20
+ }
@@ -0,0 +1,3 @@
1
+ import GroupFormEntryWorkflow from './GroupFormEntryWorkflow';
2
+
3
+ export default GroupFormEntryWorkflow;
@@ -0,0 +1,94 @@
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
+ :global(.omrs-breakpoint-lt-large-desktop) .workspace {
21
+ width: 1000px;
22
+ }
23
+
24
+ :global(.omrs-breakpoint-lt-small-desktop) .workspace {
25
+ // there's only so much we can do here. Currenlty the design does not support tablet
26
+ width: 100vw;
27
+ padding: 0 layout.$spacing-04;
28
+ }
29
+
30
+ .selectPatientMessage {
31
+ @include type.type-style('heading-03');
32
+ margin: layout.$spacing-07;
33
+ text-align: center;
34
+ }
35
+
36
+ .formMainContent {
37
+ display: flex;
38
+ text-align: center;
39
+ margin-top: layout.$spacing-05;
40
+ column-gap: layout.$spacing-05;
41
+ }
42
+
43
+ .formContainer {
44
+ flex-grow: 1;
45
+ max-height: calc(100vh - 14rem);
46
+ overflow-y: scroll;
47
+ text-align: left;
48
+ }
49
+
50
+ .formContainer :global(.cds--form-item) :global(.question-area) {
51
+ max-width: 100%;
52
+ }
53
+
54
+ .rightPanel {
55
+ min-width: 13rem;
56
+ text-align: left;
57
+ overflow-y: scroll;
58
+ display: flex;
59
+ flex-direction: column;
60
+ row-gap: layout.$spacing-05;
61
+ }
62
+
63
+ .patientCardsSection {
64
+ margin: layout.$spacing-05 0;
65
+ border-bottom: 1px solid colors.$gray-10;
66
+ }
67
+
68
+ .rightPanelActionButtons {
69
+ display: flex;
70
+ flex-direction: column;
71
+ row-gap: layout.$spacing-03;
72
+ & button {
73
+ width: 100%;
74
+ text-decoration: "none";
75
+ }
76
+ }
77
+
78
+ .formSection {
79
+ display: flex;
80
+ flex-direction: column;
81
+ row-gap: 1rem;
82
+ justify-content: flex-start;
83
+ align-items: flex-start;
84
+ text-align: left;
85
+ }
86
+
87
+ .formSectionTile {
88
+ width: 500px;
89
+ }
90
+
91
+ .formError {
92
+ @include type.type-style("helper-text-02");
93
+ color: colors.$red-60;
94
+ }
@@ -1,6 +1,8 @@
1
- import useGetAllForms from "./useGetAllForms";
2
- import useGetPatient from "./useGetPatient";
3
- import useFormState from "./useFormState";
4
- import useGetEncounter from "./useGetEncounter";
1
+ import useGetAllForms from './useGetAllForms';
2
+ import useGetPatient from './useGetPatient';
3
+ import useFormState from './useFormState';
4
+ import useGetEncounter from './useGetEncounter';
5
+ import useForm from './useForm';
5
6
 
6
- export { useGetAllForms, useGetPatient, useFormState, useGetEncounter };
7
+ export { useGetAllForms, useGetPatient, useFormState, useGetEncounter, useForm };
8
+ export * from './usePostEndpoint';
@@ -0,0 +1,56 @@
1
+ import { type FetchResponse, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
2
+ import useSWR from 'swr';
3
+ import { type SpecificQuestion, type SpecificQuestionConfig } from '../types';
4
+ import { useMemo } from 'react';
5
+
6
+ const formUrl = `${restBaseUrl}/o3/forms`;
7
+
8
+ export const useSpecificQuestions = (formUuid: string, specificQuestionConfig: Array<SpecificQuestionConfig>) => {
9
+ const specificQuestionsToLoad = useMemo(
10
+ () => getQuestionIdsByFormId(formUuid, specificQuestionConfig),
11
+ [formUuid, specificQuestionConfig],
12
+ );
13
+
14
+ const { data, error } = useSWR<FetchResponse, Error>(
15
+ specificQuestionsToLoad ? `${formUrl}/${formUuid}` : null,
16
+ openmrsFetch,
17
+ );
18
+
19
+ const specificQuestions = getQuestionsByIds(specificQuestionsToLoad, data?.data);
20
+
21
+ return {
22
+ questions: specificQuestions || null,
23
+ isError: error,
24
+ isLoading: !data && !error,
25
+ };
26
+ };
27
+
28
+ function getQuestionIdsByFormId(formUuid: string, specificQuestionConfig: Array<SpecificQuestionConfig>) {
29
+ const matchingQuestions = specificQuestionConfig.filter((question) => question.forms.includes(formUuid));
30
+ return matchingQuestions.map((question) => question.questionId);
31
+ }
32
+
33
+ function getQuestionsByIds(questionIds, formSchema): Array<SpecificQuestion> {
34
+ if (!formSchema || questionIds.lenght <= 0) {
35
+ return [];
36
+ }
37
+ const conceptLabels = formSchema.conceptReferences;
38
+ return formSchema.pages.flatMap((page) =>
39
+ page.sections.flatMap((section) =>
40
+ section.questions
41
+ .filter((question) => questionIds.includes(question.id))
42
+ .map((question) => ({
43
+ question: {
44
+ display: question.label ?? conceptLabels[question.questionOptions.concept]?.display,
45
+ id: question.id,
46
+ },
47
+ answers: (question.questionOptions.answers ?? []).map((answer) => ({
48
+ value: answer.concept,
49
+ display: answer.label ?? conceptLabels[answer.concept]?.display,
50
+ })),
51
+ })),
52
+ ),
53
+ );
54
+ }
55
+
56
+ export default useSpecificQuestions;
@@ -1,4 +1,4 @@
1
- import { useEffect, useState } from "react";
1
+ import { useEffect, useState } from 'react';
2
2
 
3
3
  const useFormState = (formUuid) => {
4
4
  const [state, setState] = useState(null);
@@ -10,10 +10,10 @@ const useFormState = (formUuid) => {
10
10
  }
11
11
  };
12
12
 
13
- window.addEventListener("ampath-form-state", handler);
13
+ window.addEventListener('ampath-form-state', handler);
14
14
 
15
15
  return () => {
16
- window.removeEventListener("ampath-form-state", handler);
16
+ window.removeEventListener('ampath-form-state', handler);
17
17
  };
18
18
  }, [formUuid]);
19
19