@digihmis/esm-triage-app 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 (148) hide show
  1. package/README.md +4 -0
  2. package/dist/117.js +1 -0
  3. package/dist/117.js.map +1 -0
  4. package/dist/132.js +1 -0
  5. package/dist/132.js.map +1 -0
  6. package/dist/152.js +1 -0
  7. package/dist/152.js.map +1 -0
  8. package/dist/183.js +66 -0
  9. package/dist/183.js.map +1 -0
  10. package/dist/209.js +1 -0
  11. package/dist/209.js.map +1 -0
  12. package/dist/22.js +1 -0
  13. package/dist/22.js.map +1 -0
  14. package/dist/222.js +1 -0
  15. package/dist/222.js.map +1 -0
  16. package/dist/317.js +1 -0
  17. package/dist/317.js.map +1 -0
  18. package/dist/349.js +1 -0
  19. package/dist/349.js.map +1 -0
  20. package/dist/371.js +1 -0
  21. package/dist/371.js.map +1 -0
  22. package/dist/378.js +1 -0
  23. package/dist/378.js.map +1 -0
  24. package/dist/442.js +1 -0
  25. package/dist/442.js.map +1 -0
  26. package/dist/45.js +1 -0
  27. package/dist/45.js.map +1 -0
  28. package/dist/466.js +1 -0
  29. package/dist/466.js.map +1 -0
  30. package/dist/508.js +1 -0
  31. package/dist/508.js.map +1 -0
  32. package/dist/61.js +1 -0
  33. package/dist/61.js.map +1 -0
  34. package/dist/640.js +1 -0
  35. package/dist/640.js.map +1 -0
  36. package/dist/689.js +1 -0
  37. package/dist/689.js.map +1 -0
  38. package/dist/709.js +1 -0
  39. package/dist/709.js.map +1 -0
  40. package/dist/712.js +1 -0
  41. package/dist/712.js.map +1 -0
  42. package/dist/720.js +1 -0
  43. package/dist/720.js.map +1 -0
  44. package/dist/737.js +12 -0
  45. package/dist/737.js.map +1 -0
  46. package/dist/742.js +1 -0
  47. package/dist/742.js.map +1 -0
  48. package/dist/771.js +1 -0
  49. package/dist/771.js.map +1 -0
  50. package/dist/806.js +1 -0
  51. package/dist/806.js.map +1 -0
  52. package/dist/809.js +1 -0
  53. package/dist/809.js.map +1 -0
  54. package/dist/9.js +15 -0
  55. package/dist/9.js.map +1 -0
  56. package/dist/912.js +1 -0
  57. package/dist/912.js.map +1 -0
  58. package/dist/913.js +1 -0
  59. package/dist/913.js.map +1 -0
  60. package/dist/940.js +1 -0
  61. package/dist/940.js.map +1 -0
  62. package/dist/974.js +1 -0
  63. package/dist/974.js.map +1 -0
  64. package/dist/976.js +1 -0
  65. package/dist/976.js.map +1 -0
  66. package/dist/digihmis-esm-triage-app.js +6 -0
  67. package/dist/digihmis-esm-triage-app.js.buildmanifest.json +905 -0
  68. package/dist/digihmis-esm-triage-app.js.map +1 -0
  69. package/dist/main.js +90 -0
  70. package/dist/main.js.map +1 -0
  71. package/dist/routes.json +1 -0
  72. package/jest.config.js +26 -0
  73. package/package.json +51 -0
  74. package/rspack.config.js +1 -0
  75. package/src/config-schema.ts +142 -0
  76. package/src/dashboard.meta.ts +16 -0
  77. package/src/declarations.d.ts +6 -0
  78. package/src/icon-wrapper/icon-wrapper.extension.tsx +17 -0
  79. package/src/index.ts +41 -0
  80. package/src/left-panel/left-panel.component.tsx +100 -0
  81. package/src/left-panel/left-panel.scss +13 -0
  82. package/src/page-header/page-header.component.tsx +35 -0
  83. package/src/page-header/page-header.scss +76 -0
  84. package/src/past-vitals/action-menu/vitals-anthropometrics.component.tsx +44 -0
  85. package/src/past-vitals/action-menu/vitals-anthropometrics.scss +0 -0
  86. package/src/past-vitals/paginated-antropometrics/paginated-antropometrics.component.tsx +132 -0
  87. package/src/past-vitals/paginated-antropometrics/paginated-antropometrics.scss +9 -0
  88. package/src/past-vitals/paginated-vitals/paginated-vitals.component.tsx +157 -0
  89. package/src/past-vitals/paginated-vitals/paginated-vitals.scss +94 -0
  90. package/src/past-vitals/paginated-vitals/type/index.ts +1 -0
  91. package/src/past-vitals/patient-banner/patient-banner.component.tsx +60 -0
  92. package/src/past-vitals/patient-banner/patient-banner.scss +72 -0
  93. package/src/past-vitals/patient-vitals-history.component.tsx +85 -0
  94. package/src/past-vitals/patient-vitals-history.resource.ts +30 -0
  95. package/src/past-vitals/patient-vitals-history.scss +75 -0
  96. package/src/past-vitals/triage-anthropometrics/antropometrics-chart.component.tsx +170 -0
  97. package/src/past-vitals/triage-anthropometrics/antropometrics-chart.scss +70 -0
  98. package/src/past-vitals/triage-anthropometrics/triage-antropometrics.component.tsx +124 -0
  99. package/src/past-vitals/triage-anthropometrics/triage-antropometrics.scss +37 -0
  100. package/src/past-vitals/triage-anthropometrics/type/index.ts +17 -0
  101. package/src/past-vitals/triage-notes/triage-note.scss +94 -0
  102. package/src/past-vitals/triage-notes/triage-notes.component.tsx +75 -0
  103. package/src/past-vitals/triage-notes/triage-notes.resource.ts +72 -0
  104. package/src/past-vitals/triage-notes/type/index.ts +50 -0
  105. package/src/past-vitals/triage-vitals/triage-vitals-history.component.tsx +229 -0
  106. package/src/past-vitals/triage-vitals/triage-vitals-history.scss +48 -0
  107. package/src/past-vitals/triage-vitals/type/index.ts +32 -0
  108. package/src/past-vitals/triage-vitals/vital-chart.component.tsx +198 -0
  109. package/src/past-vitals/triage-vitals/vitals-chart.scss +70 -0
  110. package/src/past-vitals/utils/index.ts +5 -0
  111. package/src/past-vitals/vitals-and-biometrics-header/vitals-header-item.component.tsx +61 -0
  112. package/src/past-vitals/vitals-and-biometrics-header/vitals-header-item.scss +171 -0
  113. package/src/past-vitals/vitals-and-biometrics-header/vitals-header-item.test.tsx +31 -0
  114. package/src/past-vitals/vitals-and-biometrics-header/vitals-header.extension.tsx +225 -0
  115. package/src/past-vitals/vitals-and-biometrics-header/vitals-header.scss +233 -0
  116. package/src/queue-room-table/queue-room-table.component.tsx +380 -0
  117. package/src/queue-room-table/queue-room-table.resource.ts +73 -0
  118. package/src/queue-room-table/queue-room-table.scss +193 -0
  119. package/src/queue-room-table/type/index.ts +30 -0
  120. package/src/queue-room-table/useQueueActions.ts +24 -0
  121. package/src/record-vitals-workspace/common/index.ts +19 -0
  122. package/src/record-vitals-workspace/component/vitals-biometrics-input.component.tsx +433 -0
  123. package/src/record-vitals-workspace/component/vitals-biometrics-input.scss +358 -0
  124. package/src/record-vitals-workspace/helper/index.ts +200 -0
  125. package/src/record-vitals-workspace/record-vitals.resource.ts +570 -0
  126. package/src/record-vitals-workspace/record-vitals.scss +140 -0
  127. package/src/record-vitals-workspace/record-vitals.workspace.tsx +612 -0
  128. package/src/record-vitals-workspace/schema/index.ts +28 -0
  129. package/src/record-vitals-workspace/type/index.ts +170 -0
  130. package/src/record-vitals-workspace/utils/index.ts +100 -0
  131. package/src/routes.json +70 -0
  132. package/src/setup-tests.ts +1 -0
  133. package/src/triage-queue-dashboard/triage-dashboard.resource.ts +75 -0
  134. package/src/triage-queue-dashboard/triage-queue-dashboard.component.tsx +261 -0
  135. package/src/triage-queue-dashboard/triage-queue-dashboard.scss +166 -0
  136. package/src/triage-queue-dashboard/type/index.ts +31 -0
  137. package/translations/am.json +92 -0
  138. package/translations/ar.json +92 -0
  139. package/translations/en.json +92 -0
  140. package/translations/es.json +92 -0
  141. package/translations/fr.json +92 -0
  142. package/translations/he.json +92 -0
  143. package/translations/km.json +92 -0
  144. package/translations/pt.json +92 -0
  145. package/translations/sw.json +92 -0
  146. package/translations/vi.json +92 -0
  147. package/translations/zh.json +92 -0
  148. package/tsconfig.json +5 -0
@@ -0,0 +1,132 @@
1
+ import React, { useMemo, useState } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import {
4
+ DataTable,
5
+ Table,
6
+ TableCell,
7
+ TableContainer,
8
+ TableBody,
9
+ TableHead,
10
+ TableHeader,
11
+ TableRow,
12
+ } from '@carbon/react';
13
+ import { useLayoutType, usePagination } from '@openmrs/esm-framework';
14
+ import { PatientChartPagination } from '@openmrs/esm-patient-common-lib';
15
+ import type { BiometricsTableHeader, BiometricsTableRow } from '../triage-anthropometrics/type';
16
+ import styles from './paginated-antropometrics.scss';
17
+ import { VitalsAndBiometricsActionMenu } from '../action-menu/vitals-anthropometrics.component';
18
+ import { DataTableSortState } from '../paginated-vitals/type';
19
+
20
+ interface PaginatedBiometricsProps {
21
+ tableRows: Array<BiometricsTableRow>;
22
+ pageSize: number;
23
+ urlLabel: string;
24
+ tableHeaders: Array<BiometricsTableHeader>;
25
+ patient: fhir.Patient;
26
+ }
27
+
28
+ const PaginatedBiometrics: React.FC<PaginatedBiometricsProps> = ({
29
+ tableRows,
30
+ pageSize,
31
+ urlLabel,
32
+ tableHeaders,
33
+ patient,
34
+ }) => {
35
+ const isTablet = useLayoutType() === 'tablet';
36
+
37
+ const { t } = useTranslation();
38
+
39
+ const [sortParams, setSortParams] = useState<{ key: string; sortDirection: 'ASC' | 'DESC' | 'NONE' }>({
40
+ key: '',
41
+ sortDirection: 'NONE',
42
+ });
43
+
44
+ const handleSorting = (
45
+ cellA: any,
46
+ cellB: any,
47
+ { key, sortDirection }: { key: string; sortDirection: DataTableSortState },
48
+ ) => {
49
+ if (sortDirection === 'NONE') {
50
+ setSortParams({ key: '', sortDirection });
51
+ } else {
52
+ setSortParams({ key, sortDirection });
53
+ }
54
+ return 0;
55
+ };
56
+
57
+ const sortedData: Array<BiometricsTableRow> = useMemo(() => {
58
+ if (sortParams.sortDirection === 'NONE') {
59
+ return tableRows;
60
+ }
61
+
62
+ const header = tableHeaders.find((header) => header.key === sortParams.key);
63
+
64
+ if (!header) {
65
+ return tableRows;
66
+ }
67
+
68
+ const sortedRows = tableRows.slice().sort((rowA, rowB) => {
69
+ const sortingNum = header.sortFunc(rowA, rowB);
70
+ return sortParams.sortDirection === 'DESC' ? sortingNum : -sortingNum;
71
+ });
72
+
73
+ return sortedRows;
74
+ }, [tableRows, tableHeaders, sortParams]);
75
+
76
+ const { results: paginatedBiometrics, goTo, currentPage } = usePagination(sortedData, pageSize);
77
+
78
+ return (
79
+ <>
80
+ <DataTable
81
+ headers={tableHeaders}
82
+ isSortable
83
+ overflowMenuOnHover={!isTablet}
84
+ rows={paginatedBiometrics}
85
+ size={isTablet ? 'lg' : 'sm'}
86
+ sortRow={handleSorting}
87
+ useZebraStyles>
88
+ {({ getHeaderProps, getTableProps, headers, rows }) => (
89
+ <TableContainer className={styles.tableContainer}>
90
+ <Table aria-label="biometrics" className={styles.table} {...getTableProps()}>
91
+ <TableHead>
92
+ <TableRow>
93
+ {headers.map((header) => (
94
+ <TableHeader
95
+ {...getHeaderProps({
96
+ header,
97
+ })}>
98
+ {header.header}
99
+ </TableHeader>
100
+ ))}
101
+ <TableHeader aria-label={t('actions', 'Actions')} />
102
+ </TableRow>
103
+ </TableHead>
104
+ <TableBody>
105
+ {rows.map((row) => (
106
+ <TableRow key={row.id}>
107
+ {row.cells.map((cell) => (
108
+ <TableCell key={cell.id}>{cell.value?.content ?? cell.value}</TableCell>
109
+ ))}
110
+ <TableCell className="cds--table-column-menu" id="actions">
111
+ <VitalsAndBiometricsActionMenu patient={patient} encounterUuid={row.id} />
112
+ </TableCell>
113
+ </TableRow>
114
+ ))}
115
+ </TableBody>
116
+ </Table>
117
+ </TableContainer>
118
+ )}
119
+ </DataTable>
120
+ <PatientChartPagination
121
+ currentItems={paginatedBiometrics.length}
122
+ dashboardLinkLabel={urlLabel}
123
+ onPageNumberChange={({ page }) => goTo(page)}
124
+ pageNumber={currentPage}
125
+ pageSize={pageSize}
126
+ totalItems={tableRows.length}
127
+ />
128
+ </>
129
+ );
130
+ };
131
+
132
+ export default PaginatedBiometrics;
@@ -0,0 +1,9 @@
1
+ .tableContainer {
2
+ flex: 1;
3
+ }
4
+
5
+ .table {
6
+ td {
7
+ border-bottom: none !important;
8
+ }
9
+ }
@@ -0,0 +1,157 @@
1
+ import React, { useMemo, useState } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import {
4
+ DataTable,
5
+ Table,
6
+ TableBody,
7
+ TableCell,
8
+ TableContainer,
9
+ TableHead,
10
+ TableHeader,
11
+ TableRow,
12
+ } from '@carbon/react';
13
+ import { useLayoutType, usePagination } from '@openmrs/esm-framework';
14
+ import { PatientChartPagination } from '@openmrs/esm-patient-common-lib';
15
+ import styles from './paginated-vitals.scss';
16
+ import { VitalsTableHeader, VitalsTableRow } from '../triage-vitals/type';
17
+ import { VitalsAndBiometricsActionMenu } from '../action-menu/vitals-anthropometrics.component';
18
+ import { DataTableSortState } from './type';
19
+
20
+ interface PaginatedVitalsProps {
21
+ isPrinting?: boolean;
22
+ pageSize: number;
23
+ tableHeaders: Array<VitalsTableHeader>;
24
+ tableRows: Array<VitalsTableRow>;
25
+ urlLabel: string;
26
+ patient: fhir.Patient;
27
+ }
28
+
29
+ const PaginatedVitals: React.FC<PaginatedVitalsProps> = ({
30
+ isPrinting,
31
+ pageSize,
32
+ tableHeaders,
33
+ tableRows,
34
+ urlLabel,
35
+ patient,
36
+ }) => {
37
+ const { t } = useTranslation();
38
+ const isTablet = useLayoutType() === 'tablet';
39
+
40
+ const StyledTableCell = ({ children, interpretation }: { children: React.ReactNode; interpretation: string }) => {
41
+ switch (interpretation) {
42
+ case 'critically_high':
43
+ return <TableCell className={styles.criticallyHigh}>{children}</TableCell>;
44
+ case 'critically_low':
45
+ return <TableCell className={styles.criticallyLow}>{children}</TableCell>;
46
+ case 'high':
47
+ return <TableCell className={styles.high}>{children}</TableCell>;
48
+ case 'low':
49
+ return <TableCell className={styles.low}>{children}</TableCell>;
50
+ default:
51
+ return <TableCell>{children}</TableCell>;
52
+ }
53
+ };
54
+
55
+ const [sortParams, setSortParams] = useState<{ key: string; sortDirection: 'ASC' | 'DESC' | 'NONE' }>({
56
+ key: '',
57
+ sortDirection: 'NONE',
58
+ });
59
+
60
+ const handleSorting = (
61
+ cellA: any,
62
+ cellB: any,
63
+ { key, sortDirection }: { key: string; sortDirection: DataTableSortState },
64
+ ) => {
65
+ if (sortDirection === 'NONE') {
66
+ setSortParams({ key: '', sortDirection });
67
+ } else {
68
+ setSortParams({ key, sortDirection });
69
+ }
70
+ return 0;
71
+ };
72
+
73
+ const sortedData: Array<VitalsTableRow> = useMemo(() => {
74
+ if (sortParams.sortDirection === 'NONE') {
75
+ return tableRows;
76
+ }
77
+
78
+ const header = tableHeaders.find((header) => header.key === sortParams.key);
79
+
80
+ if (!header) {
81
+ return tableRows;
82
+ }
83
+
84
+ const sortedRows = tableRows.slice().sort((rowA, rowB) => {
85
+ const sortingNum = header.sortFunc(rowA, rowB);
86
+ return sortParams.sortDirection === 'DESC' ? sortingNum : -sortingNum;
87
+ });
88
+
89
+ return sortedRows;
90
+ }, [tableHeaders, tableRows, sortParams]);
91
+
92
+ const { results: paginatedVitals, goTo, currentPage } = usePagination(sortedData, pageSize);
93
+
94
+ const rows = isPrinting ? sortedData : paginatedVitals;
95
+
96
+ return (
97
+ <>
98
+ <DataTable
99
+ headers={tableHeaders}
100
+ isSortable
101
+ overflowMenuOnHover={!isTablet}
102
+ rows={rows}
103
+ size={isTablet ? 'lg' : 'sm'}
104
+ sortRow={handleSorting}
105
+ useZebraStyles>
106
+ {({ rows, headers, getTableProps, getHeaderProps }) => (
107
+ <TableContainer className={styles.tableContainer}>
108
+ <Table aria-label="vitals" className={styles.table} {...getTableProps()}>
109
+ <TableHead>
110
+ <TableRow>
111
+ {headers.map((header) => (
112
+ <TableHeader {...getHeaderProps({ header })} key={header.key}>
113
+ {header.header}
114
+ </TableHeader>
115
+ ))}
116
+ <TableHeader aria-label={t('actions', 'Actions')} />
117
+ </TableRow>
118
+ </TableHead>
119
+ <TableBody>
120
+ {rows.map((row) => (
121
+ <TableRow key={row.id}>
122
+ {row.cells.map((cell) => {
123
+ const vitalsObj = paginatedVitals.find((obj) => obj.id === row.id);
124
+ const interpretationKey = cell.info.header + 'Interpretation';
125
+ const interpretation = vitalsObj?.[interpretationKey];
126
+
127
+ return (
128
+ <StyledTableCell key={`styled-cell-${cell.id}`} interpretation={interpretation}>
129
+ {cell.value?.content ?? cell.value}
130
+ </StyledTableCell>
131
+ );
132
+ })}
133
+ <TableCell className="cds--table-column-menu" id="actions">
134
+ <VitalsAndBiometricsActionMenu patient={patient} encounterUuid={row.id} />
135
+ </TableCell>
136
+ </TableRow>
137
+ ))}
138
+ </TableBody>
139
+ </Table>
140
+ </TableContainer>
141
+ )}
142
+ </DataTable>
143
+ {!isPrinting ? (
144
+ <PatientChartPagination
145
+ pageNumber={currentPage}
146
+ totalItems={tableRows.length}
147
+ currentItems={paginatedVitals.length}
148
+ pageSize={pageSize}
149
+ onPageNumberChange={({ page }) => goTo(page)}
150
+ dashboardLinkLabel={urlLabel}
151
+ />
152
+ ) : null}
153
+ </>
154
+ );
155
+ };
156
+
157
+ export default PaginatedVitals;
@@ -0,0 +1,94 @@
1
+ @use '@carbon/colors';
2
+ @use '@carbon/layout';
3
+ @use '@carbon/type';
4
+
5
+ .table {
6
+ td {
7
+ white-space: nowrap;
8
+ border-top: 1px solid colors.$gray-20;
9
+ border-bottom: none !important;
10
+ }
11
+ }
12
+
13
+ .criticallyLow,
14
+ .criticallyHigh,
15
+ .low,
16
+ .high {
17
+ td:nth-child(2) {
18
+ @include type.type-style('heading-compact-01');
19
+ }
20
+ }
21
+
22
+ .criticallyHigh,
23
+ .criticallyLow {
24
+ border-top: 1px solid colors.$red-20-hover !important;
25
+ background-color: colors.$red-20 !important;
26
+ }
27
+
28
+ .high,
29
+ .low {
30
+ border-top: 1px solid colors.$orange-20 !important;
31
+ background-color: colors.$orange-10 !important;
32
+ }
33
+
34
+ .criticallyLow {
35
+ td:nth-child(2)::after {
36
+ content: ' ↓↓';
37
+ }
38
+ }
39
+
40
+ .criticallyHigh {
41
+ td:nth-child(2)::after {
42
+ content: ' ↑↑';
43
+ }
44
+ }
45
+
46
+ .low {
47
+ td:nth-child(2)::after {
48
+ content: ' ↓';
49
+ }
50
+ }
51
+
52
+ .high {
53
+ td:nth-child(2)::after {
54
+ content: ' ↑';
55
+ }
56
+ }
57
+
58
+ .off-scale-low {
59
+ td:nth-child(2)::after {
60
+ content: ' ↓↓↓';
61
+ }
62
+ }
63
+
64
+ .off-scale-high {
65
+ td:nth-child(2)::after {
66
+ content: ' ↑↑↑';
67
+ }
68
+ }
69
+
70
+ .offScaleHigh,
71
+ .offScaleLow,
72
+ .criticallyHigh,
73
+ .criticallyLow,
74
+ .high,
75
+ .low {
76
+ @include type.type-style('heading-compact-01');
77
+ color: colors.$gray-100 !important;
78
+ }
79
+
80
+ .criticallyLow::after {
81
+ content: ' ↓↓';
82
+ }
83
+
84
+ .criticallyHigh::after {
85
+ content: ' ↑↑';
86
+ }
87
+
88
+ .low::after {
89
+ content: ' ↓';
90
+ }
91
+
92
+ .high::after {
93
+ content: ' ↑';
94
+ }
@@ -0,0 +1 @@
1
+ export type DataTableSortState = 'NONE' | 'DESC' | 'ASC';
@@ -0,0 +1,60 @@
1
+ import React, { useEffect, useRef, useState } from 'react';
2
+ import classNames from 'classnames';
3
+ import {
4
+ getPatientName,
5
+ PatientBannerPatientInfo,
6
+ PatientBannerToggleContactDetailsButton,
7
+ PatientPhoto,
8
+ } from '@openmrs/esm-framework';
9
+ import styles from './patient-banner.scss';
10
+
11
+ interface PatientBannerProps {
12
+ patient: fhir.Patient;
13
+ patientUuid: string;
14
+ hideActionsOverflow?: boolean;
15
+ }
16
+
17
+ const PatientBanner: React.FC<PatientBannerProps> = ({ patient, patientUuid }) => {
18
+ const patientBannerRef = useRef(null);
19
+ const [isTabletViewport, setIsTabletViewport] = useState(false);
20
+ const [showContactDetails, setShowContactDetails] = useState(false);
21
+
22
+ useEffect(() => {
23
+ const currentRef = patientBannerRef.current;
24
+ const resizeObserver = new ResizeObserver((entries) => {
25
+ for (const entry of entries) {
26
+ setIsTabletViewport(entry.contentRect.width < 1023);
27
+ }
28
+ });
29
+ resizeObserver.observe(patientBannerRef.current);
30
+ return () => {
31
+ if (currentRef) {
32
+ resizeObserver.unobserve(currentRef);
33
+ }
34
+ };
35
+ }, [patientBannerRef, setIsTabletViewport]);
36
+
37
+ const patientName = patient ? getPatientName(patient) : '';
38
+
39
+ const isDeceased = Boolean(patient?.deceasedDateTime);
40
+
41
+ return (
42
+ <header
43
+ aria-label="patient banner"
44
+ className={classNames(
45
+ styles.container,
46
+ isDeceased ? styles.deceasedPatientContainer : styles.activePatientContainer,
47
+ )}
48
+ role="banner"
49
+ ref={patientBannerRef}>
50
+ <div className={styles.patientBanner}>
51
+ <div className={styles.patientAvatar}>
52
+ <PatientPhoto patientUuid={patientUuid} patientName={patientName} />
53
+ </div>
54
+ <PatientBannerPatientInfo patient={patient} renderedFrom="patient-chart" />
55
+ </div>
56
+ </header>
57
+ );
58
+ };
59
+
60
+ export default PatientBanner;
@@ -0,0 +1,72 @@
1
+ @use '@carbon/layout';
2
+ @use '@carbon/type';
3
+ @use '@openmrs/esm-styleguide/src/vars' as *;
4
+
5
+ .container {
6
+ border-top: 0.063rem solid $ui-03;
7
+ border-bottom: 0.063rem solid $ui-03;
8
+ background-color: $ui-01;
9
+ }
10
+
11
+ .activePatientContainer,
12
+ .deceasedPatientContainer {
13
+ background-color: $ui-01;
14
+ }
15
+
16
+ .buttonCol {
17
+ display: flex;
18
+ flex-direction: column;
19
+ justify-content: space-between;
20
+ align-items: flex-end;
21
+ }
22
+
23
+ .patientBanner {
24
+ display: flex;
25
+ }
26
+
27
+ .patientAvatar {
28
+ width: 3.5rem;
29
+ height: 3.5rem;
30
+ margin: layout.$spacing-05;
31
+ border-radius: 1px;
32
+ }
33
+
34
+ .patientAvatarButton {
35
+ cursor: pointer;
36
+ border: none;
37
+ padding: 0px;
38
+ background: none;
39
+ }
40
+
41
+ .contactDetails {
42
+ color: $text-02;
43
+ width: 100%;
44
+ display: flex;
45
+
46
+ a {
47
+ @include type.type-style('body-compact-01');
48
+ text-decoration: none;
49
+ }
50
+ }
51
+
52
+ .tabletContactDetails {
53
+ display: block;
54
+
55
+ .row {
56
+ &:first-child {
57
+ border-bottom: 1px solid $ui-03;
58
+ }
59
+ }
60
+
61
+ .row > .col {
62
+ &:nth-of-type(2n + 1) {
63
+ border-right: 1px solid $ui-03;
64
+ }
65
+ }
66
+ }
67
+
68
+ .toggleContactDetailsButton {
69
+ > svg {
70
+ fill: $color-blue-60-2 !important;
71
+ }
72
+ }
@@ -0,0 +1,85 @@
1
+ import React, { useState } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { showModal, useConfig, useLayoutType, usePatient } from '@openmrs/esm-framework';
4
+ import styles from './patient-vitals-history.scss';
5
+ import { getPatientUuidFromUrl } from './utils';
6
+ import PatientBanner from './patient-banner/patient-banner.component';
7
+ import VitalsHeader from './vitals-and-biometrics-header/vitals-header.extension';
8
+ import { Tab, Button, Tabs, TabList, TabPanels, TabPanel } from '@carbon/react';
9
+ import VitalsOverview from './triage-vitals/triage-vitals-history.component';
10
+ import BiometricsBase from './triage-anthropometrics/triage-antropometrics.component';
11
+ import NotesSummary from './triage-notes/triage-notes.component';
12
+ import { AssetMovement } from '@carbon/react/icons';
13
+ import { useCurrentQueueEntry } from './patient-vitals-history.resource';
14
+ import { ConfigObject } from '../config-schema';
15
+ const openmrsSpaBase = window['getOpenmrsSpaBase']();
16
+
17
+ const PatientVitalsHistory: React.FC = () => {
18
+ const { t } = useTranslation();
19
+ const isTablet = useLayoutType() === 'tablet';
20
+ const patientUuid = getPatientUuidFromUrl();
21
+ const { patient, isLoading, error } = usePatient(patientUuid);
22
+ const { currentQueueEntry, isLoading: isLoadingQueueEntry } = useCurrentQueueEntry(patientUuid);
23
+ const [selectedTabIndex, setSelectedTabIndex] = useState(0);
24
+ const config = useConfig<ConfigObject>();
25
+
26
+ const handleMovePatientToQueue = (patientUuid: string) => {
27
+ const dispose = showModal('move-patient-to-new-room-modal', {
28
+ patientUuid,
29
+ context: 'triage',
30
+ closeModal: () => dispose(),
31
+ afterSubmitNavigateUrl: `${openmrsSpaBase}home/triage`,
32
+ });
33
+ };
34
+
35
+ const isInServiceStatus = currentQueueEntry?.status?.uuid === config.concepts.inServiceStatusUuid;
36
+
37
+ return (
38
+ <div>
39
+ <div className={styles.header}>{t('patientVitalsHistory', 'Patient Vitals History')}</div>
40
+ {patient && <PatientBanner patientUuid={patientUuid} patient={patient} hideActionsOverflow={true} />}
41
+ <VitalsHeader patientUuid={patientUuid} />
42
+
43
+ <div className={styles.tabsContainer}>
44
+ <Tabs onChange={(e) => setSelectedTabIndex(e.selectedIndex)}>
45
+ <div className={styles.tabsHeaderRow}>
46
+ <TabList scrollDebounceWait={200}>
47
+ <Tab>{t('triageNotes', 'Triage Notes')}</Tab>
48
+ <Tab>{t('vitalSigns', 'Vital Signs')}</Tab>
49
+ <Tab>{t('anthropometrics', 'Anthropometrics')}</Tab>
50
+ </TabList>
51
+ {!isLoadingQueueEntry && isInServiceStatus && (
52
+ <Button
53
+ size="sm"
54
+ kind="primary"
55
+ renderIcon={AssetMovement}
56
+ className={selectedTabIndex === 0 ? styles.buttonNotesTab : styles.buttonOtherTabs}
57
+ onClick={() => handleMovePatientToQueue(patientUuid)}>
58
+ {t('transitionPatient', 'Transition Patient')}
59
+ </Button>
60
+ )}
61
+ </div>
62
+ <TabPanels>
63
+ <TabPanel>
64
+ <div className={styles.notesContainer}>
65
+ <NotesSummary patientUuid={patientUuid} initialDisplayCount={6} loadMoreCount={6} />
66
+ </div>
67
+ </TabPanel>
68
+ <TabPanel>
69
+ <div className={styles.vitalsContainer}>
70
+ <VitalsOverview patientUuid={patientUuid} patient={patient} pageSize={10} urlLabel="vitals" />
71
+ </div>
72
+ </TabPanel>
73
+ <TabPanel>
74
+ <div className={styles.vitalsContainer}>
75
+ <BiometricsBase patientUuid={patientUuid} patient={patient} pageSize={10} urlLabel="anthropometrics" />
76
+ </div>
77
+ </TabPanel>
78
+ </TabPanels>
79
+ </Tabs>
80
+ </div>
81
+ </div>
82
+ );
83
+ };
84
+
85
+ export default PatientVitalsHistory;
@@ -0,0 +1,30 @@
1
+ import { FetchResponse, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
2
+ import useSWR from 'swr';
3
+ import { QueueEntry } from '../queue-room-table/type';
4
+ import { useMemo } from 'react';
5
+
6
+ export function useCurrentQueueEntry(patientUuid: string) {
7
+ const customRepresentation =
8
+ 'custom:(uuid,queue:(uuid,display),queueRoom:(uuid,display),status:(uuid,display),priority:(uuid,display),patient:(uuid,display),startedAt,queueComingFrom:(uuid,display),queueRoomComingFrom:(uuid,display))';
9
+
10
+ const apiUrl = `${restBaseUrl}/queue-entry?patient=${patientUuid}&isEnded=false&v=${customRepresentation}`;
11
+
12
+ const { data, error, isLoading, mutate } = useSWR<FetchResponse<{ results: Array<QueueEntry> }>>(
13
+ patientUuid ? apiUrl : null,
14
+ openmrsFetch,
15
+ );
16
+
17
+ const currentQueueEntry = useMemo(() => {
18
+ if (!data?.data?.results || data.data.results.length === 0) {
19
+ return null;
20
+ }
21
+ return data.data.results[0];
22
+ }, [data]);
23
+
24
+ return {
25
+ currentQueueEntry,
26
+ isLoading,
27
+ error,
28
+ mutate,
29
+ };
30
+ }