@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.
Files changed (102) hide show
  1. package/.turbo/turbo-build.log +36 -0
  2. package/README.md +8 -0
  3. package/dist/130.js +2 -0
  4. package/dist/130.js.LICENSE.txt +3 -0
  5. package/dist/130.js.map +1 -0
  6. package/dist/148.js +1 -0
  7. package/dist/148.js.map +1 -0
  8. package/dist/169.js +1 -0
  9. package/dist/169.js.map +1 -0
  10. package/dist/271.js +1 -0
  11. package/dist/319.js +1 -0
  12. package/dist/460.js +1 -0
  13. package/dist/501.js +1 -0
  14. package/dist/501.js.map +1 -0
  15. package/dist/542.js +1 -0
  16. package/dist/542.js.map +1 -0
  17. package/dist/574.js +1 -0
  18. package/dist/591.js +2 -0
  19. package/dist/591.js.LICENSE.txt +32 -0
  20. package/dist/591.js.map +1 -0
  21. package/dist/644.js +1 -0
  22. package/dist/668.js +2 -0
  23. package/dist/668.js.LICENSE.txt +15 -0
  24. package/dist/668.js.map +1 -0
  25. package/dist/729.js +1 -0
  26. package/dist/729.js.map +1 -0
  27. package/dist/757.js +1 -0
  28. package/dist/766.js +2 -0
  29. package/dist/766.js.LICENSE.txt +35 -0
  30. package/dist/766.js.map +1 -0
  31. package/dist/784.js +2 -0
  32. package/dist/784.js.LICENSE.txt +9 -0
  33. package/dist/784.js.map +1 -0
  34. package/dist/788.js +1 -0
  35. package/dist/805.js +1 -0
  36. package/dist/805.js.map +1 -0
  37. package/dist/807.js +1 -0
  38. package/dist/833.js +1 -0
  39. package/dist/main.js +1 -0
  40. package/dist/main.js.map +1 -0
  41. package/dist/openmrs-esm-bed-management-app.js +1 -0
  42. package/dist/openmrs-esm-bed-management-app.js.buildmanifest.json +579 -0
  43. package/dist/openmrs-esm-bed-management-app.js.map +1 -0
  44. package/dist/routes.json +1 -0
  45. package/jest.config.js +3 -0
  46. package/package.json +55 -0
  47. package/src/__mocks__/react-i18next.js +50 -0
  48. package/src/admin-card-link.component.tsx +24 -0
  49. package/src/bed-administration/bed-administration-form.component.tsx +292 -0
  50. package/src/bed-administration/bed-administration-form.scss +0 -0
  51. package/src/bed-administration/bed-administration-table.component.tsx +258 -0
  52. package/src/bed-administration/bed-administration-table.scss +112 -0
  53. package/src/bed-administration/bed-administration-types.ts +20 -0
  54. package/src/bed-administration/bed-administration.resource.ts +57 -0
  55. package/src/bed-administration/edit-bed-form.component.tsx +89 -0
  56. package/src/bed-administration/new-bed-form.component.tsx +96 -0
  57. package/src/bed-tag/bed-tag-administration-table.component.tsx +192 -0
  58. package/src/bed-tag/bed-tags-admin-form.component.tsx +127 -0
  59. package/src/bed-tag/edit-tag-form.component.tsx +72 -0
  60. package/src/bed-tag/new-tag-form.component.tsx +76 -0
  61. package/src/bed-type/bed-type-admin-form.component.tsx +166 -0
  62. package/src/bed-type/bed-type-administration-table.component.tsx +198 -0
  63. package/src/bed-type/edit-bed-type.component.tsx +72 -0
  64. package/src/bed-type/new-bed-type-form.component.tsx +80 -0
  65. package/src/card-header/card-header.component.tsx +20 -0
  66. package/src/card-header/card-header.scss +45 -0
  67. package/src/config-schema.ts +13 -0
  68. package/src/declarations.d.ts +7 -0
  69. package/src/empty-state/empty-state.component.tsx +54 -0
  70. package/src/empty-state/empty-state.scss +62 -0
  71. package/src/header/header.component.tsx +42 -0
  72. package/src/header/header.scss +72 -0
  73. package/src/header/illustration.component.tsx +13 -0
  74. package/src/home.component.tsx +15 -0
  75. package/src/home.scss +5 -0
  76. package/src/index.ts +51 -0
  77. package/src/left-panel/left-panel.component.tsx +23 -0
  78. package/src/left-panel/left-panel.scss +42 -0
  79. package/src/left-panel-link.component.tsx +39 -0
  80. package/src/root.component.tsx +39 -0
  81. package/src/root.scss +11 -0
  82. package/src/routes.json +56 -0
  83. package/src/setup-tests.ts +1 -0
  84. package/src/summary/summary.component.tsx +60 -0
  85. package/src/summary/summary.resource.ts +232 -0
  86. package/src/summary/summary.scss +74 -0
  87. package/src/types.ts +173 -0
  88. package/src/ward-card/ward-card.component.tsx +34 -0
  89. package/src/ward-card/ward-card.scss +51 -0
  90. package/src/ward-with-beds/ward-with-beds.component.tsx +167 -0
  91. package/src/ward-with-beds/ward-with-beds.scss +27 -0
  92. package/translations/am.json +52 -0
  93. package/translations/ar.json +52 -0
  94. package/translations/en.json +52 -0
  95. package/translations/es.json +52 -0
  96. package/translations/fr.json +52 -0
  97. package/translations/he.json +52 -0
  98. package/translations/km.json +52 -0
  99. package/translations/zh.json +52 -0
  100. package/translations/zh_CN.json +52 -0
  101. package/tsconfig.json +5 -0
  102. 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;
@@ -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;