@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,3 @@
1
+ import WorkflowReview from './WorkflowReview';
2
+
3
+ export default WorkflowReview;
@@ -1,7 +1,3 @@
1
- @import "~@openmrs/esm-styleguide/src/vars";
2
- @import "~carbon-components/src/globals/scss/vars";
3
- @import "~carbon-components/src/globals/scss/mixins";
4
-
5
1
  .workspaceWrapper {
6
2
  display: flex;
7
3
  justify-content: center;
@@ -1,13 +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
8
  // eslint-disable-next-line
9
- <ConfigurableLink to="${openmrsSpaBase}/forms">
10
- {t("formsAppMenuLink", "Forms")}
11
- </ConfigurableLink>
9
+ <ConfigurableLink to="${openmrsSpaBase}/forms">{t('formsAppMenuLink', 'Fast Data Entry')}</ConfigurableLink>
12
10
  );
13
11
  }
@@ -1,15 +1,13 @@
1
- import { useConfig } from "@openmrs/esm-framework";
2
- import { Tab, Tabs } from "carbon-components-react";
3
- import React from "react";
4
- import { Config } from "../config-schema";
5
- import { useGetAllForms } from "../hooks";
6
- import FormsTable from "../forms-table";
7
- import styles from "./styles.scss";
8
- import { useTranslation } from "react-i18next";
9
- import {
10
- fdeWorkflowStorageName,
11
- fdeWorkflowStorageVersion,
12
- } from "../context/FormWorkflowReducer";
1
+ import { useConfig, useSession } from '@openmrs/esm-framework';
2
+ import { Tab, Tabs, TabList, TabPanels, TabPanel } from '@carbon/react';
3
+ import React from 'react';
4
+ import { type Config } from '../config-schema';
5
+ import { useGetAllForms } from '../hooks';
6
+ import FormsTable from './forms-table';
7
+ import styles from './styles.scss';
8
+ import { useTranslation } from 'react-i18next';
9
+ import { fdeWorkflowStorageName, fdeWorkflowStorageVersion } from '../context/FormWorkflowReducer';
10
+ import { fdeGroupWorkflowStorageName, fdeGroupWorkflowStorageVersion } from '../context/GroupFormWorkflowReducer';
13
11
 
14
12
  // helper function useful for debugging
15
13
  // given a list of forms, it will organize into permissions
@@ -21,7 +19,7 @@ export const getFormPermissions = (forms) => {
21
19
  (output[form.encounterType.editPrivilege.display] = [
22
20
  ...(output[form.encounterType.editPrivilege.display] || []),
23
21
  form.display,
24
- ])
22
+ ]),
25
23
  );
26
24
  return output;
27
25
  };
@@ -40,21 +38,29 @@ const prepareRowsForTable = (rawFormData) => {
40
38
  };
41
39
 
42
40
  const FormsPage = () => {
43
- const config = useConfig() as Config;
41
+ const config = useConfig();
44
42
  const { t } = useTranslation();
45
43
  const { formCategories, formCategoriesToShow } = config;
46
44
  const { forms, isLoading, error } = useGetAllForms();
47
45
  const cleanRows = prepareRowsForTable(forms);
48
- const savedData = localStorage.getItem(fdeWorkflowStorageName);
46
+ const { user } = useSession();
47
+ const savedFormsData = localStorage.getItem(fdeWorkflowStorageName + ':' + user?.uuid);
48
+ const savedGroupFormsData = localStorage.getItem(fdeGroupWorkflowStorageName + ':' + user?.uuid);
49
49
  const activeForms = [];
50
- if (
51
- savedData &&
52
- JSON.parse(savedData)?.["_storageVersion"] === fdeWorkflowStorageVersion
53
- ) {
54
- Object.entries(JSON.parse(savedData).forms).forEach(
50
+ const activeGroupForms = [];
51
+
52
+ if (savedFormsData && JSON.parse(savedFormsData)?.['_storageVersion'] === fdeWorkflowStorageVersion) {
53
+ Object.entries(JSON.parse(savedFormsData).forms).forEach(
55
54
  ([formUuid, form]: [string, { [key: string]: unknown }]) => {
56
55
  if (form.workflowState) activeForms.push(formUuid);
57
- }
56
+ },
57
+ );
58
+ }
59
+ if (savedGroupFormsData && JSON.parse(savedGroupFormsData)?.['_storageVersion'] === fdeGroupWorkflowStorageVersion) {
60
+ Object.entries(JSON.parse(savedGroupFormsData).forms).forEach(
61
+ ([formUuid, form]: [string, { [key: string]: unknown }]) => {
62
+ if (form.workflowState) activeGroupForms.push(formUuid);
63
+ },
58
64
  );
59
65
  }
60
66
 
@@ -70,23 +76,28 @@ const FormsPage = () => {
70
76
 
71
77
  return (
72
78
  <div className={styles.mainContent}>
73
- <h3 className={styles.pageTitle}>{t("forms", "Forms")}</h3>
74
- <Tabs type="container">
75
- <Tab
76
- label={`${t("allForms", "All Forms")} (${
77
- cleanRows ? cleanRows?.length : "??"
78
- })`}
79
- >
80
- <FormsTable rows={cleanRows} {...{ error, isLoading, activeForms }} />
81
- </Tab>
82
- {categoryRows?.map((category, index) => (
83
- <Tab label={`${category.name} (${category.rows.length})`} key={index}>
84
- <FormsTable
85
- rows={category.rows}
86
- {...{ error, isLoading, activeForms }}
87
- />
79
+ <h3 className={styles.pageTitle}>{t('fastDataEntry', 'Fast Data Entry')}</h3>
80
+ <Tabs>
81
+ <TabList>
82
+ <Tab label={t('allForms', 'All Forms')}>
83
+ {`${t('allForms', 'All Forms')} (${cleanRows ? cleanRows?.length : '??'})`}
88
84
  </Tab>
89
- ))}
85
+ {categoryRows?.map((category, index) => (
86
+ <Tab label={category.name} key={index}>
87
+ {`${category.name} (${category.rows.length})`}
88
+ </Tab>
89
+ ))}
90
+ </TabList>
91
+ <TabPanels>
92
+ <TabPanel>
93
+ <FormsTable rows={cleanRows} {...{ error, isLoading, activeForms, activeGroupForms }} />
94
+ </TabPanel>
95
+ {categoryRows?.map((category, index) => (
96
+ <TabPanel key={index}>
97
+ <FormsTable rows={category.rows} {...{ error, isLoading, activeForms, activeGroupForms }} />
98
+ </TabPanel>
99
+ ))}
100
+ </TabPanels>
90
101
  </Tabs>
91
102
  </div>
92
103
  );
@@ -0,0 +1,117 @@
1
+ import { ErrorState } from '@openmrs/esm-framework';
2
+ import {
3
+ DataTable,
4
+ DataTableSkeleton,
5
+ Table,
6
+ TableBody,
7
+ TableCell,
8
+ TableContainer,
9
+ TableHead,
10
+ TableHeader,
11
+ TableRow,
12
+ TableToolbar,
13
+ TableToolbarContent,
14
+ TableToolbarSearch,
15
+ } from '@carbon/react';
16
+ import React from 'react';
17
+ import { useTranslation } from 'react-i18next';
18
+ import { Link } from 'react-router-dom';
19
+ import EmptyState from '../../empty-state/EmptyState';
20
+ import styles from './styles.scss';
21
+
22
+ const FormsTable = ({ rows, error, isLoading, activeForms, activeGroupForms }) => {
23
+ const { t } = useTranslation();
24
+
25
+ const tableHeaders = [
26
+ {
27
+ key: 'display',
28
+ header: t('formName', 'Form Name'),
29
+ isSortable: true,
30
+ },
31
+ {
32
+ key: 'actions',
33
+ header: t('actions', 'Actions'),
34
+ },
35
+ {
36
+ key: 'actions2',
37
+ header: '',
38
+ },
39
+ ];
40
+
41
+ const augmentedRows = rows?.map((row) => ({
42
+ ...row,
43
+ actions: (
44
+ <Link to={`form/${row.uuid}`}>
45
+ {activeForms.includes(row.uuid) ? t('resumeSession', 'Resume Session') : t('fillForm', 'Fill Form')}
46
+ </Link>
47
+ ),
48
+ actions2: (
49
+ <Link to={`groupform/${row.uuid}`}>
50
+ {activeGroupForms.includes(row.uuid)
51
+ ? t('resumeGroupSession', 'Resume Group Session')
52
+ : t('startGroupSession', 'Start Group Session')}
53
+ </Link>
54
+ ),
55
+ }));
56
+
57
+ if (isLoading) return <DataTableSkeleton />;
58
+ if (error) {
59
+ return <ErrorState headerTitle={t('errorLoadingData', 'Error Loading Data')} error={error} />;
60
+ }
61
+ if (augmentedRows.length === 0) {
62
+ return (
63
+ <EmptyState
64
+ headerTitle={t('noFormsFound', 'No Forms To Show')}
65
+ displayText={t(
66
+ 'noFormsFoundMessage',
67
+ 'No forms could be found for this category. Please double check the form concept uuids and access permissions.',
68
+ )}
69
+ />
70
+ );
71
+ }
72
+ return (
73
+ <DataTable rows={augmentedRows} headers={tableHeaders}>
74
+ {({ rows, headers, getTableProps, getHeaderProps, getRowProps, onInputChange }) => {
75
+ return (
76
+ <TableContainer>
77
+ <div className={styles.toolbarWrapper}>
78
+ <TableToolbar className={styles.tableToolbar}>
79
+ <TableToolbarContent>
80
+ <TableToolbarSearch onChange={onInputChange} />
81
+ </TableToolbarContent>
82
+ </TableToolbar>
83
+ </div>
84
+ <Table {...getTableProps()}>
85
+ <TableHead>
86
+ <TableRow>
87
+ {headers.map((header) => (
88
+ <TableHeader
89
+ {...getHeaderProps({
90
+ header,
91
+ isSortable: header.isSortable,
92
+ })}
93
+ >
94
+ {header.header}
95
+ </TableHeader>
96
+ ))}
97
+ </TableRow>
98
+ </TableHead>
99
+ <TableBody>
100
+ {rows?.map((row) => (
101
+ <TableRow {...getRowProps({ row })}>
102
+ {row.cells.map((cell) => (
103
+ <TableCell key={cell.id}>{cell.value}</TableCell>
104
+ ))}
105
+ </TableRow>
106
+ ))}
107
+ </TableBody>
108
+ </Table>
109
+ </TableContainer>
110
+ );
111
+ }}
112
+ </DataTable>
113
+ );
114
+ };
115
+
116
+ export default FormsTable;
117
+ export { FormsTable };
@@ -0,0 +1,3 @@
1
+ import FormsTable from './FormsTable';
2
+
3
+ export default FormsTable;
@@ -0,0 +1,19 @@
1
+ @use '@carbon/colors';
2
+ @use '@carbon/layout';
3
+
4
+ .toolbarWrapper {
5
+ position: relative;
6
+ display: flex;
7
+ height: layout.$spacing-09;
8
+ justify-content: flex-end;
9
+ }
10
+
11
+ .tableToolbar {
12
+ width: 20%;
13
+ min-width: 12.5rem;
14
+ }
15
+
16
+ .inactiveLink {
17
+ color: colors.$gray-40;
18
+ cursor: not-allowed;
19
+ }
@@ -1,3 +1,3 @@
1
- import FormsPage from "./FormsPage";
1
+ import FormsPage from './FormsPage';
2
2
 
3
3
  export default FormsPage;
@@ -1,11 +1,9 @@
1
- @import "~@openmrs/esm-styleguide/src/vars";
2
- @import "~carbon-components/src/globals/scss/vars";
3
- @import "~carbon-components/src/globals/scss/mixins";
1
+ @use '@carbon/layout';
4
2
 
5
3
  .mainContent {
6
- padding: $spacing-07;
4
+ padding: layout.$spacing-07;
7
5
  }
8
6
 
9
7
  .pageTitle {
10
- margin-bottom: $spacing-06;
8
+ margin-bottom: layout.$spacing-06;
11
9
  }
@@ -0,0 +1,26 @@
1
+ import { ExtensionSlot } from '@openmrs/esm-framework';
2
+ import React from 'react';
3
+ import GroupDisplayHeader from './group-display-header';
4
+ import styles from './styles.scss';
5
+ import { GroupFormWorkflowProvider } from '../context/GroupFormWorkflowContext';
6
+ import GroupSearchHeader from './group-search-header';
7
+ import SessionMetaWorkspace from './SessionMetaWorkspace';
8
+ import GroupSessionWorkspace from './GroupSessionWorkspace';
9
+
10
+ const GroupFormEntryWorkflow = () => {
11
+ return (
12
+ <GroupFormWorkflowProvider>
13
+ <div className={styles.breadcrumbsContainer}>
14
+ <ExtensionSlot name="breadcrumbs-slot" />
15
+ </div>
16
+ <GroupSearchHeader />
17
+ <GroupDisplayHeader />
18
+ <div className={styles.workspaceWrapper}>
19
+ <SessionMetaWorkspace />
20
+ <GroupSessionWorkspace />
21
+ </div>
22
+ </GroupFormWorkflowProvider>
23
+ );
24
+ };
25
+
26
+ export default GroupFormEntryWorkflow;
@@ -0,0 +1,207 @@
1
+ import { getGlobalStore, useConfig, useSession, useStore } from '@openmrs/esm-framework';
2
+ import { Button } from '@carbon/react';
3
+ import React, { useCallback, useContext, useEffect, useState } from 'react';
4
+ import PatientCard from '../patient-card/PatientCard';
5
+ import styles from './styles.scss';
6
+ import { useTranslation } from 'react-i18next';
7
+ import { v4 as uuid } from 'uuid';
8
+ import GroupFormWorkflowContext from '../context/GroupFormWorkflowContext';
9
+ import FormBootstrap from '../FormBootstrap';
10
+ import CompleteModal from '../CompleteModal';
11
+ import CancelModal from '../CancelModal';
12
+
13
+ const formStore = getGlobalStore('ampath-form-state');
14
+
15
+ const WorkflowNavigationButtons = () => {
16
+ const context = useContext(GroupFormWorkflowContext);
17
+ const { activeFormUuid, submitForNext, patientUuids, activePatientUuid, workflowState } = context;
18
+ const store = useStore(formStore);
19
+ const formState = store[activeFormUuid];
20
+ const navigationDisabled =
21
+ (formState !== 'ready' || workflowState !== 'EDIT_FORM') && formState !== 'readyWithValidationErrors';
22
+ const [cancelModalOpen, setCancelModalOpen] = useState(false);
23
+ const [completeModalOpen, setCompleteModalOpen] = useState(false);
24
+ const { t } = useTranslation();
25
+
26
+ const isLastPatient = activePatientUuid === patientUuids[patientUuids.length - 1];
27
+
28
+ const handleClickNext = () => {
29
+ if (workflowState === 'EDIT_FORM' || formState === 'readyWithValidationErrors') {
30
+ submitForNext();
31
+ }
32
+ };
33
+
34
+ return (
35
+ <>
36
+ <div className={styles.rightPanelActionButtons}>
37
+ <Button kind="primary" onClick={handleClickNext} disabled={navigationDisabled}>
38
+ {isLastPatient ? t('saveForm', 'Save Form') : t('nextPatient', 'Next patient')}
39
+ </Button>
40
+ <Button kind="secondary" onClick={() => setCompleteModalOpen(true)}>
41
+ {t('saveAndComplete', 'Save & Complete')}
42
+ </Button>
43
+ <Button kind="tertiary" onClick={() => setCancelModalOpen(true)}>
44
+ {t('cancel', 'Cancel')}
45
+ </Button>
46
+ </div>
47
+ <CancelModal open={cancelModalOpen} setOpen={setCancelModalOpen} context={context} />
48
+ <CompleteModal open={completeModalOpen} setOpen={setCompleteModalOpen} context={context} validateFirst={false} />
49
+ </>
50
+ );
51
+ };
52
+
53
+ const GroupSessionWorkspace = () => {
54
+ const { groupSessionConcepts } = useConfig();
55
+ const { t } = useTranslation();
56
+ const {
57
+ patientUuids,
58
+ activePatientUuid,
59
+ encounters,
60
+ activeEncounterUuid,
61
+ activeVisitUuid,
62
+ activeFormUuid,
63
+ activeGroupUuid,
64
+ activeGroupName,
65
+ activeSessionUuid,
66
+ saveEncounter,
67
+ activeSessionMeta,
68
+ groupVisitTypeUuid,
69
+ updateVisitUuid,
70
+ submitForNext,
71
+ workflowState,
72
+ } = useContext(GroupFormWorkflowContext);
73
+
74
+ const { sessionLocation } = useSession();
75
+
76
+ useEffect(() => {
77
+ if (activeVisitUuid) {
78
+ updateVisitUuid(activeVisitUuid);
79
+ }
80
+ }, [updateVisitUuid, activeVisitUuid, activePatientUuid]);
81
+
82
+ // If there's no active visit, trigger the creation of a new one
83
+ const handleEncounterCreate = useCallback(
84
+ (payload) => {
85
+ // Create a visit with the same date as the encounter being saved
86
+ const obsTime = new Date(activeSessionMeta.sessionDate);
87
+ payload.obs.forEach((item, index) => {
88
+ payload.obs[index] = {
89
+ ...item,
90
+ groupMembers: item.groupMembers?.map((mem) => ({
91
+ ...mem,
92
+ obsDatetime: obsTime.toISOString(),
93
+ })),
94
+ obsDatetime: obsTime.toISOString(),
95
+ };
96
+ });
97
+ const visitUuid = activeVisitUuid ? activeVisitUuid : uuid();
98
+ if (!activeVisitUuid) {
99
+ Object.entries(groupSessionConcepts).forEach(([field, uuid]) => {
100
+ if (activeSessionMeta?.[field] != null) {
101
+ payload.obs.push({
102
+ concept: uuid,
103
+ value: activeSessionMeta[field],
104
+ });
105
+ }
106
+ });
107
+
108
+ const otherIdentifiers = [
109
+ { concept: groupSessionConcepts.cohortId, value: activeGroupUuid },
110
+ { concept: groupSessionConcepts.cohortName, value: activeGroupName },
111
+ {
112
+ concept: groupSessionConcepts.sessionUuid,
113
+ value: activeSessionUuid,
114
+ },
115
+ ];
116
+ payload.obs.push(...otherIdentifiers);
117
+ // If this is a newly created encounter and visit, add session concepts to encounter payload.
118
+ const visitInfo = {
119
+ startDatetime: activeSessionMeta.sessionDate,
120
+ stopDatetime: activeSessionMeta.sessionDate,
121
+ uuid: visitUuid,
122
+ patient: {
123
+ uuid: activePatientUuid,
124
+ },
125
+ location: {
126
+ uuid: sessionLocation?.uuid,
127
+ },
128
+ visitType: {
129
+ uuid: groupVisitTypeUuid,
130
+ },
131
+ };
132
+ payload.visit = visitInfo;
133
+ updateVisitUuid(visitUuid);
134
+ }
135
+ payload.location = sessionLocation?.uuid;
136
+ payload.encounterDatetime = obsTime.toISOString();
137
+ },
138
+ [
139
+ activeSessionMeta,
140
+ activeVisitUuid,
141
+ sessionLocation?.uuid,
142
+ groupSessionConcepts,
143
+ activeGroupUuid,
144
+ activeGroupName,
145
+ activeSessionUuid,
146
+ activePatientUuid,
147
+ groupVisitTypeUuid,
148
+ updateVisitUuid,
149
+ ],
150
+ );
151
+
152
+ // Once form has been posted, save the new encounter uuid so we can edit it later
153
+ const handlePostResponse = useCallback(
154
+ (encounter) => {
155
+ if (encounter && encounter.uuid) {
156
+ saveEncounter(encounter.uuid);
157
+ }
158
+ },
159
+ [saveEncounter],
160
+ );
161
+
162
+ const switchPatient = useCallback(
163
+ (patientUuid) => {
164
+ submitForNext(patientUuid);
165
+ },
166
+ [submitForNext],
167
+ );
168
+
169
+ if (workflowState === 'NEW_GROUP_SESSION') return null;
170
+
171
+ return (
172
+ <div className={styles.workspace}>
173
+ <div className={styles.formMainContent}>
174
+ <div className={styles.formContainer}>
175
+ <FormBootstrap
176
+ patientUuid={activePatientUuid}
177
+ encounterUuid={activeEncounterUuid}
178
+ {...{
179
+ formUuid: activeFormUuid,
180
+ handlePostResponse,
181
+ handleEncounterCreate,
182
+ }}
183
+ />
184
+ </div>
185
+ <div className={styles.rightPanel}>
186
+ <h4>{t('formsFilled', 'Forms filled')}</h4>
187
+ <div className={styles.patientCardsSection}>
188
+ {patientUuids?.map((patientUuid) => (
189
+ <PatientCard
190
+ key={patientUuid}
191
+ {...{
192
+ patientUuid,
193
+ activePatientUuid,
194
+ editEncounter: switchPatient,
195
+ encounters,
196
+ }}
197
+ />
198
+ ))}
199
+ </div>
200
+ <WorkflowNavigationButtons />
201
+ </div>
202
+ </div>
203
+ </div>
204
+ );
205
+ };
206
+
207
+ export default GroupSessionWorkspace;