@openmrs/esm-bed-management-app 7.0.3-pre.94
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/.turbo/turbo-build.log +36 -0
- package/README.md +8 -0
- package/dist/130.js +2 -0
- package/dist/130.js.LICENSE.txt +3 -0
- package/dist/130.js.map +1 -0
- package/dist/148.js +1 -0
- package/dist/148.js.map +1 -0
- package/dist/169.js +1 -0
- package/dist/169.js.map +1 -0
- package/dist/271.js +1 -0
- package/dist/319.js +1 -0
- package/dist/460.js +1 -0
- package/dist/501.js +1 -0
- package/dist/501.js.map +1 -0
- package/dist/542.js +1 -0
- package/dist/542.js.map +1 -0
- package/dist/574.js +1 -0
- package/dist/591.js +2 -0
- package/dist/591.js.LICENSE.txt +32 -0
- package/dist/591.js.map +1 -0
- package/dist/644.js +1 -0
- package/dist/668.js +2 -0
- package/dist/668.js.LICENSE.txt +15 -0
- package/dist/668.js.map +1 -0
- package/dist/729.js +1 -0
- package/dist/729.js.map +1 -0
- package/dist/757.js +1 -0
- package/dist/766.js +2 -0
- package/dist/766.js.LICENSE.txt +35 -0
- package/dist/766.js.map +1 -0
- package/dist/784.js +2 -0
- package/dist/784.js.LICENSE.txt +9 -0
- package/dist/784.js.map +1 -0
- package/dist/788.js +1 -0
- package/dist/805.js +1 -0
- package/dist/805.js.map +1 -0
- package/dist/807.js +1 -0
- package/dist/833.js +1 -0
- package/dist/main.js +1 -0
- package/dist/main.js.map +1 -0
- package/dist/openmrs-esm-bed-management-app.js +1 -0
- package/dist/openmrs-esm-bed-management-app.js.buildmanifest.json +579 -0
- package/dist/openmrs-esm-bed-management-app.js.map +1 -0
- package/dist/routes.json +1 -0
- package/jest.config.js +3 -0
- package/package.json +55 -0
- package/src/__mocks__/react-i18next.js +50 -0
- package/src/admin-card-link.component.tsx +24 -0
- package/src/bed-administration/bed-administration-form.component.tsx +292 -0
- package/src/bed-administration/bed-administration-form.scss +0 -0
- package/src/bed-administration/bed-administration-table.component.tsx +258 -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 +57 -0
- package/src/bed-administration/edit-bed-form.component.tsx +89 -0
- package/src/bed-administration/new-bed-form.component.tsx +96 -0
- package/src/bed-tag/bed-tag-administration-table.component.tsx +192 -0
- package/src/bed-tag/bed-tags-admin-form.component.tsx +127 -0
- package/src/bed-tag/edit-tag-form.component.tsx +72 -0
- package/src/bed-tag/new-tag-form.component.tsx +76 -0
- package/src/bed-type/bed-type-admin-form.component.tsx +166 -0
- package/src/bed-type/bed-type-administration-table.component.tsx +198 -0
- package/src/bed-type/edit-bed-type.component.tsx +72 -0
- package/src/bed-type/new-bed-type-form.component.tsx +80 -0
- package/src/card-header/card-header.component.tsx +20 -0
- package/src/card-header/card-header.scss +45 -0
- package/src/config-schema.ts +13 -0
- package/src/declarations.d.ts +7 -0
- package/src/empty-state/empty-state.component.tsx +54 -0
- package/src/empty-state/empty-state.scss +62 -0
- package/src/header/header.component.tsx +42 -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 +51 -0
- package/src/left-panel/left-panel.component.tsx +23 -0
- package/src/left-panel/left-panel.scss +42 -0
- package/src/left-panel-link.component.tsx +39 -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 +60 -0
- package/src/summary/summary.resource.ts +232 -0
- package/src/summary/summary.scss +74 -0
- package/src/types.ts +173 -0
- package/src/ward-card/ward-card.component.tsx +34 -0
- package/src/ward-card/ward-card.scss +51 -0
- package/src/ward-with-beds/ward-with-beds.component.tsx +167 -0
- package/src/ward-with-beds/ward-with-beds.scss +27 -0
- package/translations/am.json +52 -0
- package/translations/ar.json +52 -0
- package/translations/en.json +52 -0
- package/translations/es.json +52 -0
- package/translations/fr.json +52 -0
- package/translations/he.json +52 -0
- package/translations/km.json +52 -0
- package/translations/zh.json +52 -0
- package/translations/zh_CN.json +52 -0
- package/tsconfig.json +5 -0
- package/webpack.config.js +1 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** At present, this entire mock is boilerplate. */
|
|
2
|
+
import 'react';
|
|
3
|
+
import 'react-i18next';
|
|
4
|
+
|
|
5
|
+
const hasChildren = (node) => node && (node.children || (node.props && node.props.children));
|
|
6
|
+
|
|
7
|
+
const getChildren = (node) => (node && node.children ? node.children : node.props && node.props.children);
|
|
8
|
+
|
|
9
|
+
const renderNodes = (reactNodes) => {
|
|
10
|
+
if (typeof reactNodes === 'string') {
|
|
11
|
+
return reactNodes;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return Object.keys(reactNodes).map((key, i) => {
|
|
15
|
+
const child = reactNodes[key];
|
|
16
|
+
const isElement = React.isValidElement(child);
|
|
17
|
+
|
|
18
|
+
if (typeof child === 'string') {
|
|
19
|
+
return child;
|
|
20
|
+
}
|
|
21
|
+
if (hasChildren(child)) {
|
|
22
|
+
const inner = renderNodes(getChildren(child));
|
|
23
|
+
return React.cloneElement(child, { ...child.props, key: i }, inner);
|
|
24
|
+
}
|
|
25
|
+
if (typeof child === 'object' && !isElement) {
|
|
26
|
+
return Object.keys(child).reduce((str, childKey) => `${str}${child[childKey]}`, '');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return child;
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const useMock = [(k) => k, {}];
|
|
34
|
+
useMock.t = (k, o) => (o && o.defaultValue) || (typeof o === 'string' ? o : k);
|
|
35
|
+
useMock.i18n = {};
|
|
36
|
+
|
|
37
|
+
module.exports = {
|
|
38
|
+
// this mock makes sure any components using the translate HoC receive the t function as a prop
|
|
39
|
+
Trans: ({ children }) => renderNodes(children),
|
|
40
|
+
Translation: ({ children }) => children((k) => k, { i18n: {} }),
|
|
41
|
+
useTranslation: () => useMock,
|
|
42
|
+
|
|
43
|
+
// mock if needed
|
|
44
|
+
I18nextProvider: reactI18next.I18nextProvider,
|
|
45
|
+
initReactI18next: reactI18next.initReactI18next,
|
|
46
|
+
setDefaults: reactI18next.setDefaults,
|
|
47
|
+
getDefaults: reactI18next.getDefaults,
|
|
48
|
+
setI18n: reactI18next.setI18n,
|
|
49
|
+
getI18n: reactI18next.getI18n,
|
|
50
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { Layer, ClickableTile } from '@carbon/react';
|
|
4
|
+
import { ArrowRight } from '@carbon/react/icons';
|
|
5
|
+
|
|
6
|
+
const BedManagementAdminCardLink: React.FC = () => {
|
|
7
|
+
const { t } = useTranslation();
|
|
8
|
+
const header = t('manageBeds', 'Manage Beds');
|
|
9
|
+
return (
|
|
10
|
+
<Layer>
|
|
11
|
+
<ClickableTile href={window.getOpenmrsSpaBase() + 'bed-management'} rel="noopener noreferrer">
|
|
12
|
+
<div>
|
|
13
|
+
<div className="heading">{header}</div>
|
|
14
|
+
<div className="content">{t('bedManagement', 'Bed Management')}</div>
|
|
15
|
+
</div>
|
|
16
|
+
<div className="iconWrapper">
|
|
17
|
+
<ArrowRight size={16} />
|
|
18
|
+
</div>
|
|
19
|
+
</ClickableTile>
|
|
20
|
+
</Layer>
|
|
21
|
+
);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default BedManagementAdminCardLink;
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import capitalize from 'lodash-es/capitalize';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { useForm, Controller } from 'react-hook-form';
|
|
5
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
6
|
+
import {
|
|
7
|
+
Button,
|
|
8
|
+
ComboBox,
|
|
9
|
+
ComposedModal,
|
|
10
|
+
Form,
|
|
11
|
+
FormGroup,
|
|
12
|
+
ModalBody,
|
|
13
|
+
ModalFooter,
|
|
14
|
+
ModalHeader,
|
|
15
|
+
NumberInput,
|
|
16
|
+
Select,
|
|
17
|
+
SelectItem,
|
|
18
|
+
Stack,
|
|
19
|
+
TextArea,
|
|
20
|
+
TextInput,
|
|
21
|
+
InlineNotification,
|
|
22
|
+
} from '@carbon/react';
|
|
23
|
+
import { useTranslation } from 'react-i18next';
|
|
24
|
+
import { type Location } from '@openmrs/esm-framework';
|
|
25
|
+
import type { BedType, BedFormData } from '../types';
|
|
26
|
+
import { type BedAdministrationData } from './bed-administration-types';
|
|
27
|
+
|
|
28
|
+
const numberInString = z.string().transform((val, ctx) => {
|
|
29
|
+
const parsed = parseInt(val);
|
|
30
|
+
if (isNaN(parsed) || parsed < 1) {
|
|
31
|
+
ctx.addIssue({
|
|
32
|
+
code: z.ZodIssueCode.custom,
|
|
33
|
+
message: 'Please enter a valid number',
|
|
34
|
+
});
|
|
35
|
+
return z.NEVER;
|
|
36
|
+
}
|
|
37
|
+
return val;
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const BedAdministrationSchema = z.object({
|
|
41
|
+
bedId: z.string().max(255),
|
|
42
|
+
description: z.string().max(255),
|
|
43
|
+
bedRow: numberInString,
|
|
44
|
+
bedColumn: numberInString,
|
|
45
|
+
location: z
|
|
46
|
+
.object({ display: z.string(), uuid: z.string() })
|
|
47
|
+
.refine((value) => value.display != '', 'Please select a valid location'),
|
|
48
|
+
occupancyStatus: z.string().refine((value) => value != '', 'Please select a valid occupied status'),
|
|
49
|
+
bedType: z.string().refine((value) => value != '', 'Please select a valid bed type'),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
interface BedAdministrationFormProps {
|
|
53
|
+
showModal: boolean;
|
|
54
|
+
onModalChange: (showModal: boolean) => void;
|
|
55
|
+
availableBedTypes: Array<BedType>;
|
|
56
|
+
allLocations: Location[];
|
|
57
|
+
handleCreateQuestion?: (formData: BedAdministrationData) => void;
|
|
58
|
+
headerTitle: string;
|
|
59
|
+
occupancyStatuses: string[];
|
|
60
|
+
initialData: BedFormData;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface ErrorType {
|
|
64
|
+
message: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const BedAdministrationForm: React.FC<BedAdministrationFormProps> = ({
|
|
68
|
+
showModal,
|
|
69
|
+
onModalChange,
|
|
70
|
+
availableBedTypes,
|
|
71
|
+
allLocations,
|
|
72
|
+
handleCreateQuestion,
|
|
73
|
+
headerTitle,
|
|
74
|
+
occupancyStatuses,
|
|
75
|
+
initialData,
|
|
76
|
+
}) => {
|
|
77
|
+
const { t } = useTranslation();
|
|
78
|
+
const [occupancyStatus, setOccupancyStatus] = useState(capitalize(initialData.status));
|
|
79
|
+
const [selectedBedType] = useState(initialData.bedType?.name ?? '');
|
|
80
|
+
const [showErrorNotification, setShowErrorNotification] = useState(false);
|
|
81
|
+
const [formStateError, setFormStateError] = useState('');
|
|
82
|
+
|
|
83
|
+
const filterLocationNames = (location) => {
|
|
84
|
+
return location.item.display?.toLowerCase().includes(location?.inputValue?.toLowerCase()) ?? [];
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const {
|
|
88
|
+
handleSubmit,
|
|
89
|
+
control,
|
|
90
|
+
formState: { isDirty },
|
|
91
|
+
} = useForm<BedAdministrationData>({
|
|
92
|
+
mode: 'all',
|
|
93
|
+
resolver: zodResolver(BedAdministrationSchema),
|
|
94
|
+
defaultValues: {
|
|
95
|
+
bedId: initialData.bedNumber ?? '',
|
|
96
|
+
description: initialData.bedType?.description ?? '',
|
|
97
|
+
bedRow: initialData.row.toString() ?? '0',
|
|
98
|
+
bedColumn: initialData.column.toString() ?? '0',
|
|
99
|
+
location: initialData.location ?? {},
|
|
100
|
+
occupancyStatus: capitalize(initialData.status) ?? occupancyStatus,
|
|
101
|
+
bedType: initialData.bedType?.name ?? '',
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const onSubmit = (formData: BedAdministrationData) => {
|
|
106
|
+
const result = BedAdministrationSchema.safeParse(formData);
|
|
107
|
+
if (result.success) {
|
|
108
|
+
setShowErrorNotification(false);
|
|
109
|
+
handleCreateQuestion(formData);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const onError = (error: { [key: string]: ErrorType }) => {
|
|
114
|
+
setFormStateError(Object.entries(error)[0][1].message);
|
|
115
|
+
setShowErrorNotification(true);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<ComposedModal open={showModal} onClose={() => onModalChange(false)} preventCloseOnClickOutside>
|
|
120
|
+
<ModalHeader title={headerTitle} />
|
|
121
|
+
<Form onSubmit={handleSubmit(onSubmit, onError)}>
|
|
122
|
+
<ModalBody hasScrollingContent>
|
|
123
|
+
<Stack gap={3}>
|
|
124
|
+
<FormGroup legendText={''}>
|
|
125
|
+
<Controller
|
|
126
|
+
name="bedId"
|
|
127
|
+
control={control}
|
|
128
|
+
render={({ field, fieldState }) => (
|
|
129
|
+
<>
|
|
130
|
+
<TextInput
|
|
131
|
+
id="bedId"
|
|
132
|
+
labelText={t('bedId', 'Bed number')}
|
|
133
|
+
placeholder={t('bedIdPlaceholder', 'e.g. BMW-201')}
|
|
134
|
+
invalidText={fieldState.error?.message}
|
|
135
|
+
{...field}
|
|
136
|
+
/>
|
|
137
|
+
</>
|
|
138
|
+
)}
|
|
139
|
+
/>
|
|
140
|
+
</FormGroup>
|
|
141
|
+
|
|
142
|
+
<FormGroup>
|
|
143
|
+
<Controller
|
|
144
|
+
name="description"
|
|
145
|
+
control={control}
|
|
146
|
+
render={({ field, fieldState }) => (
|
|
147
|
+
<>
|
|
148
|
+
<TextArea
|
|
149
|
+
rows={2}
|
|
150
|
+
id="description"
|
|
151
|
+
invalidText={fieldState?.error?.message}
|
|
152
|
+
labelText={t('description', 'Bed description')}
|
|
153
|
+
{...field}
|
|
154
|
+
placeholder={t('description', 'Enter the bed description')}
|
|
155
|
+
/>
|
|
156
|
+
</>
|
|
157
|
+
)}
|
|
158
|
+
/>
|
|
159
|
+
</FormGroup>
|
|
160
|
+
|
|
161
|
+
<FormGroup>
|
|
162
|
+
<Controller
|
|
163
|
+
name="bedRow"
|
|
164
|
+
control={control}
|
|
165
|
+
render={({ fieldState, field }) => (
|
|
166
|
+
<NumberInput
|
|
167
|
+
hideSteppers
|
|
168
|
+
id="bedRow"
|
|
169
|
+
label="Bed row"
|
|
170
|
+
labelText="Bed row"
|
|
171
|
+
invalidText={fieldState?.error?.message}
|
|
172
|
+
{...field}
|
|
173
|
+
/>
|
|
174
|
+
)}
|
|
175
|
+
/>
|
|
176
|
+
</FormGroup>
|
|
177
|
+
|
|
178
|
+
<FormGroup>
|
|
179
|
+
<Controller
|
|
180
|
+
name="bedColumn"
|
|
181
|
+
control={control}
|
|
182
|
+
render={({ field, fieldState }) => (
|
|
183
|
+
<NumberInput
|
|
184
|
+
hideSteppers
|
|
185
|
+
id="bedColumn"
|
|
186
|
+
label="Bed column"
|
|
187
|
+
labelText="Bed column"
|
|
188
|
+
invalidText={fieldState.error?.message}
|
|
189
|
+
{...field}
|
|
190
|
+
/>
|
|
191
|
+
)}
|
|
192
|
+
/>
|
|
193
|
+
</FormGroup>
|
|
194
|
+
|
|
195
|
+
<FormGroup>
|
|
196
|
+
<Controller
|
|
197
|
+
name="location"
|
|
198
|
+
control={control}
|
|
199
|
+
render={({ fieldState, field: { onChange, onBlur, value, ref } }) => (
|
|
200
|
+
<ComboBox
|
|
201
|
+
aria-label={t('location', 'Location')}
|
|
202
|
+
shouldFilterItem={filterLocationNames}
|
|
203
|
+
id="location"
|
|
204
|
+
label={t('location', 'Location')}
|
|
205
|
+
invalidText={fieldState?.error?.message}
|
|
206
|
+
items={allLocations}
|
|
207
|
+
onBlur={onBlur}
|
|
208
|
+
ref={ref}
|
|
209
|
+
selectedItem={value}
|
|
210
|
+
onChange={({ selectedItem }) => onChange(selectedItem)}
|
|
211
|
+
itemToString={(location) => location?.display ?? ''}
|
|
212
|
+
placeholder={t('selectBedLocation', 'Select a bed location')}
|
|
213
|
+
titleText={t('bedLocation', 'Location')}
|
|
214
|
+
/>
|
|
215
|
+
)}
|
|
216
|
+
/>
|
|
217
|
+
</FormGroup>
|
|
218
|
+
|
|
219
|
+
<FormGroup>
|
|
220
|
+
<Controller
|
|
221
|
+
name="occupancyStatus"
|
|
222
|
+
control={control}
|
|
223
|
+
render={({ field, fieldState }) => (
|
|
224
|
+
<Select
|
|
225
|
+
id="occupancyStatus"
|
|
226
|
+
labelText={t('occupancyStatus', 'Occupied Status')}
|
|
227
|
+
invalidText={fieldState.error?.message}
|
|
228
|
+
defaultValue={occupancyStatus}
|
|
229
|
+
onChange={(event) => setOccupancyStatus(event.target.value)}
|
|
230
|
+
value={occupancyStatus}
|
|
231
|
+
{...field}>
|
|
232
|
+
<SelectItem text={t('chooseOccupiedStatus', 'Choose occupied status')} value="" />
|
|
233
|
+
{occupancyStatuses.map((occupancyStatus, index) => (
|
|
234
|
+
<SelectItem
|
|
235
|
+
text={t('occupancyStatus', `${occupancyStatus}`)}
|
|
236
|
+
value={t('occupancyStatus', `${occupancyStatus}`)}
|
|
237
|
+
key={`occupancyStatus-${index}`}
|
|
238
|
+
/>
|
|
239
|
+
))}
|
|
240
|
+
</Select>
|
|
241
|
+
)}
|
|
242
|
+
/>
|
|
243
|
+
</FormGroup>
|
|
244
|
+
|
|
245
|
+
<FormGroup>
|
|
246
|
+
<Controller
|
|
247
|
+
name="bedType"
|
|
248
|
+
control={control}
|
|
249
|
+
render={({ field }) => (
|
|
250
|
+
<Select
|
|
251
|
+
id="bedType"
|
|
252
|
+
labelText={t('bedType', 'Bed type')}
|
|
253
|
+
invalidText={t('required', 'Required')}
|
|
254
|
+
defaultValue={selectedBedType}
|
|
255
|
+
{...field}>
|
|
256
|
+
<SelectItem text={t('chooseBedtype', 'Choose a bed type')} />
|
|
257
|
+
{availableBedTypes.map((bedType, index) => (
|
|
258
|
+
<SelectItem text={bedType.name} value={bedType.name} key={`bedType-${index}`}>
|
|
259
|
+
{bedType.name}
|
|
260
|
+
</SelectItem>
|
|
261
|
+
))}
|
|
262
|
+
</Select>
|
|
263
|
+
)}
|
|
264
|
+
/>
|
|
265
|
+
</FormGroup>
|
|
266
|
+
{showErrorNotification && (
|
|
267
|
+
<InlineNotification
|
|
268
|
+
lowContrast
|
|
269
|
+
title={t('error', 'Error')}
|
|
270
|
+
style={{ minWidth: '100%', margin: '0', padding: '0' }}
|
|
271
|
+
role="alert"
|
|
272
|
+
kind="error"
|
|
273
|
+
subtitle={t('pleaseFillField', formStateError) + '.'}
|
|
274
|
+
onClose={() => setShowErrorNotification(false)}
|
|
275
|
+
/>
|
|
276
|
+
)}
|
|
277
|
+
</Stack>
|
|
278
|
+
</ModalBody>
|
|
279
|
+
<ModalFooter>
|
|
280
|
+
<Button onClick={() => onModalChange(false)} kind="secondary">
|
|
281
|
+
{t('cancel', 'Cancel')}
|
|
282
|
+
</Button>
|
|
283
|
+
<Button disabled={!isDirty} type="submit">
|
|
284
|
+
<span>{t('save', 'Save')}</span>
|
|
285
|
+
</Button>
|
|
286
|
+
</ModalFooter>
|
|
287
|
+
</Form>
|
|
288
|
+
</ComposedModal>
|
|
289
|
+
);
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
export default BedAdministrationForm;
|
|
File without changes
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import {
|
|
4
|
+
Button,
|
|
5
|
+
DataTable,
|
|
6
|
+
DataTableSkeleton,
|
|
7
|
+
Dropdown,
|
|
8
|
+
InlineLoading,
|
|
9
|
+
Pagination,
|
|
10
|
+
Table,
|
|
11
|
+
TableBody,
|
|
12
|
+
TableCell,
|
|
13
|
+
TableContainer,
|
|
14
|
+
TableHead,
|
|
15
|
+
TableHeader,
|
|
16
|
+
TableRow,
|
|
17
|
+
Tag,
|
|
18
|
+
Tile,
|
|
19
|
+
} from '@carbon/react';
|
|
20
|
+
import { Add, Edit } from '@carbon/react/icons';
|
|
21
|
+
import { ErrorState, isDesktop as desktopLayout, useLayoutType, usePagination } from '@openmrs/esm-framework';
|
|
22
|
+
import type { BedFormData } from '../types';
|
|
23
|
+
import { useBedsGroupedByLocation } from '../summary/summary.resource';
|
|
24
|
+
import EditBedForm from './edit-bed-form.component';
|
|
25
|
+
import Header from '../header/header.component';
|
|
26
|
+
import NewBedForm from './new-bed-form.component';
|
|
27
|
+
import styles from './bed-administration-table.scss';
|
|
28
|
+
import { CardHeader } from '../card-header/card-header.component';
|
|
29
|
+
|
|
30
|
+
const BedAdministrationTable: React.FC = () => {
|
|
31
|
+
const { t } = useTranslation();
|
|
32
|
+
const headerTitle = t('wardAllocation', 'Ward Allocation');
|
|
33
|
+
const layout = useLayoutType();
|
|
34
|
+
const isTablet = layout === 'tablet';
|
|
35
|
+
const responsiveSize = isTablet ? 'lg' : 'sm';
|
|
36
|
+
const isDesktop = desktopLayout(layout);
|
|
37
|
+
|
|
38
|
+
const {
|
|
39
|
+
data: wardsGroupedByLocations,
|
|
40
|
+
isLoading: isLoadingBeds,
|
|
41
|
+
isValidating,
|
|
42
|
+
mutate,
|
|
43
|
+
error,
|
|
44
|
+
} = useBedsGroupedByLocation();
|
|
45
|
+
const [showAddBedModal, setShowAddBedModal] = useState(false);
|
|
46
|
+
const [showEditBedModal, setShowEditBedModal] = useState(false);
|
|
47
|
+
const [editData, setEditData] = useState<BedFormData>();
|
|
48
|
+
const [filterOption, setFilterOption] = useState('ALL');
|
|
49
|
+
|
|
50
|
+
function CustomTag({ condition }: { condition: boolean }) {
|
|
51
|
+
const { t } = useTranslation();
|
|
52
|
+
|
|
53
|
+
if (condition) {
|
|
54
|
+
return (
|
|
55
|
+
<Tag type="green" size="md">
|
|
56
|
+
{t('yes', 'Yes')}
|
|
57
|
+
</Tag>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<Tag type="red" size="md">
|
|
63
|
+
{t('no', 'No')}
|
|
64
|
+
</Tag>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const handleBedStatusChange = ({ selectedItem }: { selectedItem: string }) =>
|
|
69
|
+
setFilterOption(selectedItem.trim().toUpperCase());
|
|
70
|
+
|
|
71
|
+
const [pageSize, setPageSize] = useState(10);
|
|
72
|
+
const { results, currentPage, totalPages, goTo } = usePagination(
|
|
73
|
+
filterOption === 'ALL'
|
|
74
|
+
? wardsGroupedByLocations
|
|
75
|
+
: wardsGroupedByLocations.filter((bed) => bed.status === filterOption) ?? [],
|
|
76
|
+
pageSize,
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const tableHeaders = [
|
|
80
|
+
{
|
|
81
|
+
key: 'bedNumber',
|
|
82
|
+
header: t('bedId', 'Bed ID'),
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
key: 'location',
|
|
86
|
+
header: t('location', 'Location'),
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
key: 'occupancyStatus',
|
|
90
|
+
header: t('occupancyStatus', 'Occupied'),
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
key: 'allocationStatus',
|
|
94
|
+
header: t('allocationStatus', 'Allocated'),
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
key: 'actions',
|
|
98
|
+
header: t('actions', 'Actions'),
|
|
99
|
+
},
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
const tableRows = useMemo(() => {
|
|
103
|
+
return results.flat().map((bed) => ({
|
|
104
|
+
id: bed.uuid,
|
|
105
|
+
bedNumber: bed.bedNumber,
|
|
106
|
+
location: bed.location.display,
|
|
107
|
+
occupancyStatus: <CustomTag condition={bed?.status === 'OCCUPIED'} />,
|
|
108
|
+
allocationStatus: <CustomTag condition={Boolean(bed.location?.uuid)} />,
|
|
109
|
+
actions: (
|
|
110
|
+
<>
|
|
111
|
+
<Button
|
|
112
|
+
enterDelayMs={300}
|
|
113
|
+
renderIcon={Edit}
|
|
114
|
+
onClick={(e) => {
|
|
115
|
+
e.preventDefault();
|
|
116
|
+
setEditData(bed);
|
|
117
|
+
setShowEditBedModal(true);
|
|
118
|
+
setShowAddBedModal(false);
|
|
119
|
+
}}
|
|
120
|
+
kind={'ghost'}
|
|
121
|
+
iconDescription={t('editBed', 'Edit Bed')}
|
|
122
|
+
hasIconOnly
|
|
123
|
+
size={responsiveSize}
|
|
124
|
+
tooltipAlignment="start"
|
|
125
|
+
/>
|
|
126
|
+
</>
|
|
127
|
+
),
|
|
128
|
+
}));
|
|
129
|
+
}, [responsiveSize, results, t]);
|
|
130
|
+
|
|
131
|
+
if (isLoadingBeds && !wardsGroupedByLocations.length) {
|
|
132
|
+
return (
|
|
133
|
+
<>
|
|
134
|
+
<Header route="Ward Allocation" />
|
|
135
|
+
<div className={styles.widgetCard}>
|
|
136
|
+
<DataTableSkeleton role="progressbar" compact={isDesktop} zebra />
|
|
137
|
+
</div>
|
|
138
|
+
</>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (error) {
|
|
143
|
+
return (
|
|
144
|
+
<>
|
|
145
|
+
<Header route="Ward Allocation" />
|
|
146
|
+
<div className={styles.widgetCard}>
|
|
147
|
+
<ErrorState error={error} headerTitle={headerTitle} />
|
|
148
|
+
</div>
|
|
149
|
+
</>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return (
|
|
154
|
+
<>
|
|
155
|
+
<Header route="Ward Allocation" />
|
|
156
|
+
<div className={styles.flexContainer}>
|
|
157
|
+
{results?.length ? (
|
|
158
|
+
<div className={styles.filterContainer}>
|
|
159
|
+
<Dropdown
|
|
160
|
+
id="occupancyStatus"
|
|
161
|
+
initialSelectedItem={'All'}
|
|
162
|
+
label=""
|
|
163
|
+
titleText={t('filterByOccupancyStatus', 'Filter by occupancy status') + ':'}
|
|
164
|
+
type="inline"
|
|
165
|
+
items={['All', 'Available', 'Occupied']}
|
|
166
|
+
onChange={handleBedStatusChange}
|
|
167
|
+
/>
|
|
168
|
+
</div>
|
|
169
|
+
) : null}
|
|
170
|
+
</div>
|
|
171
|
+
<div className={styles.widgetCard}>
|
|
172
|
+
{showAddBedModal ? (
|
|
173
|
+
<NewBedForm onModalChange={setShowAddBedModal} showModal={showAddBedModal} mutate={mutate} />
|
|
174
|
+
) : null}
|
|
175
|
+
{showEditBedModal ? (
|
|
176
|
+
<EditBedForm
|
|
177
|
+
onModalChange={setShowEditBedModal}
|
|
178
|
+
showModal={showEditBedModal}
|
|
179
|
+
editData={editData}
|
|
180
|
+
mutate={mutate}
|
|
181
|
+
/>
|
|
182
|
+
) : null}
|
|
183
|
+
<CardHeader title={headerTitle}>
|
|
184
|
+
<span className={styles.backgroundDataFetchingIndicator}>
|
|
185
|
+
<span>{isValidating ? <InlineLoading /> : null}</span>
|
|
186
|
+
</span>
|
|
187
|
+
{results?.length ? (
|
|
188
|
+
<Button
|
|
189
|
+
kind="ghost"
|
|
190
|
+
renderIcon={(props) => <Add size={16} {...props} />}
|
|
191
|
+
onClick={() => setShowAddBedModal(true)}>
|
|
192
|
+
{t('addBed', 'Add bed')}
|
|
193
|
+
</Button>
|
|
194
|
+
) : null}
|
|
195
|
+
</CardHeader>
|
|
196
|
+
<DataTable rows={tableRows} headers={tableHeaders} isSortable size={isTablet ? 'lg' : 'sm'} useZebraStyles>
|
|
197
|
+
{({ rows, headers, getTableProps }) => (
|
|
198
|
+
<TableContainer>
|
|
199
|
+
<Table {...getTableProps()}>
|
|
200
|
+
<TableHead>
|
|
201
|
+
<TableRow>
|
|
202
|
+
{headers.map((header) => (
|
|
203
|
+
<TableHeader>{header.header?.content ?? header.header}</TableHeader>
|
|
204
|
+
))}
|
|
205
|
+
</TableRow>
|
|
206
|
+
</TableHead>
|
|
207
|
+
<TableBody>
|
|
208
|
+
{rows.map((row) => (
|
|
209
|
+
<TableRow key={row.id}>
|
|
210
|
+
{row.cells.map((cell) => (
|
|
211
|
+
<TableCell key={cell.id}>{cell.value?.content ?? cell.value}</TableCell>
|
|
212
|
+
))}
|
|
213
|
+
</TableRow>
|
|
214
|
+
))}
|
|
215
|
+
</TableBody>
|
|
216
|
+
</Table>
|
|
217
|
+
{rows.length === 0 ? (
|
|
218
|
+
<div className={styles.tileContainer}>
|
|
219
|
+
<Tile className={styles.tile}>
|
|
220
|
+
<div className={styles.tileContent}>
|
|
221
|
+
<p className={styles.content}>{t('No data', 'No data to display')}</p>
|
|
222
|
+
<p className={styles.helper}>{t('checkFilters', 'Check the filters above')}</p>
|
|
223
|
+
</div>
|
|
224
|
+
<p className={styles.separator}>{t('or', 'or')}</p>
|
|
225
|
+
<Button
|
|
226
|
+
kind="ghost"
|
|
227
|
+
size="sm"
|
|
228
|
+
renderIcon={(props) => <Add size={16} {...props} />}
|
|
229
|
+
onClick={() => setShowAddBedModal(true)}>
|
|
230
|
+
{t('addBed', 'Add bed')}
|
|
231
|
+
</Button>
|
|
232
|
+
</Tile>
|
|
233
|
+
</div>
|
|
234
|
+
) : null}
|
|
235
|
+
<Pagination
|
|
236
|
+
backwardText="Previous page"
|
|
237
|
+
forwardText="Next page"
|
|
238
|
+
page={currentPage}
|
|
239
|
+
pageNumberText="Page Number"
|
|
240
|
+
pageSize={totalPages}
|
|
241
|
+
pageSizes={[10, 20, 30, 40, 50]}
|
|
242
|
+
totalItems={wardsGroupedByLocations.length}
|
|
243
|
+
onChange={({ pageSize, page }) => {
|
|
244
|
+
setPageSize(pageSize);
|
|
245
|
+
if (page !== currentPage) {
|
|
246
|
+
goTo(page);
|
|
247
|
+
}
|
|
248
|
+
}}
|
|
249
|
+
/>
|
|
250
|
+
</TableContainer>
|
|
251
|
+
)}
|
|
252
|
+
</DataTable>
|
|
253
|
+
</div>
|
|
254
|
+
</>
|
|
255
|
+
);
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
export default BedAdministrationTable;
|