@kenyaemr/esm-bed-management-app 1.0.1-pre.4 → 1.0.1-pre.43

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 (108) hide show
  1. package/dist/26.js +1 -1
  2. package/dist/26.js.map +1 -1
  3. package/dist/294.js +1 -1
  4. package/dist/294.js.map +1 -1
  5. package/dist/360.js +1 -0
  6. package/dist/360.js.map +1 -0
  7. package/dist/452.js +1 -0
  8. package/dist/{884.js.map → 452.js.map} +1 -1
  9. package/dist/482.js +2 -0
  10. package/dist/482.js.LICENSE.txt +19 -0
  11. package/dist/482.js.map +1 -0
  12. package/dist/532.js +1 -0
  13. package/dist/532.js.map +1 -0
  14. package/dist/558.js +1 -1
  15. package/dist/558.js.map +1 -1
  16. package/dist/574.js +1 -1
  17. package/dist/663.js +2 -0
  18. package/dist/663.js.LICENSE.txt +44 -0
  19. package/dist/663.js.map +1 -0
  20. package/dist/666.js +1 -0
  21. package/dist/666.js.map +1 -0
  22. package/dist/708.js +2 -0
  23. package/dist/{330.js.LICENSE.txt → 708.js.LICENSE.txt} +0 -20
  24. package/dist/708.js.map +1 -0
  25. package/dist/763.js +2 -0
  26. package/dist/763.js.map +1 -0
  27. package/dist/774.js +1 -0
  28. package/dist/774.js.map +1 -0
  29. package/dist/815.js +1 -0
  30. package/dist/815.js.map +1 -0
  31. package/dist/847.js +1 -0
  32. package/dist/847.js.map +1 -0
  33. package/dist/898.js +1 -0
  34. package/dist/898.js.map +1 -0
  35. package/dist/931.js +1 -0
  36. package/dist/931.js.map +1 -0
  37. package/dist/935.js +1 -0
  38. package/dist/935.js.map +1 -0
  39. package/dist/esm-kenyaemr-bed-management-app.js +1 -1
  40. package/dist/esm-kenyaemr-bed-management-app.js.buildmanifest.json +207 -135
  41. package/dist/esm-kenyaemr-bed-management-app.js.map +1 -1
  42. package/dist/main.js +1 -1
  43. package/dist/main.js.map +1 -1
  44. package/dist/routes.json +1 -1
  45. package/package.json +4 -1
  46. package/src/bed-administration/bed-administration-table.scss +3 -0
  47. package/src/bed-administration/bed-administration-types.ts +8 -0
  48. package/src/bed-admission/active-patients/active-patients-table.component.tsx +13 -20
  49. package/src/bed-admission/active-patients/active-visits.resource.ts +1 -3
  50. package/src/bed-admission/active-patients/eligible-admissions.resource.ts +43 -0
  51. package/src/bed-admission/admitted-patients/active-admissions.resource.ts +5 -1
  52. package/src/bed-admission/admitted-patients/admitted-patients-table.component.tsx +3 -3
  53. package/src/bed-admission/bed-admission-tabs.component.tsx +0 -1
  54. package/src/bed-admission/bed-admission.resource.ts +1 -1
  55. package/src/bed-admission/bed-layout/bed-layout-list.component.tsx +2 -2
  56. package/src/bed-admission/bed-tag/bed-tag-administration-table.component.tsx +240 -0
  57. package/src/bed-admission/bed-tag/bed-tags-admin-form.component.tsx +146 -0
  58. package/src/bed-admission/bed-tag/delete-bed-tag.component.tsx +69 -0
  59. package/src/bed-admission/bed-tag/delete-bedForm.component.tsx +108 -0
  60. package/src/bed-admission/bed-tag/edit-tag-form.component.tsx +80 -0
  61. package/src/bed-admission/bed-tag/new-tag-form.component.tsx +83 -0
  62. package/src/bed-admission/bed-type/bed-type-admin-form.component.tsx +203 -0
  63. package/src/bed-admission/bed-type/bed-type-administration-table.component.tsx +246 -0
  64. package/src/bed-admission/bed-type/delete-bed-type.component.tsx +69 -0
  65. package/src/bed-admission/bed-type/deleteBedtypeForm.component.tsx +108 -0
  66. package/src/bed-admission/bed-type/edit-bed-type.component.tsx +80 -0
  67. package/src/bed-admission/bed-type/new-bed-type-form.component.tsx +87 -0
  68. package/src/bed-admission/types.ts +1 -3
  69. package/src/bed-admission/ward/new-ward-form.component.tsx +81 -0
  70. package/src/bed-admission/ward/ward-admin-form.component.tsx +193 -0
  71. package/src/bed-admission/ward/ward-administration-table.component.tsx +173 -0
  72. package/src/config-schema.ts +7 -1
  73. package/src/index.ts +21 -0
  74. package/src/root.component.tsx +6 -0
  75. package/src/routes.json +16 -1
  76. package/src/summary/summary.resource.ts +257 -1
  77. package/src/types.ts +23 -0
  78. package/src/workspace/allocate-bed-workspace.component.tsx +51 -18
  79. package/src/workspace/allocate-bed.scss +7 -0
  80. package/tsconfig.json +2 -1
  81. package/dist/187.js +0 -1
  82. package/dist/187.js.map +0 -1
  83. package/dist/207.js +0 -1
  84. package/dist/207.js.map +0 -1
  85. package/dist/283.js +0 -1
  86. package/dist/283.js.map +0 -1
  87. package/dist/330.js +0 -2
  88. package/dist/330.js.map +0 -1
  89. package/dist/352.js +0 -1
  90. package/dist/352.js.map +0 -1
  91. package/dist/404.js +0 -1
  92. package/dist/404.js.map +0 -1
  93. package/dist/455.js +0 -2
  94. package/dist/455.js.map +0 -1
  95. package/dist/629.js +0 -1
  96. package/dist/629.js.map +0 -1
  97. package/dist/707.js +0 -1
  98. package/dist/707.js.map +0 -1
  99. package/dist/800.js +0 -2
  100. package/dist/800.js.LICENSE.txt +0 -3
  101. package/dist/800.js.map +0 -1
  102. package/dist/884.js +0 -1
  103. package/dist/933.js +0 -1
  104. package/dist/933.js.map +0 -1
  105. package/dist/959.js +0 -1
  106. package/dist/959.js.map +0 -1
  107. /package/.yarn/versions/{6816f0d4.yml → 739e37b3.yml} +0 -0
  108. /package/dist/{455.js.LICENSE.txt → 763.js.LICENSE.txt} +0 -0
@@ -10,3 +10,11 @@ export interface BedAdministrationData {
10
10
  occupancyStatus: string;
11
11
  bedType: string;
12
12
  }
13
+ export interface BedTypeDataAdministration {
14
+ name: string;
15
+ displayName: string;
16
+ description: string;
17
+ }
18
+ export interface BedTagDataAdministration {
19
+ name: string;
20
+ }
@@ -29,21 +29,13 @@ import {
29
29
  } from "@openmrs/esm-framework";
30
30
  import React, { useCallback, useMemo, useState } from "react";
31
31
  import { useTranslation } from "react-i18next";
32
- import {
33
- formatWaitTime,
34
- getOriginFromPathName,
35
- getTagColor,
36
- getTagType,
37
- trimVisitNumber,
38
- } from "../helpers/functions";
32
+ import { getOriginFromPathName } from "../helpers/functions";
39
33
  import styles from "./styles.scss";
40
- import { usePatientQueuesList } from "./patient-queues.resource";
41
- import { useActiveVisits } from "./active-visits.resource";
42
34
  import EmptyState from "../../empty-state/empty-state.component";
43
35
  import AssignBedWorkSpace from "../../workspace/allocate-bed-workspace.component";
44
36
  import AdmissionActionButton from "./admission-action-button.component";
45
37
  import { patientDetailsProps } from "../types";
46
- import ViewActionsMenu from "./view-action-menu.component";
38
+ import { useEligibleAdmissions } from "./eligible-admissions.resource";
47
39
 
48
40
  interface ActiveVisitsTableProps {
49
41
  status: string;
@@ -66,15 +58,15 @@ const ActivePatientsTable: React.FC<ActiveVisitsTableProps> = ({
66
58
 
67
59
  const layout = useLayoutType();
68
60
 
69
- const { patientQueueEntries, isLoading } = useActiveVisits();
70
61
  const { restrictWardAdministrationToLoginLocation } = useConfig();
71
62
 
63
+ const { patientEntries, isLoading } = useEligibleAdmissions();
72
64
  const handleBedAssigmentModal = useCallback(
73
65
  (entry) => {
74
66
  setSelectedPatientDetails({
75
67
  name: entry.name,
76
68
  patientUuid: entry.patientUuid,
77
- encounter: entry.encounter,
69
+ encounter: entry.admissionEncounterUuid,
78
70
  locationUuid: session?.sessionLocation?.uuid,
79
71
  locationTo: entry.locationTo,
80
72
  locationFrom: entry.locationFrom,
@@ -108,7 +100,7 @@ const ActivePatientsTable: React.FC<ActiveVisitsTableProps> = ({
108
100
  goTo,
109
101
  results: paginatedQueueEntries,
110
102
  currentPage,
111
- } = usePagination(patientQueueEntries, currentPageSize);
103
+ } = usePagination(patientEntries, currentPageSize);
112
104
 
113
105
  const tableHeaders = useMemo(
114
106
  () => [
@@ -119,7 +111,7 @@ const ActivePatientsTable: React.FC<ActiveVisitsTableProps> = ({
119
111
  },
120
112
  {
121
113
  id: 1,
122
- header: t("idNumber", "ID Number"),
114
+ header: t("idNumber", "Identifier"),
123
115
  key: "idNumber",
124
116
  },
125
117
  {
@@ -153,6 +145,7 @@ const ActivePatientsTable: React.FC<ActiveVisitsTableProps> = ({
153
145
 
154
146
  const tableRows = useMemo(() => {
155
147
  return paginatedQueueEntries?.map((entry) => ({
148
+ id: `${entry.idNumber}`,
156
149
  ...entry,
157
150
  actions: {
158
151
  content: (
@@ -160,21 +153,21 @@ const ActivePatientsTable: React.FC<ActiveVisitsTableProps> = ({
160
153
  ),
161
154
  },
162
155
  }));
163
- }, [paginatedQueueEntries, status, t, renderActionButton, fromPage]);
156
+ }, [paginatedQueueEntries, renderActionButton]);
164
157
 
165
158
  if (isLoading) {
166
159
  return <DataTableSkeleton role="progressbar" />;
167
160
  }
168
161
 
169
162
  if (
170
- (!isLoading && patientQueueEntries && status === "pending") ||
163
+ (!isLoading && patientEntries && status === "pending") ||
171
164
  status === "completed" ||
172
165
  status === ""
173
166
  ) {
174
- setPatientCount(patientQueueEntries.length);
167
+ setPatientCount(patientEntries.length);
175
168
  }
176
169
 
177
- if (patientQueueEntries?.length) {
170
+ if (patientEntries?.length) {
178
171
  return (
179
172
  <div className={styles.container}>
180
173
  <div className={styles.headerBtnContainer}></div>
@@ -258,7 +251,7 @@ const ActivePatientsTable: React.FC<ActiveVisitsTableProps> = ({
258
251
  page={currentPage}
259
252
  pageSize={currentPageSize}
260
253
  pageSizes={pageSizes}
261
- totalItems={patientQueueEntries?.length}
254
+ totalItems={patientEntries?.length}
262
255
  className={styles.pagination}
263
256
  onChange={({ pageSize, page }) => {
264
257
  if (pageSize !== currentPageSize) {
@@ -291,7 +284,7 @@ const ActivePatientsTable: React.FC<ActiveVisitsTableProps> = ({
291
284
 
292
285
  return (
293
286
  <EmptyState
294
- msg={t("noQueueItems", "No queue items to display")}
287
+ msg={t("noPatientForAdmission", "There are no patients for admission")}
295
288
  helper=""
296
289
  />
297
290
  );
@@ -16,7 +16,6 @@ import {
16
16
  dayjs.extend(isToday);
17
17
 
18
18
  export interface ActiveVisit {
19
- age: string;
20
19
  id: string;
21
20
  idNumber: string;
22
21
  gender: string;
@@ -91,12 +90,11 @@ export function useActiveVisits() {
91
90
  ) {
92
91
  setSize((currentSize) => currentSize + 1);
93
92
  }
94
- }, [data, pageNumber]);
93
+ }, [data, pageNumber, setSize]);
95
94
 
96
95
  const mapVisitProperties = (visit: Visit): ActiveVisit => {
97
96
  // create base object
98
97
  const activeVisits: ActiveVisit = {
99
- age: visit?.patient?.person?.age,
100
98
  id: visit.uuid,
101
99
  idNumber: null,
102
100
  gender: visit?.patient?.person?.gender,
@@ -0,0 +1,43 @@
1
+ import { useMemo } from "react";
2
+ import useSWR from "swr";
3
+ import { openmrsFetch, useConfig } from "@openmrs/esm-framework";
4
+
5
+ interface PatientAdmissionListResponse {
6
+ idNumber: string;
7
+ name: string;
8
+ age: number;
9
+ gender: string;
10
+ visitType: string;
11
+ visitStartTime: string;
12
+ admissionEncounterUuid: string;
13
+ patientUuid: string;
14
+ }
15
+
16
+ export function useEligibleAdmissions() {
17
+ const { patientListForAdmissionUrl } = useConfig();
18
+ const { data, error, isLoading } = useSWR<
19
+ { data: Array<PatientAdmissionListResponse> },
20
+ Error
21
+ >(patientListForAdmissionUrl ?? null, openmrsFetch);
22
+
23
+ const patientQueueEntries = useMemo(() => {
24
+ const rawData = data?.data ?? [];
25
+
26
+ return rawData.map((el) => ({
27
+ ...el,
28
+ visitStartTime: new Date(
29
+ parseInt(el.visitStartTime[0]),
30
+ parseInt(el.visitStartTime[1]),
31
+ parseInt(el.visitStartTime[2]),
32
+ parseInt(el.visitStartTime[3]),
33
+ parseInt(el.visitStartTime[4])
34
+ ).toUTCString(),
35
+ }));
36
+ }, [data?.data]);
37
+
38
+ return {
39
+ patientEntries: patientQueueEntries ? patientQueueEntries : [],
40
+ isLoading,
41
+ error,
42
+ };
43
+ }
@@ -18,6 +18,10 @@ interface VisitResponse {
18
18
  totalCount: number;
19
19
  }
20
20
 
21
+ // interface SQLBedAssignmentResponse {
22
+
23
+ // }
24
+
21
25
  export interface BedPatientAssignment {
22
26
  patientUuid: string;
23
27
  age: number;
@@ -71,7 +75,7 @@ export function useActiveAdmissions() {
71
75
  ) {
72
76
  setSize((currentSize) => currentSize + 1);
73
77
  }
74
- }, [data, pageNumber]);
78
+ }, [data, pageNumber, setSize]);
75
79
 
76
80
  const formattedActiveVisits: any = (data: FetchResponse<VisitResponse>) => {
77
81
  const result = data
@@ -64,7 +64,7 @@ const AdmittedPatientsTable: React.FC<ActiveVisitsTableProps> = ({
64
64
  setSelectedPatientDetails({
65
65
  name: entry.name,
66
66
  patientUuid: entry.patientUuid,
67
- encounter: entry.encounter,
67
+ encounter: entry.admissionEncounterUuid,
68
68
  locationUuid: session?.sessionLocation?.uuid,
69
69
  locationTo: entry.locationTo,
70
70
  locationFrom: entry.locationFrom,
@@ -91,7 +91,7 @@ const AdmittedPatientsTable: React.FC<ActiveVisitsTableProps> = ({
91
91
  // />
92
92
  // );
93
93
  },
94
- [handleBedAssigmentModal, status]
94
+ [status]
95
95
  );
96
96
 
97
97
  const {
@@ -148,7 +148,7 @@ const AdmittedPatientsTable: React.FC<ActiveVisitsTableProps> = ({
148
148
  ),
149
149
  },
150
150
  }));
151
- }, [paginatedQueueEntries, status, t, renderActionButton, fromPage]);
151
+ }, [paginatedQueueEntries]);
152
152
 
153
153
  if (isLoading) {
154
154
  return <DataTableSkeleton role="progressbar" />;
@@ -4,7 +4,6 @@ import styles from "./bed-admission-tabs-styles.scss";
4
4
  import { useTranslation } from "react-i18next";
5
5
  import ActivePatientsTable from "./active-patients/active-patients-table.component";
6
6
  import AdmittedPatientsList from "./admitted-patients/admitted-patients.component";
7
- import DischargedPatientsList from "./discharged-patients/discharged-patients.componet";
8
7
  import WardCard from "../ward-card/ward-card.component";
9
8
 
10
9
  const BedAdmissionTabs: React.FC = () => {
@@ -1,4 +1,4 @@
1
- import { FetchResponse, openmrsFetch } from "@openmrs/esm-framework";
1
+ import { FetchResponse, openmrsFetch, useConfig } from "@openmrs/esm-framework";
2
2
 
3
3
  export async function assignPatientBed(
4
4
  requestPayload,
@@ -53,7 +53,7 @@ const BedLayoutList: React.FC<BedLayoutListProps> = React.memo(
53
53
  );
54
54
  }
55
55
 
56
- if (locationUuid === undefined) {
56
+ if (locationUuid === "") {
57
57
  return (
58
58
  <div className={styles.errorContainer}>
59
59
  <EmptyState
@@ -63,7 +63,7 @@ const BedLayoutList: React.FC<BedLayoutListProps> = React.memo(
63
63
  </div>
64
64
  );
65
65
  }
66
- if (locationUuid !== undefined && !bedData?.length) {
66
+ if (locationUuid !== "" && !bedData?.length) {
67
67
  return (
68
68
  <div className={styles.errorContainer}>
69
69
  <EmptyState
@@ -0,0 +1,240 @@
1
+ import React, { useMemo, useState } from "react";
2
+ import { useTranslation } from "react-i18next";
3
+ import {
4
+ Button,
5
+ DataTable,
6
+ DataTableSkeleton,
7
+ InlineLoading,
8
+ Pagination,
9
+ Table,
10
+ TableBody,
11
+ TableCell,
12
+ TableContainer,
13
+ TableHead,
14
+ TableHeader,
15
+ TableRow,
16
+ Tile,
17
+ } from "@carbon/react";
18
+ import { Add, Edit, Delete } from "@carbon/react/icons";
19
+ import {
20
+ isDesktop as desktopLayout,
21
+ showNotification,
22
+ showToast,
23
+ useLayoutType,
24
+ } from "@openmrs/esm-framework";
25
+ import { CardHeader, ErrorState } from "@openmrs/esm-patient-common-lib";
26
+ import type { BedTagData } from "../../types";
27
+ import { deleteBedTag, useBedTag } from "../../summary/summary.resource";
28
+ import Header from "../../header/header.component";
29
+ import styles from "../../bed-administration/bed-administration-table.scss";
30
+ import BedTagForm from "./new-tag-form.component";
31
+ import EditBedTagForm from "./edit-tag-form.component";
32
+ import DeleteBedTag from "./delete-bed-tag.component";
33
+
34
+ const BedTagAdministrationTable: React.FC = () => {
35
+ const { t } = useTranslation();
36
+ const headerTitle = t("bedTag", "Bed Tag");
37
+ const layout = useLayoutType();
38
+ const isTablet = layout === "tablet";
39
+ const responsiveSize = isTablet ? "lg" : "sm";
40
+ const isDesktop = desktopLayout(layout);
41
+ const [isBedDataLoading] = useState(false);
42
+ const [showBedTagsModal, setAddBedTagsModal] = useState(false);
43
+ const [showDeleteBedTagsModal, setDeleteBedTagsModal] = useState(false);
44
+ const [showEditBedModal, setShowEditBedModal] = useState(false);
45
+ const [editData, setEditData] = useState<BedTagData>();
46
+ const [currentPage, setCurrentPage] = useState(1);
47
+ const [pageSize] = useState(10);
48
+ const { bedTypeData, isError, loading, validate, mutate } = useBedTag();
49
+ const tableHeaders = [
50
+ {
51
+ header: t("ids", "Id"),
52
+ key: "ids",
53
+ },
54
+ {
55
+ header: t("name", "Name"),
56
+ key: "name",
57
+ },
58
+ {
59
+ header: t("actions", "Actions"),
60
+ key: "actions",
61
+ },
62
+ ];
63
+ const tableRows = useMemo(() => {
64
+ return bedTypeData?.map((entry) => ({
65
+ id: entry.uuid,
66
+ ids: entry.id,
67
+ name: entry?.name,
68
+ actions: (
69
+ <>
70
+ <Button
71
+ enterDelayMs={300}
72
+ renderIcon={Edit}
73
+ onClick={(e) => {
74
+ e.preventDefault();
75
+ setEditData(entry);
76
+ setShowEditBedModal(true);
77
+ setAddBedTagsModal(false);
78
+ setDeleteBedTagsModal(false);
79
+ }}
80
+ kind={"ghost"}
81
+ iconDescription={t("editBedTag", "Edit Bed Tag")}
82
+ hasIconOnly
83
+ size={responsiveSize}
84
+ tooltipAlignment="start"
85
+ />
86
+ <Button
87
+ enterDelayMs={300}
88
+ renderIcon={Delete}
89
+ onClick={(e) => {
90
+ e.preventDefault();
91
+ setEditData(entry);
92
+ setShowEditBedModal(false);
93
+ setAddBedTagsModal(false);
94
+ setDeleteBedTagsModal(true);
95
+ }}
96
+ kind={"ghost"}
97
+ iconDescription={t("deleteBedTag", "Delete Bed Tag")}
98
+ hasIconOnly
99
+ size={responsiveSize}
100
+ tooltipAlignment="start"
101
+ />
102
+ </>
103
+ ),
104
+ }));
105
+ }, [responsiveSize, bedTypeData, t]);
106
+
107
+ if (isBedDataLoading || loading) {
108
+ return (
109
+ <>
110
+ <Header route="Bed Tag" />
111
+ <div className={styles.widgetCard}>
112
+ <DataTableSkeleton role="progressbar" compact={isDesktop} zebra />
113
+ </div>
114
+ </>
115
+ );
116
+ }
117
+ if (isError) {
118
+ return (
119
+ <>
120
+ <Header route="Bed Tag" />
121
+ <div className={styles.widgetCard}>
122
+ <ErrorState error={isError} headerTitle={headerTitle} />
123
+ </div>
124
+ </>
125
+ );
126
+ }
127
+ return (
128
+ <>
129
+ <Header route="Bed Tag" />
130
+
131
+ <div className={styles.widgetCard}>
132
+ {showBedTagsModal ? (
133
+ <BedTagForm
134
+ onModalChange={setAddBedTagsModal}
135
+ showModal={showBedTagsModal}
136
+ mutate={mutate}
137
+ />
138
+ ) : null}
139
+ {showEditBedModal ? (
140
+ <EditBedTagForm
141
+ onModalChange={setShowEditBedModal}
142
+ showModal={showEditBedModal}
143
+ editData={editData}
144
+ mutate={mutate}
145
+ />
146
+ ) : null}
147
+ {showDeleteBedTagsModal ? (
148
+ <DeleteBedTag
149
+ onModalChange={setDeleteBedTagsModal}
150
+ showModal={showDeleteBedTagsModal}
151
+ editData={editData}
152
+ mutate={mutate}
153
+ />
154
+ ) : null}
155
+ <CardHeader title={headerTitle}>
156
+ <span className={styles.backgroundDataFetchingIndicator}>
157
+ <span>{validate ? <InlineLoading /> : null}</span>
158
+ </span>
159
+ {bedTypeData?.length ? (
160
+ <Button
161
+ kind="ghost"
162
+ renderIcon={(props) => <Add size={16} {...props} />}
163
+ onClick={() => setAddBedTagsModal(true)}
164
+ >
165
+ {t("addBedTag", "Add Bed Tag")}
166
+ </Button>
167
+ ) : null}
168
+ </CardHeader>
169
+ <DataTable
170
+ rows={tableRows}
171
+ headers={tableHeaders}
172
+ isSortable
173
+ size={isTablet ? "lg" : "sm"}
174
+ useZebraStyles
175
+ >
176
+ {({ rows, headers, getTableProps }) => (
177
+ <TableContainer>
178
+ <Table {...getTableProps()}>
179
+ <TableHead>
180
+ <TableRow>
181
+ {headers.map((header) => (
182
+ <TableHeader>
183
+ {header.header?.content ?? header.header}
184
+ </TableHeader>
185
+ ))}
186
+ </TableRow>
187
+ </TableHead>
188
+ <TableBody>
189
+ {rows.map((row) => (
190
+ <TableRow key={row.id}>
191
+ {row.cells.map((cell) => (
192
+ <TableCell key={cell.id}>
193
+ {cell.value?.content ?? cell.value}
194
+ </TableCell>
195
+ ))}
196
+ </TableRow>
197
+ ))}
198
+ </TableBody>
199
+ </Table>
200
+ {rows.length === 0 ? (
201
+ <div className={styles.tileContainer}>
202
+ <Tile className={styles.tile}>
203
+ <div className={styles.tileContent}>
204
+ <p className={styles.content}>
205
+ {t("No data", "No data to display")}
206
+ </p>
207
+ <p className={styles.helper}>
208
+ {t("checkFilters", "Check the filters above")}
209
+ </p>
210
+ </div>
211
+ <p className={styles.separator}>{t("or", "or")}</p>
212
+ <Button
213
+ kind="ghost"
214
+ size="sm"
215
+ renderIcon={(props) => <Add size={16} {...props} />}
216
+ onClick={() => setAddBedTagsModal(true)}
217
+ >
218
+ {t("bedTag", "Add Bed Tag")}
219
+ </Button>
220
+ </Tile>
221
+ </div>
222
+ ) : null}
223
+ <Pagination
224
+ page={currentPage}
225
+ pageSize={pageSize}
226
+ pageSizes={[10, 20, 30, 40, 50]}
227
+ totalItems={bedTypeData.length}
228
+ onChange={({ page, pageSize }) => {
229
+ setCurrentPage(page);
230
+ pageSize(pageSize);
231
+ }}
232
+ />
233
+ </TableContainer>
234
+ )}
235
+ </DataTable>
236
+ </div>
237
+ </>
238
+ );
239
+ };
240
+ export default BedTagAdministrationTable;
@@ -0,0 +1,146 @@
1
+ import React, { useState } from "react";
2
+ import { z } from "zod";
3
+ import { useForm, Controller } from "react-hook-form";
4
+ import { zodResolver } from "@hookform/resolvers/zod";
5
+ import {
6
+ Button,
7
+ ComposedModal,
8
+ Form,
9
+ FormGroup,
10
+ ModalBody,
11
+ ModalFooter,
12
+ ModalHeader,
13
+ Stack,
14
+ TextInput,
15
+ InlineNotification,
16
+ } from "@carbon/react";
17
+ import { useTranslation } from "react-i18next";
18
+ import { Location } from "@openmrs/esm-framework";
19
+ import type { BedTagData } from "../../types";
20
+
21
+ const BedTagAdministrationSchema = z.object({
22
+ name: z
23
+ .string()
24
+ .max(255)
25
+ .refine(
26
+ (value) => {
27
+ return (
28
+ typeof value === "string" &&
29
+ value.trim().length > 0 &&
30
+ !/\d/.test(value)
31
+ );
32
+ },
33
+ {
34
+ message: "Bed tag must not contain numbers",
35
+ }
36
+ ),
37
+ });
38
+
39
+ interface BedTagAdministrationFormProps {
40
+ showModal: boolean;
41
+ onModalChange: (showModal: boolean) => void;
42
+ availableBedTypes: Array<BedTagData>;
43
+ allLocations: Location[];
44
+ handleCreateQuestion?: (formData: BedTagData) => void;
45
+ handleDeleteBedTag?: () => void;
46
+ headerTitle: string;
47
+ initialData: BedTagData;
48
+ }
49
+
50
+ interface ErrorType {
51
+ message: string;
52
+ }
53
+
54
+ const BedTagsAdministrationForm: React.FC<BedTagAdministrationFormProps> = ({
55
+ showModal,
56
+ onModalChange,
57
+ handleCreateQuestion,
58
+ headerTitle,
59
+ initialData,
60
+ }) => {
61
+ const { t } = useTranslation();
62
+
63
+ const [showErrorNotification, setShowErrorNotification] = useState(false);
64
+ const [formStateError, setFormStateError] = useState("");
65
+
66
+ const {
67
+ handleSubmit,
68
+ control,
69
+ formState: { isDirty },
70
+ } = useForm<BedTagData>({
71
+ mode: "all",
72
+ resolver: zodResolver(BedTagAdministrationSchema),
73
+ defaultValues: {
74
+ name: initialData.name || "",
75
+ },
76
+ });
77
+
78
+ const onSubmit = (formData: BedTagData) => {
79
+ const result = BedTagAdministrationSchema.safeParse(formData);
80
+ if (result.success) {
81
+ setShowErrorNotification(false);
82
+ handleCreateQuestion(formData);
83
+ }
84
+ };
85
+
86
+ const onError = (error: { [key: string]: ErrorType }) => {
87
+ setFormStateError(Object.entries(error)[0][1].message);
88
+ setShowErrorNotification(true);
89
+ };
90
+
91
+ return (
92
+ <ComposedModal
93
+ open={showModal}
94
+ onClose={() => onModalChange(false)}
95
+ preventCloseOnClickOutside
96
+ >
97
+ <ModalHeader title={headerTitle} />
98
+ <Form onSubmit={handleSubmit(onSubmit, onError)}>
99
+ <ModalBody hasScrollingContent>
100
+ <Stack gap={3}>
101
+ <FormGroup legendText={""}>
102
+ <Controller
103
+ name="name"
104
+ control={control}
105
+ rules={{ required: true }}
106
+ render={({ field, fieldState }) => (
107
+ <>
108
+ <TextInput
109
+ id="bedTag"
110
+ labelText={t("bedTag", "Bed Tag Name")}
111
+ placeholder={t("bedTagPlaceholder", "")}
112
+ invalidText={fieldState.error?.message}
113
+ {...field}
114
+ />
115
+ </>
116
+ )}
117
+ />
118
+ </FormGroup>
119
+
120
+ {showErrorNotification && (
121
+ <InlineNotification
122
+ lowContrast
123
+ title={t("error", "Error")}
124
+ style={{ minWidth: "100%", margin: "0rem", padding: "0rem" }}
125
+ role="alert"
126
+ kind="error"
127
+ subtitle={t("pleaseFillField", formStateError) + "."}
128
+ onClose={() => setShowErrorNotification(false)}
129
+ />
130
+ )}
131
+ </Stack>
132
+ </ModalBody>
133
+ <ModalFooter>
134
+ <Button onClick={() => onModalChange(false)} kind="secondary">
135
+ {t("cancel", "Cancel")}
136
+ </Button>
137
+ <Button disabled={!isDirty} type="submit">
138
+ <span>{t("save", "Save")}</span>
139
+ </Button>
140
+ </ModalFooter>
141
+ </Form>
142
+ </ComposedModal>
143
+ );
144
+ };
145
+
146
+ export default BedTagsAdministrationForm;