@kenyaemr/esm-bed-management-app 1.0.1-pre.11
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.
- package/.editorconfig +12 -0
- package/.eslintignore +2 -0
- package/.eslintrc +37 -0
- package/.husky/pre-commit +4 -0
- package/.idea/inspectionProfiles/Project_Default.xml +6 -0
- package/.idea/modules.xml +8 -0
- package/.idea/vcs.xml +6 -0
- package/.prettierignore +14 -0
- package/.turbo.json +18 -0
- package/.yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs +541 -0
- package/.yarn/plugins/@yarnpkg/plugin-version.cjs +550 -0
- package/.yarn/versions/3d353a50.yml +0 -0
- package/LICENSE +373 -0
- package/README.md +40 -0
- package/dist/207.js +1 -0
- package/dist/207.js.map +1 -0
- package/dist/26.js +2 -0
- package/dist/26.js.LICENSE.txt +32 -0
- package/dist/26.js.map +1 -0
- package/dist/283.js +1 -0
- package/dist/283.js.map +1 -0
- package/dist/294.js +2 -0
- package/dist/294.js.LICENSE.txt +9 -0
- package/dist/294.js.map +1 -0
- package/dist/330.js +2 -0
- package/dist/330.js.LICENSE.txt +44 -0
- package/dist/330.js.map +1 -0
- package/dist/404.js +1 -0
- package/dist/404.js.map +1 -0
- package/dist/455.js +2 -0
- package/dist/455.js.LICENSE.txt +9 -0
- package/dist/455.js.map +1 -0
- package/dist/558.js +2 -0
- package/dist/558.js.LICENSE.txt +14 -0
- package/dist/558.js.map +1 -0
- package/dist/574.js +1 -0
- package/dist/629.js +1 -0
- package/dist/629.js.map +1 -0
- package/dist/637.js +1 -0
- package/dist/637.js.map +1 -0
- package/dist/707.js +1 -0
- package/dist/707.js.map +1 -0
- package/dist/800.js +2 -0
- package/dist/800.js.LICENSE.txt +3 -0
- package/dist/800.js.map +1 -0
- package/dist/850.js +1 -0
- package/dist/850.js.map +1 -0
- package/dist/884.js +1 -0
- package/dist/884.js.map +1 -0
- package/dist/933.js +1 -0
- package/dist/933.js.map +1 -0
- package/dist/esm-kenyaemr-bed-management-app.js +1 -0
- package/dist/esm-kenyaemr-bed-management-app.js.buildmanifest.json +506 -0
- package/dist/esm-kenyaemr-bed-management-app.js.map +1 -0
- package/dist/main.js +1 -0
- package/dist/main.js.map +1 -0
- package/dist/routes.json +1 -0
- package/i18next-parser.config.js +89 -0
- package/jest.config.js +0 -0
- package/package.json +112 -0
- package/src/__mocks__/react-i18next.js +55 -0
- package/src/admin-card-link.component.tsx +27 -0
- package/src/assets/landing-page.png +0 -0
- package/src/assets/logo.svg +1 -0
- package/src/bed-administration/bed-administration-form.component.tsx +326 -0
- package/src/bed-administration/bed-administration-form.scss +0 -0
- package/src/bed-administration/bed-administration-table.component.tsx +317 -0
- package/src/bed-administration/bed-administration-table.scss +112 -0
- package/src/bed-administration/bed-administration-types.ts +20 -0
- package/src/bed-administration/bed-administration.resource.ts +59 -0
- package/src/bed-administration/edit-bed-form.component.tsx +100 -0
- package/src/bed-administration/new-bed-form.component.tsx +112 -0
- package/src/bed-admission/active-patients/active-patients-table.component.tsx +299 -0
- package/src/bed-admission/active-patients/active-visits.resource.ts +171 -0
- package/src/bed-admission/active-patients/admission-action-button-styles.scss +0 -0
- package/src/bed-admission/active-patients/admission-action-button.component.tsx +26 -0
- package/src/bed-admission/active-patients/index.tsx +15 -0
- package/src/bed-admission/active-patients/patient-queues.resource.ts +136 -0
- package/src/bed-admission/active-patients/styles.scss +284 -0
- package/src/bed-admission/active-patients/view-action-menu.component.tsx +33 -0
- package/src/bed-admission/admitted-patients/active-admissions.resource.ts +125 -0
- package/src/bed-admission/admitted-patients/admitted-patients-table.component.tsx +280 -0
- package/src/bed-admission/admitted-patients/admitted-patients.component.tsx +22 -0
- package/src/bed-admission/admitted-patients/location-combo-box.component.tsx +55 -0
- package/src/bed-admission/admitted-patients/styles.scss +284 -0
- package/src/bed-admission/bed-admission-tabs-styles.scss +30 -0
- package/src/bed-admission/bed-admission-tabs.component.tsx +69 -0
- package/src/bed-admission/bed-admission.component.tsx +15 -0
- package/src/bed-admission/bed-admission.resource.ts +52 -0
- package/src/bed-admission/bed-layout/bed-layout-list.component.tsx +101 -0
- package/src/bed-admission/bed-layout/bed-layout.component.tsx +64 -0
- package/src/bed-admission/bed-layout/bed-layout.scss +118 -0
- package/src/bed-admission/bed-layout/min-bed-layout.component.tsx +26 -0
- package/src/bed-admission/bed-tag/bed-tag-administration-table.component.tsx +217 -0
- package/src/bed-admission/bed-tag/bed-tags-admin-form.component.tsx +131 -0
- package/src/bed-admission/bed-tag/edit-tag-form.component.tsx +80 -0
- package/src/bed-admission/bed-tag/new-tag-form.component.tsx +83 -0
- package/src/bed-admission/bed-type/bed-type-admin-form.component.tsx +173 -0
- package/src/bed-admission/bed-type/bed-type-administration-table.component.tsx +222 -0
- package/src/bed-admission/bed-type/edit-bed-type.component.tsx +80 -0
- package/src/bed-admission/bed-type/new-bed-type-form.component.tsx +87 -0
- package/src/bed-admission/createDashboardLink.tsx +47 -0
- package/src/bed-admission/discharged-patients/discharged-patients.componet.tsx +19 -0
- package/src/bed-admission/helpers/functions.ts +102 -0
- package/src/bed-admission/types.ts +133 -0
- package/src/config-schema.ts +31 -0
- package/src/declarations.d.ts +7 -0
- package/src/empty-state/empty-state.component.tsx +69 -0
- package/src/empty-state/empty-state.scss +62 -0
- package/src/header/header.component.tsx +51 -0
- package/src/header/header.scss +72 -0
- package/src/header/illustration.component.tsx +13 -0
- package/src/home.component.tsx +15 -0
- package/src/home.scss +5 -0
- package/src/index.ts +78 -0
- package/src/left-panel/left-panel.component.tsx +33 -0
- package/src/left-panel/left-panel.scss +41 -0
- package/src/left-panel-link.component.tsx +49 -0
- package/src/root.component.tsx +39 -0
- package/src/root.scss +11 -0
- package/src/routes.json +56 -0
- package/src/setup-tests.ts +1 -0
- package/src/summary/summary.component.tsx +74 -0
- package/src/summary/summary.resource.ts +211 -0
- package/src/summary/summary.scss +72 -0
- package/src/types.ts +163 -0
- package/src/ward-card/ward-card.component.tsx +41 -0
- package/src/ward-card/ward-card.scss +51 -0
- package/src/ward-with-beds/ward-with-beds.component.tsx +186 -0
- package/src/ward-with-beds/ward-with-beds.scss +27 -0
- package/src/workspace/allocate-bed-workspace.component.tsx +188 -0
- package/src/workspace/allocate-bed.scss +124 -0
- package/src/workspace/overlay.component.tsx +55 -0
- package/src/workspace/overlay.scss +96 -0
- package/translations/en.json +7 -0
- package/tsconfig.json +23 -0
- package/webpack.config.js +1 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import React, { useCallback } from "react";
|
|
2
|
+
import { useTranslation } from "react-i18next";
|
|
3
|
+
import { showToast, showNotification, useConfig } from "@openmrs/esm-framework";
|
|
4
|
+
|
|
5
|
+
import type { InitialData, Mutator } from "../types";
|
|
6
|
+
import { useBedType, saveBed } from "./bed-administration.resource";
|
|
7
|
+
import BedAdministrationForm from "./bed-administration-form.component";
|
|
8
|
+
import { useLocationsByTag } from "../summary/summary.resource";
|
|
9
|
+
import { BedAdministrationData } from "./bed-administration-types";
|
|
10
|
+
|
|
11
|
+
interface NewBedFormProps {
|
|
12
|
+
showModal: boolean;
|
|
13
|
+
onModalChange: (showModal: boolean) => void;
|
|
14
|
+
mutate: Mutator;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const NewBedForm: React.FC<NewBedFormProps> = ({
|
|
18
|
+
showModal,
|
|
19
|
+
onModalChange,
|
|
20
|
+
mutate,
|
|
21
|
+
}) => {
|
|
22
|
+
const { t } = useTranslation();
|
|
23
|
+
const { admissionLocationTagUuid } = useConfig();
|
|
24
|
+
const { data: admissionLocations } = useLocationsByTag(
|
|
25
|
+
admissionLocationTagUuid
|
|
26
|
+
);
|
|
27
|
+
const headerTitle = t("createNewBed", "Create a new bed");
|
|
28
|
+
const occupancyStatuses = ["Available", "Occupied"];
|
|
29
|
+
const { bedTypes } = useBedType();
|
|
30
|
+
const availableBedTypes = bedTypes ? bedTypes : [];
|
|
31
|
+
|
|
32
|
+
const initialData: InitialData = {
|
|
33
|
+
uuid: "",
|
|
34
|
+
bedNumber: "",
|
|
35
|
+
status: "",
|
|
36
|
+
description: "",
|
|
37
|
+
row: 0,
|
|
38
|
+
column: 0,
|
|
39
|
+
location: {
|
|
40
|
+
display: "",
|
|
41
|
+
uuid: "",
|
|
42
|
+
},
|
|
43
|
+
bedType: {
|
|
44
|
+
name: "",
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const handleCreateQuestion = useCallback(
|
|
49
|
+
(formData: BedAdministrationData) => {
|
|
50
|
+
const {
|
|
51
|
+
bedId,
|
|
52
|
+
description,
|
|
53
|
+
occupancyStatus,
|
|
54
|
+
bedRow,
|
|
55
|
+
bedColumn,
|
|
56
|
+
location,
|
|
57
|
+
bedType,
|
|
58
|
+
} = formData;
|
|
59
|
+
|
|
60
|
+
const bedObject = {
|
|
61
|
+
bedNumber: bedId,
|
|
62
|
+
bedType,
|
|
63
|
+
description,
|
|
64
|
+
status: occupancyStatus.toUpperCase(),
|
|
65
|
+
row: parseInt(bedRow.toString()),
|
|
66
|
+
column: parseInt(bedColumn.toString()),
|
|
67
|
+
locationUuid: location.uuid,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
saveBed({ bedPayload: bedObject })
|
|
71
|
+
.then(() => {
|
|
72
|
+
showToast({
|
|
73
|
+
title: t("formCreated", "New bed created"),
|
|
74
|
+
kind: "success",
|
|
75
|
+
critical: true,
|
|
76
|
+
description: `Bed ${bedId} was created successfully.`,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
mutate();
|
|
80
|
+
onModalChange(false);
|
|
81
|
+
})
|
|
82
|
+
.catch((error) => {
|
|
83
|
+
showNotification({
|
|
84
|
+
title: t("errorCreatingForm", "Error creating bed"),
|
|
85
|
+
kind: "error",
|
|
86
|
+
critical: true,
|
|
87
|
+
description: error?.message,
|
|
88
|
+
});
|
|
89
|
+
onModalChange(false);
|
|
90
|
+
});
|
|
91
|
+
onModalChange(false);
|
|
92
|
+
},
|
|
93
|
+
[onModalChange, mutate, t]
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<>
|
|
98
|
+
<BedAdministrationForm
|
|
99
|
+
onModalChange={onModalChange}
|
|
100
|
+
allLocations={admissionLocations}
|
|
101
|
+
availableBedTypes={availableBedTypes}
|
|
102
|
+
showModal={showModal}
|
|
103
|
+
handleCreateQuestion={handleCreateQuestion}
|
|
104
|
+
headerTitle={headerTitle}
|
|
105
|
+
occupancyStatuses={occupancyStatuses}
|
|
106
|
+
initialData={initialData}
|
|
107
|
+
/>
|
|
108
|
+
</>
|
|
109
|
+
);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export default NewBedForm;
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DataTable,
|
|
3
|
+
DataTableSkeleton,
|
|
4
|
+
DefinitionTooltip,
|
|
5
|
+
Pagination,
|
|
6
|
+
Table,
|
|
7
|
+
TableBody,
|
|
8
|
+
TableCell,
|
|
9
|
+
TableContainer,
|
|
10
|
+
TableHead,
|
|
11
|
+
TableHeader,
|
|
12
|
+
TableRow,
|
|
13
|
+
Tag,
|
|
14
|
+
Layer,
|
|
15
|
+
TableToolbar,
|
|
16
|
+
TableToolbarContent,
|
|
17
|
+
TableToolbarSearch,
|
|
18
|
+
TableExpandedRow,
|
|
19
|
+
TableExpandHeader,
|
|
20
|
+
TableExpandRow,
|
|
21
|
+
} from "@carbon/react";
|
|
22
|
+
|
|
23
|
+
import {
|
|
24
|
+
isDesktop,
|
|
25
|
+
useConfig,
|
|
26
|
+
useLayoutType,
|
|
27
|
+
usePagination,
|
|
28
|
+
useSession,
|
|
29
|
+
} from "@openmrs/esm-framework";
|
|
30
|
+
import React, { useCallback, useMemo, useState } from "react";
|
|
31
|
+
import { useTranslation } from "react-i18next";
|
|
32
|
+
import {
|
|
33
|
+
formatWaitTime,
|
|
34
|
+
getOriginFromPathName,
|
|
35
|
+
getTagColor,
|
|
36
|
+
getTagType,
|
|
37
|
+
trimVisitNumber,
|
|
38
|
+
} from "../helpers/functions";
|
|
39
|
+
import styles from "./styles.scss";
|
|
40
|
+
import { usePatientQueuesList } from "./patient-queues.resource";
|
|
41
|
+
import { useActiveVisits } from "./active-visits.resource";
|
|
42
|
+
import EmptyState from "../../empty-state/empty-state.component";
|
|
43
|
+
import AssignBedWorkSpace from "../../workspace/allocate-bed-workspace.component";
|
|
44
|
+
import AdmissionActionButton from "./admission-action-button.component";
|
|
45
|
+
import { patientDetailsProps } from "../types";
|
|
46
|
+
import ViewActionsMenu from "./view-action-menu.component";
|
|
47
|
+
|
|
48
|
+
interface ActiveVisitsTableProps {
|
|
49
|
+
status: string;
|
|
50
|
+
setPatientCount?: (value: number) => void;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const ActivePatientsTable: React.FC<ActiveVisitsTableProps> = ({
|
|
54
|
+
status,
|
|
55
|
+
setPatientCount,
|
|
56
|
+
}) => {
|
|
57
|
+
const { t } = useTranslation();
|
|
58
|
+
const session = useSession();
|
|
59
|
+
const currentPathName: string = window.location.pathname;
|
|
60
|
+
const fromPage: string = getOriginFromPathName(currentPathName);
|
|
61
|
+
const pageSizes = [10, 20, 30, 40, 50];
|
|
62
|
+
const [currentPageSize, setPageSize] = useState(10);
|
|
63
|
+
const [showOverlay, setShowOverlay] = useState(false);
|
|
64
|
+
const [selectedPatientDetails, setSelectedPatientDetails] =
|
|
65
|
+
useState<patientDetailsProps>();
|
|
66
|
+
|
|
67
|
+
const layout = useLayoutType();
|
|
68
|
+
|
|
69
|
+
const { patientQueueEntries, isLoading } = useActiveVisits();
|
|
70
|
+
const { restrictWardAdministrationToLoginLocation } = useConfig();
|
|
71
|
+
|
|
72
|
+
const handleBedAssigmentModal = useCallback(
|
|
73
|
+
(entry) => {
|
|
74
|
+
setSelectedPatientDetails({
|
|
75
|
+
name: entry.name,
|
|
76
|
+
patientUuid: entry.patientUuid,
|
|
77
|
+
encounter: entry.encounter,
|
|
78
|
+
locationUuid: session?.sessionLocation?.uuid,
|
|
79
|
+
locationTo: entry.locationTo,
|
|
80
|
+
locationFrom: entry.locationFrom,
|
|
81
|
+
queueUuid: entry.uuid,
|
|
82
|
+
});
|
|
83
|
+
setShowOverlay(true);
|
|
84
|
+
},
|
|
85
|
+
[session?.sessionLocation?.uuid]
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const renderActionButton = useCallback(
|
|
89
|
+
(entry) => {
|
|
90
|
+
const buttonTexts = {
|
|
91
|
+
pending: "Assign Bed",
|
|
92
|
+
completed: "Transfer",
|
|
93
|
+
};
|
|
94
|
+
const buttonText = buttonTexts[status] || "Un-assign";
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<AdmissionActionButton
|
|
98
|
+
entry={entry}
|
|
99
|
+
handleBedAssigmentModal={handleBedAssigmentModal}
|
|
100
|
+
buttonText={buttonText}
|
|
101
|
+
/>
|
|
102
|
+
);
|
|
103
|
+
},
|
|
104
|
+
[handleBedAssigmentModal, status]
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const {
|
|
108
|
+
goTo,
|
|
109
|
+
results: paginatedQueueEntries,
|
|
110
|
+
currentPage,
|
|
111
|
+
} = usePagination(patientQueueEntries, currentPageSize);
|
|
112
|
+
|
|
113
|
+
const tableHeaders = useMemo(
|
|
114
|
+
() => [
|
|
115
|
+
{
|
|
116
|
+
id: 0,
|
|
117
|
+
header: t("name", "Name"),
|
|
118
|
+
key: "name",
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
id: 1,
|
|
122
|
+
header: t("idNumber", "Identifier"),
|
|
123
|
+
key: "idNumber",
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: 2,
|
|
127
|
+
header: t("gender", "Gender"),
|
|
128
|
+
key: "gender",
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
id: 3,
|
|
132
|
+
header: t("age", "Age"),
|
|
133
|
+
key: "age",
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
id: 4,
|
|
137
|
+
header: t("visitType", "Visit type"),
|
|
138
|
+
key: "visitType",
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
id: 5,
|
|
142
|
+
header: t("visitStartTime", "Visit start date/time"),
|
|
143
|
+
key: "visitStartTime",
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: 6,
|
|
147
|
+
header: t("action", "Action"),
|
|
148
|
+
key: "actions",
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
[t]
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
const tableRows = useMemo(() => {
|
|
155
|
+
return paginatedQueueEntries?.map((entry) => ({
|
|
156
|
+
...entry,
|
|
157
|
+
actions: {
|
|
158
|
+
content: (
|
|
159
|
+
<div className={styles.displayFlex}>{renderActionButton(entry)}</div>
|
|
160
|
+
),
|
|
161
|
+
},
|
|
162
|
+
}));
|
|
163
|
+
}, [paginatedQueueEntries, renderActionButton]);
|
|
164
|
+
|
|
165
|
+
if (isLoading) {
|
|
166
|
+
return <DataTableSkeleton role="progressbar" />;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (
|
|
170
|
+
(!isLoading && patientQueueEntries && status === "pending") ||
|
|
171
|
+
status === "completed" ||
|
|
172
|
+
status === ""
|
|
173
|
+
) {
|
|
174
|
+
setPatientCount(patientQueueEntries.length);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (patientQueueEntries?.length) {
|
|
178
|
+
return (
|
|
179
|
+
<div className={styles.container}>
|
|
180
|
+
<div className={styles.headerBtnContainer}></div>
|
|
181
|
+
|
|
182
|
+
<DataTable
|
|
183
|
+
data-floating-menu-container
|
|
184
|
+
headers={tableHeaders}
|
|
185
|
+
overflowMenuOnHover={isDesktop(layout) ? true : false}
|
|
186
|
+
rows={tableRows}
|
|
187
|
+
isSortable
|
|
188
|
+
size="xs"
|
|
189
|
+
useZebraStyles
|
|
190
|
+
>
|
|
191
|
+
{({ rows, headers, getTableProps, getRowProps, onInputChange }) => (
|
|
192
|
+
<TableContainer className={styles.tableContainer}>
|
|
193
|
+
<TableToolbar
|
|
194
|
+
style={{
|
|
195
|
+
position: "static",
|
|
196
|
+
height: "3rem",
|
|
197
|
+
overflow: "visible",
|
|
198
|
+
backgroundColor: "color",
|
|
199
|
+
}}
|
|
200
|
+
>
|
|
201
|
+
<TableToolbarContent className={styles.toolbarContent}>
|
|
202
|
+
<Layer>
|
|
203
|
+
<TableToolbarSearch
|
|
204
|
+
className={styles.search}
|
|
205
|
+
onChange={onInputChange}
|
|
206
|
+
placeholder={t("searchThisList", "Search this list")}
|
|
207
|
+
size="sm"
|
|
208
|
+
/>
|
|
209
|
+
</Layer>
|
|
210
|
+
</TableToolbarContent>
|
|
211
|
+
</TableToolbar>
|
|
212
|
+
<Table {...getTableProps()} className={styles.activeVisitsTable}>
|
|
213
|
+
<TableHead>
|
|
214
|
+
<TableRow>
|
|
215
|
+
<TableExpandHeader />
|
|
216
|
+
{headers.map((header) => (
|
|
217
|
+
<TableHeader>
|
|
218
|
+
{header.header?.content ?? header.header}
|
|
219
|
+
</TableHeader>
|
|
220
|
+
))}
|
|
221
|
+
</TableRow>
|
|
222
|
+
</TableHead>
|
|
223
|
+
<TableBody>
|
|
224
|
+
{rows.map((row, index) => {
|
|
225
|
+
return (
|
|
226
|
+
<>
|
|
227
|
+
<TableExpandRow {...getRowProps({ row })} key={row.id}>
|
|
228
|
+
{row.cells.map((cell) => (
|
|
229
|
+
<TableCell key={cell.id}>
|
|
230
|
+
{cell.value?.content ?? cell.value}
|
|
231
|
+
</TableCell>
|
|
232
|
+
))}
|
|
233
|
+
</TableExpandRow>
|
|
234
|
+
|
|
235
|
+
{row.isExpanded ? (
|
|
236
|
+
<TableExpandedRow
|
|
237
|
+
className={styles.expandedLabQueueVisitRow}
|
|
238
|
+
colSpan={headers.length + 2}
|
|
239
|
+
>
|
|
240
|
+
<>
|
|
241
|
+
{/* <span>{tableRows[index]?.comment ?? ""}</span> */}
|
|
242
|
+
</>
|
|
243
|
+
</TableExpandedRow>
|
|
244
|
+
) : (
|
|
245
|
+
<TableExpandedRow
|
|
246
|
+
className={styles.hiddenRow}
|
|
247
|
+
colSpan={headers.length + 2}
|
|
248
|
+
/>
|
|
249
|
+
)}
|
|
250
|
+
</>
|
|
251
|
+
);
|
|
252
|
+
})}
|
|
253
|
+
</TableBody>
|
|
254
|
+
</Table>
|
|
255
|
+
<Pagination
|
|
256
|
+
forwardText="Next page"
|
|
257
|
+
backwardText="Previous page"
|
|
258
|
+
page={currentPage}
|
|
259
|
+
pageSize={currentPageSize}
|
|
260
|
+
pageSizes={pageSizes}
|
|
261
|
+
totalItems={patientQueueEntries?.length}
|
|
262
|
+
className={styles.pagination}
|
|
263
|
+
onChange={({ pageSize, page }) => {
|
|
264
|
+
if (pageSize !== currentPageSize) {
|
|
265
|
+
setPageSize(pageSize);
|
|
266
|
+
}
|
|
267
|
+
if (page !== currentPage) {
|
|
268
|
+
goTo(page);
|
|
269
|
+
}
|
|
270
|
+
}}
|
|
271
|
+
/>
|
|
272
|
+
</TableContainer>
|
|
273
|
+
)}
|
|
274
|
+
</DataTable>
|
|
275
|
+
{showOverlay && (
|
|
276
|
+
<AssignBedWorkSpace
|
|
277
|
+
patientDetails={selectedPatientDetails}
|
|
278
|
+
closePanel={() => setShowOverlay(false)}
|
|
279
|
+
queueStatus={status}
|
|
280
|
+
headerTitle={t(
|
|
281
|
+
"assignBedToPatient",
|
|
282
|
+
restrictWardAdministrationToLoginLocation === true
|
|
283
|
+
? `Assign Bed to Patient ${selectedPatientDetails.name} in the ${session?.sessionLocation?.display} Ward`
|
|
284
|
+
: `Assign Bed to Patient ${selectedPatientDetails.name}`
|
|
285
|
+
)}
|
|
286
|
+
/>
|
|
287
|
+
)}
|
|
288
|
+
</div>
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return (
|
|
293
|
+
<EmptyState
|
|
294
|
+
msg={t("noQueueItems", "No queue items to display")}
|
|
295
|
+
helper=""
|
|
296
|
+
/>
|
|
297
|
+
);
|
|
298
|
+
};
|
|
299
|
+
export default ActivePatientsTable;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import useSWRInfinite from "swr/infinite";
|
|
3
|
+
import dayjs from "dayjs";
|
|
4
|
+
import isToday from "dayjs/plugin/isToday";
|
|
5
|
+
import last from "lodash-es/last";
|
|
6
|
+
import {
|
|
7
|
+
openmrsFetch,
|
|
8
|
+
type Visit,
|
|
9
|
+
useSession,
|
|
10
|
+
type FetchResponse,
|
|
11
|
+
formatDatetime,
|
|
12
|
+
parseDate,
|
|
13
|
+
useConfig,
|
|
14
|
+
} from "@openmrs/esm-framework";
|
|
15
|
+
|
|
16
|
+
dayjs.extend(isToday);
|
|
17
|
+
|
|
18
|
+
export interface ActiveVisit {
|
|
19
|
+
age: string;
|
|
20
|
+
id: string;
|
|
21
|
+
idNumber: string;
|
|
22
|
+
gender: string;
|
|
23
|
+
location: string;
|
|
24
|
+
name: string;
|
|
25
|
+
patientUuid: string;
|
|
26
|
+
visitStartTime: string;
|
|
27
|
+
visitType: string;
|
|
28
|
+
visitUuid: string;
|
|
29
|
+
[identifier: string]: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface VisitResponse {
|
|
33
|
+
results: Array<Visit>;
|
|
34
|
+
links: Array<{ rel: "prev" | "next" }>;
|
|
35
|
+
totalCount: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function useActiveVisits() {
|
|
39
|
+
const session = useSession();
|
|
40
|
+
const config = useConfig();
|
|
41
|
+
const { inpatientVisitUuid } = useConfig();
|
|
42
|
+
const sessionLocation = session?.sessionLocation?.uuid;
|
|
43
|
+
|
|
44
|
+
const customRepresentation =
|
|
45
|
+
"custom:(uuid,patient:(uuid,identifiers:(identifier,uuid,identifierType:(name,uuid)),person:(age,display,gender,uuid,attributes:(value,attributeType:(uuid,display))))," +
|
|
46
|
+
"visitType:(uuid,name,display),location:(uuid,name,display),startDatetime,stopDatetime)";
|
|
47
|
+
|
|
48
|
+
const getUrl = (
|
|
49
|
+
pageIndex,
|
|
50
|
+
previousPageData: FetchResponse<VisitResponse>
|
|
51
|
+
) => {
|
|
52
|
+
if (
|
|
53
|
+
pageIndex &&
|
|
54
|
+
!previousPageData?.data?.links?.some((link) => link.rel === "next")
|
|
55
|
+
) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const url = `/ws/rest/v1/visit?v=${customRepresentation}&`;
|
|
60
|
+
const urlSearchParams = new URLSearchParams();
|
|
61
|
+
|
|
62
|
+
urlSearchParams.append("includeInactive", "false");
|
|
63
|
+
urlSearchParams.append("visitType", `${inpatientVisitUuid}`);
|
|
64
|
+
urlSearchParams.append("totalCount", "true");
|
|
65
|
+
//urlSearchParams.append("location", `${sessionLocation}`);
|
|
66
|
+
|
|
67
|
+
if (pageIndex) {
|
|
68
|
+
urlSearchParams.append("startIndex", `${pageIndex * 50}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return url + urlSearchParams.toString();
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const {
|
|
75
|
+
data,
|
|
76
|
+
error,
|
|
77
|
+
isLoading,
|
|
78
|
+
isValidating,
|
|
79
|
+
size: pageNumber,
|
|
80
|
+
setSize,
|
|
81
|
+
mutate,
|
|
82
|
+
} = useSWRInfinite<FetchResponse<VisitResponse>, Error>(
|
|
83
|
+
sessionLocation ? getUrl : null,
|
|
84
|
+
openmrsFetch
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
if (
|
|
89
|
+
data &&
|
|
90
|
+
data?.[pageNumber - 1]?.data?.links?.some((link) => link.rel === "next")
|
|
91
|
+
) {
|
|
92
|
+
setSize((currentSize) => currentSize + 1);
|
|
93
|
+
}
|
|
94
|
+
}, [data, pageNumber, setSize]);
|
|
95
|
+
|
|
96
|
+
const mapVisitProperties = (visit: Visit): ActiveVisit => {
|
|
97
|
+
// create base object
|
|
98
|
+
const activeVisits: ActiveVisit = {
|
|
99
|
+
age: visit?.patient?.person?.age,
|
|
100
|
+
id: visit.uuid,
|
|
101
|
+
idNumber: null,
|
|
102
|
+
gender: visit?.patient?.person?.gender,
|
|
103
|
+
location: visit?.location?.uuid,
|
|
104
|
+
name: visit?.patient?.person?.display,
|
|
105
|
+
patientUuid: visit?.patient?.uuid,
|
|
106
|
+
visitStartTime: formatDatetime(parseDate(visit?.startDatetime)),
|
|
107
|
+
visitType: visit?.visitType?.display,
|
|
108
|
+
visitUuid: visit.uuid,
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// in case no configuration is given the previous behavior remains the same
|
|
112
|
+
if (!config?.activeVisits?.identifiers) {
|
|
113
|
+
activeVisits.idNumber =
|
|
114
|
+
visit?.patient?.identifiers[0]?.identifier ?? "--";
|
|
115
|
+
} else {
|
|
116
|
+
// map identifiers on config
|
|
117
|
+
config?.activeVisits?.identifiers?.map((configIdentifier) => {
|
|
118
|
+
// check if in the current visit the patient has in his identifiers the current identifierType name
|
|
119
|
+
const visitIdentifier = visit?.patient?.identifiers.find(
|
|
120
|
+
(visitIdentifier) =>
|
|
121
|
+
visitIdentifier?.identifierType?.name ===
|
|
122
|
+
configIdentifier?.identifierName
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
// add the new identifier or rewrite existing one to activeVisit object
|
|
126
|
+
// the parameter will corresponds to the name of the key value of the configuration
|
|
127
|
+
// and the respective value is the visit identifier
|
|
128
|
+
// If there isn't a identifier we display this default text '--'
|
|
129
|
+
activeVisits[configIdentifier.header?.key] =
|
|
130
|
+
visitIdentifier?.identifier ?? "--";
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// map attributes on config
|
|
135
|
+
config?.activeVisits?.attributes?.map(({ display, header }) => {
|
|
136
|
+
// check if in the current visit the person has in his attributes the current display
|
|
137
|
+
const personAttributes = visit?.patient?.person?.attributes.find(
|
|
138
|
+
(personAttributes) =>
|
|
139
|
+
personAttributes?.attributeType?.display === display
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
// add the new attribute or rewrite existing one to activeVisit object
|
|
143
|
+
// the parameter will correspond to the name of the key value of the configuration
|
|
144
|
+
// and the respective value is the persons value
|
|
145
|
+
// If there isn't a attribute we display this default text '--'
|
|
146
|
+
activeVisits[header?.key] = personAttributes?.value ?? "--";
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
return activeVisits;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const formattedActiveVisits: Array<ActiveVisit> = data
|
|
153
|
+
? [].concat(
|
|
154
|
+
...data?.map((res) => res?.data?.results?.map(mapVisitProperties))
|
|
155
|
+
)
|
|
156
|
+
: [];
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
patientQueueEntries: formattedActiveVisits,
|
|
160
|
+
isError: error,
|
|
161
|
+
isLoading,
|
|
162
|
+
isValidating,
|
|
163
|
+
patientQueueCount: data?.[0]?.data?.totalCount ?? 0,
|
|
164
|
+
mutate,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export const getOriginFromPathName = (pathname = "") => {
|
|
169
|
+
const from = pathname.split("/");
|
|
170
|
+
return last(from);
|
|
171
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Button, Tooltip } from "@carbon/react";
|
|
3
|
+
import { HospitalBed } from "@carbon/react/icons";
|
|
4
|
+
import { useTranslation } from "react-i18next";
|
|
5
|
+
import styles from "./admission-action-button-styles.scss";
|
|
6
|
+
|
|
7
|
+
const AdmissionActionButton = ({
|
|
8
|
+
entry,
|
|
9
|
+
handleBedAssigmentModal,
|
|
10
|
+
buttonText,
|
|
11
|
+
}) => {
|
|
12
|
+
const { t } = useTranslation();
|
|
13
|
+
return (
|
|
14
|
+
<Tooltip align="bottom" label={t("buttonTooltip", buttonText)}>
|
|
15
|
+
<Button
|
|
16
|
+
className={styles.actionButton}
|
|
17
|
+
kind="ghost"
|
|
18
|
+
renderIcon={HospitalBed}
|
|
19
|
+
onClick={() => handleBedAssigmentModal(entry)}
|
|
20
|
+
iconDescription={t("buttonText", buttonText)}
|
|
21
|
+
></Button>
|
|
22
|
+
</Tooltip>
|
|
23
|
+
);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default AdmissionActionButton;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import Header from "../../header/header.component";
|
|
3
|
+
import styles from "./styles.scss";
|
|
4
|
+
import BedAdmissionTabs from "../bed-admission-tabs.component";
|
|
5
|
+
|
|
6
|
+
const ActivePatientsHome: React.FC = () => {
|
|
7
|
+
return (
|
|
8
|
+
<section className={styles.section}>
|
|
9
|
+
<Header route="In Patient" headerTitle="" />
|
|
10
|
+
<BedAdmissionTabs />
|
|
11
|
+
</section>
|
|
12
|
+
);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default ActivePatientsHome;
|