@kenyaemr/esm-ward-app 8.5.1-pre.50 → 8.5.1-pre.61

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 (116) hide show
  1. package/.turbo/turbo-build.log +30 -20
  2. package/dist/1498.js +1 -1
  3. package/dist/1498.js.LICENSE.txt +65 -0
  4. package/dist/1498.js.map +1 -1
  5. package/dist/1899.js +1 -1
  6. package/dist/1899.js.map +1 -1
  7. package/dist/1917.js +1 -1
  8. package/dist/1917.js.map +1 -1
  9. package/dist/2557.js +1 -1
  10. package/dist/2557.js.map +1 -1
  11. package/dist/2932.js +1 -1
  12. package/dist/3103.js +1 -0
  13. package/dist/3103.js.map +1 -0
  14. package/dist/3104.js +1 -0
  15. package/dist/3365.js +1 -1
  16. package/dist/3737.js +1 -1
  17. package/dist/4224.js +1 -2
  18. package/dist/4224.js.map +1 -1
  19. package/dist/4300.js +1 -1
  20. package/dist/6009.js +1 -1
  21. package/dist/6009.js.map +1 -1
  22. package/dist/6871.js +1 -2
  23. package/dist/6871.js.map +1 -1
  24. package/dist/717.js +1 -0
  25. package/dist/717.js.map +1 -0
  26. package/dist/7179.js +1 -2
  27. package/dist/7179.js.map +1 -1
  28. package/dist/723.js +1 -1
  29. package/dist/723.js.map +1 -0
  30. package/dist/7449.js +1 -1
  31. package/dist/7449.js.map +1 -1
  32. package/dist/7495.js +2 -0
  33. package/dist/7495.js.map +1 -0
  34. package/dist/7524.js +1 -1
  35. package/dist/7524.js.map +1 -1
  36. package/dist/7661.js +1 -1
  37. package/dist/7661.js.map +1 -1
  38. package/dist/7893.js +1 -0
  39. package/dist/7893.js.map +1 -0
  40. package/dist/8130.js +1 -0
  41. package/dist/8130.js.map +1 -0
  42. package/dist/8205.js +1 -1
  43. package/dist/8308.js +1 -1
  44. package/dist/8308.js.map +1 -1
  45. package/dist/8411.js +1 -0
  46. package/dist/8411.js.map +1 -0
  47. package/dist/8501.js +1 -2
  48. package/dist/8501.js.map +1 -1
  49. package/dist/9045.js +1 -1
  50. package/dist/9045.js.map +1 -1
  51. package/dist/9830.js +1 -0
  52. package/dist/9830.js.map +1 -0
  53. package/dist/9876.js +1 -2
  54. package/dist/9876.js.map +1 -1
  55. package/dist/{1879.js → 9954.js} +1 -1
  56. package/dist/9954.js.map +1 -0
  57. package/dist/kenyaemr-esm-ward-app.js +1 -1
  58. package/dist/kenyaemr-esm-ward-app.js.buildmanifest.json +251 -326
  59. package/dist/kenyaemr-esm-ward-app.js.map +1 -1
  60. package/dist/main.js +2 -1
  61. package/dist/main.js.LICENSE.txt +114 -0
  62. package/dist/main.js.map +1 -1
  63. package/dist/routes.json +1 -1
  64. package/package.json +1 -1
  65. package/src/config-schema.ts +55 -0
  66. package/src/hooks/useSummaryMetrics.ts +1 -1
  67. package/src/in-patient/admission-request.component.tsx +114 -0
  68. package/src/in-patient/admission-request.test.tsx +472 -0
  69. package/src/in-patient/encounter-observations/encounter-observations.component.tsx +71 -0
  70. package/src/in-patient/encounter-observations/index.ts +3 -0
  71. package/src/in-patient/encounter-observations/styles.scss +22 -0
  72. package/src/in-patient/encounter-observations/visit.resource.tsx +161 -0
  73. package/src/in-patient/in-patient-table/in-patient-table.component.tsx +155 -0
  74. package/src/in-patient/in-patient-table/in-patient-table.scss +37 -0
  75. package/src/in-patient/in-patient.component.tsx +32 -0
  76. package/src/in-patient/in-patient.meta.tsx +10 -0
  77. package/src/in-patient/in-patient.resource.tsx +56 -0
  78. package/src/in-patient/inpatient-detail-view.component.tsx +129 -0
  79. package/src/in-patient/inpatient-forms.component.tsx +74 -0
  80. package/src/in-patient/inpatient.scss +23 -0
  81. package/src/index.ts +9 -0
  82. package/src/routes.json +20 -0
  83. package/src/ward-view/linelist-wards/LineListTable.tsx +6 -6
  84. package/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.workspace.tsx +17 -8
  85. package/src/ward-workspace/admit-patient-form-workspace/patient-admission.resources.ts +2 -6
  86. package/src/ward.resource.ts +10 -2
  87. package/translations/en.json +25 -0
  88. package/dist/1879.js.map +0 -1
  89. package/dist/1919.js +0 -1
  90. package/dist/2123.js +0 -1
  91. package/dist/2123.js.map +0 -1
  92. package/dist/237.js +0 -2
  93. package/dist/237.js.LICENSE.txt +0 -54
  94. package/dist/237.js.map +0 -1
  95. package/dist/2898.js +0 -2
  96. package/dist/2898.js.map +0 -1
  97. package/dist/2953.js +0 -2
  98. package/dist/2953.js.LICENSE.txt +0 -9
  99. package/dist/2953.js.map +0 -1
  100. package/dist/4191.js +0 -2
  101. package/dist/4191.js.LICENSE.txt +0 -9
  102. package/dist/4191.js.map +0 -1
  103. package/dist/4224.js.LICENSE.txt +0 -9
  104. package/dist/4300.js.map +0 -1
  105. package/dist/465.js +0 -1
  106. package/dist/465.js.map +0 -1
  107. package/dist/681.js +0 -2
  108. package/dist/681.js.LICENSE.txt +0 -9
  109. package/dist/681.js.map +0 -1
  110. package/dist/6871.js.LICENSE.txt +0 -9
  111. package/dist/7179.js.LICENSE.txt +0 -9
  112. package/dist/8501.js.LICENSE.txt +0 -5
  113. package/dist/8622.js +0 -1
  114. package/dist/8622.js.map +0 -1
  115. package/dist/9876.js.LICENSE.txt +0 -9
  116. /package/dist/{2898.js.LICENSE.txt → 7495.js.LICENSE.txt} +0 -0
@@ -0,0 +1,161 @@
1
+ import type { OpenmrsResource, Privilege } from '@openmrs/esm-framework';
2
+
3
+ export interface MappedEncounter {
4
+ id: string;
5
+ datetime: string;
6
+ encounterType: string;
7
+ editPrivilege: string;
8
+ form: OpenmrsResource;
9
+ obs: Array<Observation>;
10
+ provider: string;
11
+ visitUuid: string;
12
+ visitType: string;
13
+ visitTypeUuid?: string;
14
+ visitStartDatetime?: string;
15
+ visitStopDatetime?: string;
16
+ }
17
+
18
+ export interface Encounter {
19
+ uuid: string;
20
+ diagnoses: Array<Diagnosis>;
21
+ encounterDatetime: string;
22
+ encounterProviders: Array<EncounterProvider>;
23
+ encounterType: {
24
+ uuid: string;
25
+ display: string;
26
+ viewPrivilege: Privilege;
27
+ editPrivilege: Privilege;
28
+ };
29
+ obs: Array<Observation>;
30
+ orders: Array<Order>;
31
+ form: OpenmrsResource;
32
+ patient: OpenmrsResource;
33
+ }
34
+
35
+ export interface EncounterProvider {
36
+ uuid: string;
37
+ display: string;
38
+ encounterRole: {
39
+ uuid: string;
40
+ display: string;
41
+ };
42
+ provider: {
43
+ uuid: string;
44
+ person: {
45
+ uuid: string;
46
+ display: string;
47
+ };
48
+ };
49
+ }
50
+
51
+ export interface Observation {
52
+ uuid: string;
53
+ concept: {
54
+ uuid: string;
55
+ display: string;
56
+ conceptClass: {
57
+ uuid: string;
58
+ display: string;
59
+ };
60
+ };
61
+ display: string;
62
+ groupMembers: null | Array<{
63
+ uuid: string;
64
+ concept: {
65
+ uuid: string;
66
+ display: string;
67
+ };
68
+ value: {
69
+ uuid: string;
70
+ display: string;
71
+ };
72
+ display: string;
73
+ }>;
74
+ value: any;
75
+ obsDatetime?: string;
76
+ }
77
+
78
+ export interface Order {
79
+ uuid: string;
80
+ dateActivated: string;
81
+ dateStopped?: Date | null;
82
+ dose: number;
83
+ dosingInstructions: string | null;
84
+ dosingType?: 'org.openmrs.FreeTextDosingInstructions' | 'org.openmrs.SimpleDosingInstructions';
85
+ doseUnits: {
86
+ uuid: string;
87
+ display: string;
88
+ };
89
+ drug: {
90
+ uuid: string;
91
+ name: string;
92
+ strength: string;
93
+ display: string;
94
+ };
95
+ duration: number;
96
+ durationUnits: {
97
+ uuid: string;
98
+ display: string;
99
+ };
100
+ frequency: {
101
+ uuid: string;
102
+ display: string;
103
+ };
104
+ numRefills: number;
105
+ orderNumber: string;
106
+ orderReason: string | null;
107
+ orderReasonNonCoded: string | null;
108
+ orderer: {
109
+ uuid: string;
110
+ person: {
111
+ uuid: string;
112
+ display: string;
113
+ };
114
+ };
115
+ orderType: {
116
+ uuid: string;
117
+ display: string;
118
+ };
119
+ route: {
120
+ uuid: string;
121
+ display: string;
122
+ };
123
+ quantity: number;
124
+ quantityUnits: OpenmrsResource;
125
+ }
126
+
127
+ export interface Note {
128
+ concept: OpenmrsResource;
129
+ note: string;
130
+ provider: {
131
+ name: string;
132
+ role: string;
133
+ };
134
+ time: string;
135
+ }
136
+
137
+ export interface OrderItem {
138
+ order: Order;
139
+ provider: {
140
+ name: string;
141
+ role: string;
142
+ };
143
+ }
144
+
145
+ export interface Diagnosis {
146
+ certainty: string;
147
+ display: string;
148
+ encounter: OpenmrsResource;
149
+ links: Array<any>;
150
+ patient: OpenmrsResource;
151
+ rank: number;
152
+ resourceVersion: string;
153
+ uuid: string;
154
+ voided: boolean;
155
+ diagnosis: {
156
+ coded: {
157
+ display: string;
158
+ links: Array<any>;
159
+ };
160
+ };
161
+ }
@@ -0,0 +1,155 @@
1
+ import React, { useMemo } from 'react';
2
+ import {
3
+ DataTable,
4
+ TableContainer,
5
+ Table,
6
+ TableHead,
7
+ TableRow,
8
+ TableExpandHeader,
9
+ TableHeader,
10
+ TableBody,
11
+ TableExpandRow,
12
+ TableExpandedRow,
13
+ TableCell,
14
+ Button,
15
+ } from '@carbon/react';
16
+ import { Edit } from '@carbon/react/icons';
17
+ import { type Encounter } from '../encounter-observations/visit.resource';
18
+ import { useTranslation } from 'react-i18next';
19
+ import { formatDatetime, launchWorkspace, usePagination, type Visit } from '@openmrs/esm-framework';
20
+ import EncounterObservations from '../encounter-observations';
21
+ import { EmptyState, PatientChartPagination } from '@openmrs/esm-patient-common-lib';
22
+ import styles from './in-patient-table.scss';
23
+ import { mutate } from 'swr';
24
+
25
+ type InPatientTableProps = {
26
+ tableRows: Array<Encounter>;
27
+ currentVisit: Visit;
28
+ };
29
+
30
+ const InPatientTable: React.FC<InPatientTableProps> = ({ tableRows }) => {
31
+ const { t } = useTranslation();
32
+ const headers = [
33
+ { key: 'dateTime', header: t('dateDate', 'Date & time') },
34
+ { key: 'formName', header: t('formName', 'Form Name') },
35
+ { key: 'provider', header: t('provider', 'Provider') },
36
+ { key: 'encounterType', header: t('encounterType', 'Encounter Type') },
37
+ ];
38
+
39
+ const { results, goTo, currentPage } = usePagination(tableRows, 10);
40
+
41
+ const paginatedRows = useMemo(() => {
42
+ return results.map((row) => ({
43
+ id: row.uuid,
44
+ dateTime: formatDatetime(new Date(row.encounterDatetime), {
45
+ mode: 'standard',
46
+ }),
47
+ formName: row.form.display,
48
+ provider: row.encounterProviders[0]?.provider?.person?.display,
49
+ encounterType: row.encounterType.display,
50
+ }));
51
+ }, [results]);
52
+
53
+ const onEncounterEdit = (encounter: Encounter) => {
54
+ launchWorkspace('patient-form-entry-workspace', {
55
+ workspaceTitle: encounter.form.display,
56
+ mutateForm: () => {
57
+ mutate((key) => typeof key === 'string' && key.startsWith(`/ws/rest/v1/encounter`), undefined, {
58
+ revalidate: true,
59
+ });
60
+ },
61
+ formInfo: {
62
+ encounterUuid: encounter.uuid,
63
+ formUuid: encounter?.form?.uuid,
64
+ additionalProps: {},
65
+ },
66
+ });
67
+ };
68
+
69
+ if (results.length === 0) {
70
+ return (
71
+ <EmptyState displayText={t('noEncounters', 'No encounters found')} headerTitle={t('encounters', 'Encounters')} />
72
+ );
73
+ }
74
+
75
+ return (
76
+ <>
77
+ <DataTable size="sm" useZebraStyles rows={paginatedRows} headers={headers}>
78
+ {({
79
+ rows,
80
+ headers,
81
+ getHeaderProps,
82
+ getRowProps,
83
+
84
+ getTableProps,
85
+ getTableContainerProps,
86
+ }) => (
87
+ <TableContainer
88
+ title={t('encounters', 'Encounters')}
89
+ description={t('encountersDescription', 'List of encounters during the current visit')}
90
+ {...getTableContainerProps()}>
91
+ <Table {...getTableProps()} aria-label="sample table">
92
+ <TableHead>
93
+ <TableRow>
94
+ <TableExpandHeader aria-label="expand row" />
95
+ {headers.map((header, i) => (
96
+ <TableHeader
97
+ key={i}
98
+ {...getHeaderProps({
99
+ header,
100
+ })}>
101
+ {header.header}
102
+ </TableHeader>
103
+ ))}
104
+ </TableRow>
105
+ </TableHead>
106
+ <TableBody>
107
+ {rows.map((row, index) => (
108
+ <React.Fragment key={row.id}>
109
+ <TableExpandRow
110
+ {...getRowProps({
111
+ row,
112
+ })}>
113
+ {row.cells.map((cell) => (
114
+ <TableCell key={cell.id}>{cell.value}</TableCell>
115
+ ))}
116
+ </TableExpandRow>
117
+ {row.isExpanded ? (
118
+ <TableExpandedRow className={styles.expandedRow} colSpan={headers.length + 2}>
119
+ <>
120
+ <EncounterObservations observations={results[index].obs} />
121
+
122
+ <>
123
+ {results[index]?.form?.uuid && (
124
+ <Button
125
+ kind="ghost"
126
+ onClick={() => onEncounterEdit(results[index])}
127
+ renderIcon={(props) => <Edit size={16} {...props} />}>
128
+ {t('editThisEncounter', 'Edit this encounter')}
129
+ </Button>
130
+ )}
131
+ </>
132
+ </>
133
+ </TableExpandedRow>
134
+ ) : (
135
+ <TableExpandedRow className={styles.hiddenRow} colSpan={headers.length + 2} />
136
+ )}
137
+ </React.Fragment>
138
+ ))}
139
+ </TableBody>
140
+ </Table>
141
+ </TableContainer>
142
+ )}
143
+ </DataTable>
144
+ <PatientChartPagination
145
+ currentItems={results.length}
146
+ totalItems={tableRows?.length}
147
+ pageNumber={currentPage}
148
+ pageSize={10}
149
+ onPageNumberChange={(page) => goTo(page)}
150
+ />
151
+ </>
152
+ );
153
+ };
154
+
155
+ export default InPatientTable;
@@ -0,0 +1,37 @@
1
+ @use '@carbon/layout';
2
+ @use '@carbon/type';
3
+ @use '@openmrs/esm-styleguide/src/vars' as *;
4
+
5
+ .tableContainer {
6
+ padding: 0;
7
+
8
+ :global(.cds--data-table-header) {
9
+ padding: 0;
10
+ }
11
+
12
+ :global(.cds--table-toolbar) {
13
+ position: relative;
14
+ overflow: visible;
15
+ top: 0;
16
+ }
17
+
18
+ &:global(.cds--data-table-container) {
19
+ background: none;
20
+ }
21
+ }
22
+
23
+ .expandedRow {
24
+ padding-inline-start: layout.$spacing-05;
25
+
26
+ > td {
27
+ padding: inherit;
28
+
29
+ > div {
30
+ max-height: max-content;
31
+ }
32
+ }
33
+
34
+ > div {
35
+ background-color: $ui-02;
36
+ }
37
+ }
@@ -0,0 +1,32 @@
1
+ import { Tab, TabList, TabPanel, TabPanels, Tabs } from '@carbon/react';
2
+ import React from 'react';
3
+ import AdmissionRequest from './admission-request.component';
4
+ import { useTranslation } from 'react-i18next';
5
+ import InpatientDetailView from './inpatient-detail-view.component';
6
+
7
+ type InPatientProps = {
8
+ patientUuid: string;
9
+ patient: fhir.Patient;
10
+ };
11
+
12
+ const InPatient: React.FC<InPatientProps> = ({ patientUuid }) => {
13
+ const { t } = useTranslation();
14
+ return (
15
+ <Tabs>
16
+ <TabList contained>
17
+ <Tab>{t('admissionRequests', 'Admission Requests')}</Tab>
18
+ <Tab>{t('inpatientDetails', 'Inpatient Detail')}</Tab>
19
+ </TabList>
20
+ <TabPanels>
21
+ <TabPanel>
22
+ <AdmissionRequest patientUuid={patientUuid} />;
23
+ </TabPanel>
24
+ <TabPanel>
25
+ <InpatientDetailView patientUuid={patientUuid} />
26
+ </TabPanel>
27
+ </TabPanels>
28
+ </Tabs>
29
+ );
30
+ };
31
+
32
+ export default InPatient;
@@ -0,0 +1,10 @@
1
+ export const inPatientMeta = {
2
+ slot: 'patient-chart-in-patient-dashboard-slot',
3
+ path: 'in-patient',
4
+ title: 'In Patient',
5
+ moduleName: '@kenyaemr/esm-bed-management-app',
6
+ name: 'In Patient',
7
+ columns: 1,
8
+ config: {},
9
+ icon: 'omrs-icon-hospital-bed',
10
+ };
@@ -0,0 +1,56 @@
1
+ import { type Encounter, openmrsFetch, restBaseUrl, useConfig } from '@openmrs/esm-framework';
2
+ import useSWR from 'swr';
3
+ import type { InpatientRequest as AdmissionRequest } from '../types';
4
+ import type { WardConfigObject } from '../config-schema';
5
+
6
+ export const usePatientEncounters = (patientUuid: string) => {
7
+ const { inPatientForms } = useConfig<WardConfigObject>();
8
+ const { data, isLoading, error, mutate } = useSWR<{
9
+ data: { results: Array<Encounter> };
10
+ }>(
11
+ `${restBaseUrl}/encounter?patient=${patientUuid}&v=custom:(uuid,display,encounterDatetime,obs:full,form:(uuid,display),encounterType:(uuid,display),encounterProviders:(uuid,display,encounterRole:(uuid,display),provider:(uuid,person:(uuid,display))),orders:(uuid,display),diagnoses:(uuid,display)`,
12
+ openmrsFetch,
13
+ );
14
+
15
+ const encounters =
16
+ data?.data?.['results']?.filter((encounter) =>
17
+ inPatientForms?.find((form) => form.uuid === encounter?.form?.uuid),
18
+ ) ?? [];
19
+
20
+ return {
21
+ encounters: encounters,
22
+ isLoading,
23
+ error,
24
+ mutate,
25
+ };
26
+ };
27
+
28
+ const defaultRep =
29
+ 'custom:(' +
30
+ 'dispositionLocation,' +
31
+ 'dispositionType,' +
32
+ 'disposition,' +
33
+ 'dispositionEncounter:full,' +
34
+ 'patient:(uuid,identifiers,voided,' +
35
+ 'person:(uuid,display,gender,age,birthdate,birthtime,preferredName,preferredAddress,dead,deathDate)),' +
36
+ 'dispositionObsGroup,' +
37
+ 'visit)';
38
+
39
+ export const useAdmissionRequest = (patientUuid: string) => {
40
+ const patientUuids = [patientUuid];
41
+ const searchParams = new URLSearchParams();
42
+ searchParams.set('dispositionType', 'ADMIT');
43
+ searchParams.set('patients', patientUuids.join(','));
44
+ searchParams.set('v', defaultRep);
45
+
46
+ const url = `${restBaseUrl}/emrapi/inpatient/request?${searchParams.toString()}`;
47
+
48
+ const { data, isLoading, error, mutate } = useSWR<{ data: { results: Array<AdmissionRequest> } }>(url, openmrsFetch);
49
+
50
+ return {
51
+ admissionRequest: data?.data?.results ?? [],
52
+ isLoading: isLoading,
53
+ error: error,
54
+ mutate: mutate,
55
+ };
56
+ };
@@ -0,0 +1,129 @@
1
+ import { DataTableSkeleton, InlineLoading, Layer, Tile } from '@carbon/react';
2
+ import {
3
+ ErrorState,
4
+ formatDatetime,
5
+ parseDate,
6
+ useEmrConfiguration,
7
+ usePatient,
8
+ useVisit,
9
+ type EmrApiConfigurationResponse,
10
+ } from '@openmrs/esm-framework';
11
+ import { CardHeader, EmptyDataIllustration } from '@openmrs/esm-patient-common-lib/src';
12
+ import dayjs from 'dayjs';
13
+ import React, { useMemo, type FC } from 'react';
14
+ import { useTranslation } from 'react-i18next';
15
+ import { useAdmissionLocation } from '../hooks/useAdmissionLocation';
16
+ import InpatientForms from './inpatient-forms.component';
17
+ import styles from './inpatient.scss';
18
+
19
+ type InpatientDetailViewProps = {
20
+ patientUuid: string;
21
+ };
22
+
23
+ const InpatientDetailView: FC<InpatientDetailViewProps> = ({ patientUuid }) => {
24
+ const { isLoading: isLoadingPatient, patient, error } = usePatient(patientUuid);
25
+ const { isLoadingEmrConfiguration, emrConfiguration, errorFetchingEmrConfiguration } = useEmrConfiguration();
26
+ const { isLoading: isLoadingActiveVisit, error: currVisistError } = useVisit(patientUuid);
27
+ const { t } = useTranslation();
28
+ if (isLoadingActiveVisit || isLoadingEmrConfiguration || isLoadingPatient) {
29
+ return <DataTableSkeleton />;
30
+ }
31
+
32
+ if (error || errorFetchingEmrConfiguration || currVisistError) {
33
+ return (
34
+ <ErrorState
35
+ error={error ?? errorFetchingEmrConfiguration ?? currVisistError}
36
+ headerTitle={t('inpatientdetails', 'Inpatient Details')}
37
+ />
38
+ );
39
+ }
40
+
41
+ return (
42
+ <div>
43
+ <PatientAdmitted patientUuid={patientUuid} patient={patient} emrConfiguration={emrConfiguration} />
44
+ </div>
45
+ );
46
+ };
47
+
48
+ export default InpatientDetailView;
49
+
50
+ const PatientAdmitted: FC<{
51
+ patientUuid: string;
52
+ patient: fhir.Patient;
53
+ emrConfiguration: EmrApiConfigurationResponse;
54
+ }> = ({ emrConfiguration, patient, patientUuid }) => {
55
+ const { currentVisit } = useVisit(patientUuid);
56
+ const { t } = useTranslation();
57
+
58
+ const { isPatientAdmitted, dateOfAdmission, dayasInWard, ward } = useMemo(() => {
59
+ const hasAdmissionEncounter = currentVisit.encounters.find(
60
+ (encounter) => encounter.encounterType.uuid === emrConfiguration?.admissionEncounterType?.uuid,
61
+ );
62
+ const hasDischargeEncounter = currentVisit.encounters.find(
63
+ (encounter) => encounter.encounterType.uuid === emrConfiguration?.exitFromInpatientEncounterType?.uuid,
64
+ );
65
+
66
+ const dateOfAdmission = hasAdmissionEncounter?.encounterDatetime
67
+ ? parseDate(hasAdmissionEncounter?.encounterDatetime)
68
+ : null;
69
+
70
+ const today = dayjs().startOf('day');
71
+ const dayasInWard = dateOfAdmission ? Math.abs(today.diff(dateOfAdmission, 'days')) : 0;
72
+
73
+ return {
74
+ isPatientAdmitted: hasAdmissionEncounter && !hasDischargeEncounter,
75
+ dateOfAdmission,
76
+ dayasInWard,
77
+ ward: hasAdmissionEncounter?.location,
78
+ };
79
+ }, [emrConfiguration, currentVisit]);
80
+ const { isLoading, admissionLocation, error } = useAdmissionLocation(undefined, ward?.uuid);
81
+ const bedLayout = useMemo(() => {
82
+ return admissionLocation?.bedLayouts?.find((layout) => layout.patients?.some((pat) => pat?.uuid === patientUuid));
83
+ }, [admissionLocation, patientUuid]);
84
+
85
+ if (!isPatientAdmitted) {
86
+ return (
87
+ <Layer>
88
+ <CardHeader title={t('admissionDetails', 'Admission Details')}>
89
+ <></>
90
+ </CardHeader>
91
+ <Tile className={styles.patientNotAdmitted}>
92
+ <EmptyDataIllustration />
93
+ <p>{t('patientNotAdmitted', 'This Patient Not currently admitted to ward')}</p>;
94
+ </Tile>
95
+ </Layer>
96
+ );
97
+ }
98
+ return (
99
+ <Layer>
100
+ <CardHeader title={t('admissionDetail', 'Admission Details')}>
101
+ <InpatientForms patientUuid={patientUuid} patient={patient} emrConfiguration={emrConfiguration} />
102
+ </CardHeader>
103
+ <div className={styles.detailsContainer}>
104
+ <Tile>
105
+ <strong>{t('dateOfAdmission', 'Date of Admission')}</strong>
106
+ <p>{formatDatetime(dateOfAdmission)}</p>
107
+ </Tile>
108
+ <Tile>
109
+ <strong>{t('daysInWard', 'Days in ward')}</strong>
110
+ <p>{dayasInWard}</p>
111
+ </Tile>
112
+ <Tile>
113
+ <strong>{t('ward', 'Ward')}</strong>
114
+ <p>{ward?.display}</p>
115
+ </Tile>
116
+ <Tile>
117
+ {isLoading ? (
118
+ <InlineLoading />
119
+ ) : (
120
+ <>
121
+ <strong>{t('bed', 'Bed')}</strong>
122
+ <p>{bedLayout?.bedNumber}</p>
123
+ </>
124
+ )}
125
+ </Tile>
126
+ </div>
127
+ </Layer>
128
+ );
129
+ };
@@ -0,0 +1,74 @@
1
+ import { ComboButton, Dropdown, MenuItem } from '@carbon/react';
2
+ import React, { type FC, useMemo } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import {
5
+ type EmrApiConfigurationResponse,
6
+ evaluateAsBoolean,
7
+ launchWorkspace,
8
+ useConfig,
9
+ useVisit,
10
+ } from '@openmrs/esm-framework';
11
+ import { launchStartVisitPrompt } from '@openmrs/esm-patient-common-lib';
12
+ import dayjs from 'dayjs';
13
+ import { type WardConfigObject } from '../config-schema';
14
+ type InpatientFormsProps = {
15
+ patientUuid: string;
16
+ patient: fhir.Patient;
17
+ emrConfiguration: EmrApiConfigurationResponse;
18
+ };
19
+ const InpatientForms: FC<InpatientFormsProps> = ({ patientUuid, patient, emrConfiguration }) => {
20
+ const { t } = useTranslation();
21
+ const { inPatientForms } = useConfig<WardConfigObject>();
22
+ const { currentVisit } = useVisit(patientUuid);
23
+ const filteredForms = inPatientForms.filter((form) => {
24
+ if (!form.hideExpression) {
25
+ return true;
26
+ }
27
+ const age = dayjs().diff(dayjs(patient.birthDate), 'year');
28
+ const ageInDays = dayjs().diff(dayjs(patient.birthDate), 'day');
29
+ const ageInMonths = dayjs().diff(dayjs(patient.birthDate), 'month');
30
+ const gender = patient.gender;
31
+ const hide = form.hideExpression
32
+ ? evaluateAsBoolean(form.hideExpression, { age, gender, ageInDays, ageInMonths })
33
+ : false;
34
+ return hide;
35
+ });
36
+
37
+ const isPatientAdmitted = useMemo(() => {
38
+ const hasAdmissionEncounter = currentVisit.encounters.some(
39
+ (encounter) => encounter.encounterType.uuid === emrConfiguration?.admissionEncounterType?.uuid,
40
+ );
41
+ const hasDischargeEncounter = currentVisit.encounters.some(
42
+ (encounter) => encounter.encounterType.uuid === emrConfiguration?.exitFromInpatientEncounterType?.uuid,
43
+ );
44
+ return hasAdmissionEncounter && !hasDischargeEncounter;
45
+ }, [emrConfiguration, currentVisit]);
46
+
47
+ const handleLaunchForm = (form: { label: string; uuid: string }) => {
48
+ if (!currentVisit) {
49
+ return launchStartVisitPrompt();
50
+ }
51
+ launchWorkspace('patient-form-entry-workspace', {
52
+ workspaceTitle: form.label,
53
+ mutateForm: () => {},
54
+ formInfo: {
55
+ encounterUuid: '',
56
+ formUuid: form.uuid,
57
+ additionalProps: {},
58
+ },
59
+ });
60
+ };
61
+
62
+ if (!isPatientAdmitted) {
63
+ return null;
64
+ }
65
+ return (
66
+ <ComboButton size="sm" label={t('inPatientForms', 'In-Patient Forms')}>
67
+ {filteredForms.map((form) => (
68
+ <MenuItem key={form.uuid} onClick={() => handleLaunchForm(form)} label={form.label} />
69
+ ))}
70
+ </ComboButton>
71
+ );
72
+ };
73
+
74
+ export default InpatientForms;
@@ -0,0 +1,23 @@
1
+ @use '@carbon/layout';
2
+ @use '@carbon/type';
3
+ @use '@carbon/colors';
4
+
5
+ .patientNotAdmitted {
6
+ margin-bottom: layout.$spacing-05;
7
+ display: flex;
8
+ flex-direction: column;
9
+ align-items: center;
10
+ gap: layout.$spacing-05;
11
+ }
12
+
13
+ .layoutContainer {
14
+ border: solid 1px colors.$gray-40;
15
+ }
16
+
17
+ .detailsContainer {
18
+ display: grid;
19
+
20
+ grid-template-columns: 1fr 1fr;
21
+
22
+ gap: layout.$spacing-05;
23
+ }