@kenyaemr/esm-ward-app 8.0.1-pre.99 → 8.0.2

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 (196) hide show
  1. package/.turbo/turbo-build.log +20 -24
  2. package/dist/109.js +1 -0
  3. package/dist/109.js.map +1 -0
  4. package/dist/124.js +1 -0
  5. package/dist/124.js.map +1 -0
  6. package/dist/125.js +1 -0
  7. package/dist/125.js.map +1 -0
  8. package/dist/130.js +1 -1
  9. package/dist/130.js.LICENSE.txt +2 -0
  10. package/dist/130.js.map +1 -1
  11. package/dist/146.js +1 -0
  12. package/dist/146.js.map +1 -0
  13. package/dist/15.js +1 -0
  14. package/dist/15.js.map +1 -0
  15. package/dist/153.js +1 -0
  16. package/dist/153.js.map +1 -0
  17. package/dist/303.js +2 -1
  18. package/dist/303.js.map +1 -1
  19. package/dist/325.js +1 -0
  20. package/dist/325.js.map +1 -0
  21. package/dist/372.js +2 -0
  22. package/dist/372.js.map +1 -0
  23. package/dist/471.js +1 -0
  24. package/dist/471.js.map +1 -0
  25. package/dist/481.js +1 -0
  26. package/dist/481.js.map +1 -0
  27. package/dist/53.js +1 -0
  28. package/dist/53.js.map +1 -0
  29. package/dist/{960.js → 559.js} +1 -1
  30. package/dist/559.js.map +1 -0
  31. package/dist/574.js +1 -1
  32. package/dist/576.js +1 -0
  33. package/dist/576.js.map +1 -0
  34. package/dist/577.js +1 -1
  35. package/dist/577.js.map +1 -1
  36. package/dist/{255.js → 649.js} +2 -2
  37. package/dist/649.js.LICENSE.txt +9 -0
  38. package/dist/649.js.map +1 -0
  39. package/dist/{659.js → 662.js} +1 -1
  40. package/dist/662.js.map +1 -0
  41. package/dist/920.js +1 -0
  42. package/dist/920.js.map +1 -0
  43. package/dist/921.js +1 -0
  44. package/dist/921.js.map +1 -0
  45. package/dist/922.js +1 -0
  46. package/dist/922.js.map +1 -0
  47. package/dist/969.js +1 -0
  48. package/dist/969.js.map +1 -0
  49. package/dist/kenyaemr-esm-ward-app.js +1 -1
  50. package/dist/kenyaemr-esm-ward-app.js.buildmanifest.json +304 -128
  51. package/dist/kenyaemr-esm-ward-app.js.map +1 -1
  52. package/dist/main.js +1 -1
  53. package/dist/main.js.LICENSE.txt +0 -10
  54. package/dist/main.js.map +1 -1
  55. package/dist/routes.json +1 -1
  56. package/mock.tsx +62 -0
  57. package/package-lock.json +5001 -0
  58. package/package.json +2 -3
  59. package/src/action-menu-buttons/clinical-forms-workspace-siderail.component.tsx +37 -0
  60. package/src/action-menu-buttons/discharge-workspace-siderail.component.tsx +20 -0
  61. package/src/beds/empty-bed-skeleton.tsx +4 -3
  62. package/src/beds/empty-bed.component.tsx +3 -3
  63. package/src/beds/ward-bed.component.tsx +41 -0
  64. package/src/beds/ward-bed.scss +45 -0
  65. package/src/beds/{occupied-bed.test.tsx → ward-bed.test.tsx} +42 -20
  66. package/src/config-schema.ts +203 -84
  67. package/src/constant.ts +1 -1
  68. package/src/hooks/useAdmissionLocation.ts +22 -4
  69. package/src/hooks/useAssignedBedByPatient.ts +9 -0
  70. package/src/hooks/useBeds.ts +3 -4
  71. package/src/hooks/useConcept.ts +3 -4
  72. package/src/hooks/useEmrConfiguration.ts +5 -0
  73. package/src/hooks/useInpatientAdmission.ts +9 -14
  74. package/src/hooks/useInpatientRequest.ts +4 -15
  75. package/src/hooks/useLocations.ts +8 -51
  76. package/src/hooks/useMotherAndChildren.ts +46 -0
  77. package/src/hooks/useObs.ts +3 -7
  78. package/src/hooks/usePatientPendingOrders.ts +16 -0
  79. package/src/hooks/useWardPatientGrouping.ts +32 -0
  80. package/src/index.ts +45 -17
  81. package/src/location-selector/location-selector.component.tsx +18 -21
  82. package/src/root.component.tsx +3 -0
  83. package/src/routes.json +41 -3
  84. package/src/types/index.ts +50 -1
  85. package/src/ward-patient-card/card-rows/admission-request-note-row.component.tsx +38 -0
  86. package/src/ward-patient-card/card-rows/coded-obs-tags-row.component.tsx +108 -0
  87. package/src/ward-patient-card/card-rows/mother-child-row.component.tsx +84 -0
  88. package/src/ward-patient-card/card-rows/mother-child-row.scss +22 -0
  89. package/src/ward-patient-card/card-rows/pending-items-row.component.tsx +54 -0
  90. package/src/ward-patient-card/row-elements/ward-patient-age.tsx +1 -1
  91. package/src/ward-patient-card/row-elements/ward-patient-coded-obs-tags.tsx +62 -39
  92. package/src/ward-patient-card/row-elements/ward-patient-header-address.tsx +5 -5
  93. package/src/ward-patient-card/row-elements/ward-patient-identifier.tsx +18 -41
  94. package/src/ward-patient-card/row-elements/ward-patient-location.tsx +19 -0
  95. package/src/ward-patient-card/row-elements/ward-patient-obs.resource.ts +38 -34
  96. package/src/ward-patient-card/row-elements/ward-patient-obs.tsx +26 -13
  97. package/src/ward-patient-card/row-elements/ward-patient-pending-order.component.tsx +45 -0
  98. package/src/ward-patient-card/row-elements/ward-patient-pending-transfer.tsx +38 -0
  99. package/src/ward-patient-card/row-elements/ward-patient-responsive-tooltip.tsx +32 -0
  100. package/src/ward-patient-card/row-elements/ward-patient-skeleton-text.tsx +9 -0
  101. package/src/ward-patient-card/ward-patient-card.component.tsx +14 -45
  102. package/src/ward-patient-card/ward-patient-card.scss +68 -9
  103. package/src/ward-view/default-ward/default-ward-beds.component.tsx +42 -0
  104. package/src/ward-view/default-ward/default-ward-patient-card-header.component.tsx +32 -0
  105. package/src/ward-view/default-ward/default-ward-patient-card.component.tsx +31 -0
  106. package/src/ward-view/default-ward/default-ward-pending-patients.component.tsx +52 -0
  107. package/src/ward-view/default-ward/default-ward-unassigned-patients.component.tsx +32 -0
  108. package/src/ward-view/default-ward/default-ward-view.component.tsx +31 -0
  109. package/src/ward-view/materal-ward/maternal-ward-beds.component.tsx +65 -0
  110. package/src/ward-view/materal-ward/maternal-ward-patient-card-header.component.tsx +30 -0
  111. package/src/ward-view/materal-ward/maternal-ward-patient-card.component.tsx +93 -0
  112. package/src/{beds/occupied-bed.scss → ward-view/materal-ward/maternal-ward-patient-card.scss} +4 -9
  113. package/src/ward-view/materal-ward/maternal-ward-patient-card.test.tsx +58 -0
  114. package/src/ward-view/materal-ward/maternal-ward-pending-patients.component.tsx +48 -0
  115. package/src/ward-view/materal-ward/maternal-ward-unassigned-patients.component.tsx +33 -0
  116. package/src/ward-view/materal-ward/maternal-ward-view.component.tsx +38 -0
  117. package/src/ward-view/materal-ward/maternal-ward-view.resource.ts +89 -0
  118. package/src/ward-view/ward-view.component.tsx +15 -163
  119. package/src/ward-view/ward-view.resource.ts +193 -1
  120. package/src/ward-view/ward-view.scss +17 -6
  121. package/src/ward-view/ward-view.test.tsx +43 -48
  122. package/src/ward-view/ward.component.tsx +106 -0
  123. package/src/ward-view-header/admission-requests-bar.component.tsx +14 -9
  124. package/src/ward-view-header/admission-requests-bar.test.tsx +11 -23
  125. package/src/ward-view-header/admission-requests.scss +1 -1
  126. package/src/ward-view-header/ward-metric.component.tsx +24 -0
  127. package/src/ward-view-header/ward-metric.scss +25 -0
  128. package/src/ward-view-header/ward-metrics.component.tsx +78 -0
  129. package/src/ward-view-header/ward-metrics.scss +7 -0
  130. package/src/ward-view-header/ward-metrics.test.tsx +37 -0
  131. package/src/ward-view-header/ward-view-header.component.tsx +9 -4
  132. package/src/ward-view-header/ward-view-header.scss +0 -1
  133. package/src/ward-workspace/admission-request-card/admission-request-card-actions.component.tsx +70 -6
  134. package/src/ward-workspace/admission-request-card/admission-request-card-header.component.tsx +10 -23
  135. package/src/ward-workspace/admission-request-card/admission-request-card.component.tsx +9 -3
  136. package/src/ward-workspace/admission-request-card/admission-request-card.scss +13 -4
  137. package/src/ward-workspace/admission-request-workspace/admission-requests-workspace.test.tsx +55 -33
  138. package/src/ward-workspace/admission-request-workspace/admission-requests.workspace.tsx +30 -37
  139. package/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.test.tsx +98 -203
  140. package/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.workspace.tsx +116 -180
  141. package/src/ward-workspace/bed-selector.component.tsx +119 -0
  142. package/src/ward-workspace/bed-selector.scss +15 -0
  143. package/src/ward-workspace/patient-banner/patient-banner.component.tsx +7 -14
  144. package/src/ward-workspace/patient-clinical-forms-workspace/patient-clinical-forms.workspace.tsx +23 -0
  145. package/src/{ward-patient-workspace → ward-workspace/patient-details}/ward-patient-action-button.extension.tsx +2 -1
  146. package/src/{ward-patient-workspace → ward-workspace/patient-details}/ward-patient.workspace.tsx +18 -9
  147. package/src/ward-workspace/patient-discharge/patient-discharge.scss +41 -0
  148. package/src/ward-workspace/patient-discharge/patient-discharge.workspace.tsx +113 -0
  149. package/src/ward-workspace/patient-transfer-bed-swap/patient-bed-swap-form.component.tsx +68 -79
  150. package/src/ward-workspace/patient-transfer-bed-swap/patient-transfer-request-form.component.tsx +24 -24
  151. package/src/ward-workspace/patient-transfer-bed-swap/patient-transfer-swap.scss +12 -2
  152. package/src/ward-workspace/patient-transfer-bed-swap/patient-transfer-swap.workspace.tsx +12 -8
  153. package/src/ward-workspace/patient-transfer-request-workspace/patient-transfer-request.workspace.tsx +11 -0
  154. package/src/ward-workspace/ward-patient-notes/form/notes-form.component.tsx +1 -1
  155. package/src/ward-workspace/ward-patient-notes/form/notes-form.test.tsx +1 -1
  156. package/src/ward-workspace/ward-patient-notes/history/notes-container.component.tsx +2 -2
  157. package/src/ward-workspace/ward-patient-notes/notes.resource.ts +5 -7
  158. package/src/ward-workspace/ward-patient-notes/notes.workspace.tsx +1 -1
  159. package/src/ward-workspace/ward-patient-notes/types.ts +0 -4
  160. package/src/ward.resource.ts +38 -2
  161. package/translations/en.json +31 -7
  162. package/dist/152.js +0 -1
  163. package/dist/152.js.map +0 -1
  164. package/dist/255.js.map +0 -1
  165. package/dist/269.js +0 -1
  166. package/dist/269.js.map +0 -1
  167. package/dist/346.js +0 -1
  168. package/dist/346.js.map +0 -1
  169. package/dist/466.js +0 -1
  170. package/dist/466.js.map +0 -1
  171. package/dist/659.js.map +0 -1
  172. package/dist/729.js +0 -1
  173. package/dist/729.js.map +0 -1
  174. package/dist/749.js +0 -1
  175. package/dist/749.js.map +0 -1
  176. package/dist/76.js +0 -1
  177. package/dist/76.js.map +0 -1
  178. package/dist/793.js +0 -2
  179. package/dist/793.js.map +0 -1
  180. package/dist/803.js +0 -1
  181. package/dist/803.js.map +0 -1
  182. package/dist/960.js.map +0 -1
  183. package/src/beds/empty-bed.scss +0 -28
  184. package/src/beds/occupied-bed.component.tsx +0 -35
  185. package/src/beds/unassigned-patient.component.tsx +0 -20
  186. package/src/beds/unassigned-patient.scss +0 -6
  187. package/src/config-schema-admission-request-note.ts +0 -9
  188. package/src/config-schema-extension-colored-obs-tags.ts +0 -91
  189. package/src/hooks/useCurrentWardCardConfig.ts +0 -32
  190. package/src/ward-patient-card/card-rows/admission-request-note.extension.tsx +0 -27
  191. package/src/ward-patient-card/card-rows/colored-obs-tags-card-row.extension.tsx +0 -13
  192. package/src/ward-patient-card/ward-patient-card-element.component.tsx +0 -65
  193. package/src/ward-view/ward-bed.component.tsx +0 -14
  194. /package/dist/{793.js.LICENSE.txt → 303.js.LICENSE.txt} +0 -0
  195. /package/dist/{255.js.LICENSE.txt → 372.js.LICENSE.txt} +0 -0
  196. /package/src/{ward-patient-workspace → ward-workspace/patient-details}/ward-patient.style.scss +0 -0
@@ -0,0 +1,22 @@
1
+ @use '@carbon/layout';
2
+ @use '@openmrs/esm-styleguide/src/vars' as *;
3
+
4
+ .motherOrBabyRow {
5
+ display: flex;
6
+ }
7
+
8
+ .motherOrBabyRowElementsDiv {
9
+ flex: 1;
10
+ }
11
+
12
+ .motherOrBabyIconDiv {
13
+ display: flex;
14
+ align-items: center;
15
+ }
16
+
17
+ .motherOrBabyIcon {
18
+ padding: layout.$spacing-02;
19
+ border-radius: 50%;
20
+ background-color: $grey-2;
21
+ margin-right: layout.$spacing-03;
22
+ }
@@ -0,0 +1,54 @@
1
+ import { Hourglass } from '@carbon/react/icons';
2
+ import React, { useCallback, useEffect } from 'react';
3
+ import { type WardPatient } from '../../types';
4
+
5
+ import { useElementConfig } from '../../ward-view/ward-view.resource';
6
+ import { WardPatientPendingOrder } from '../row-elements/ward-patient-pending-order.component';
7
+ import WardPatientPendingTransfer from '../row-elements/ward-patient-pending-transfer';
8
+ import styles from '../ward-patient-card.scss';
9
+
10
+ export interface PendingItemsRowProps {
11
+ id: string;
12
+ wardPatient: WardPatient;
13
+ }
14
+
15
+ const PendingItemsRow: React.FC<PendingItemsRowProps> = ({ id, wardPatient }) => {
16
+ const { orders, showPendingItems } = useElementConfig('pendingItems', id);
17
+ const [hasPendingOrders, setHasPendingOrders] = React.useState(false);
18
+
19
+ const hasPendingItems = !!wardPatient?.inpatientRequest || hasPendingOrders;
20
+
21
+ const handlePendingOrderCount = useCallback((count: number) => {
22
+ if (count > 0) {
23
+ setHasPendingOrders(true);
24
+ }
25
+ }, []);
26
+
27
+ useEffect(() => {
28
+ if (!orders?.orderTypes?.length) {
29
+ setHasPendingOrders(false);
30
+ }
31
+ }, [orders]);
32
+
33
+ return (
34
+ <div className={styles.wardPatientCardPendingItemsRow}>
35
+ {showPendingItems && hasPendingItems ? (
36
+ <>
37
+ <Hourglass className={styles.hourGlassIcon} size="16" />:
38
+ </>
39
+ ) : null}
40
+ {orders?.orderTypes.map(({ uuid, label }) => (
41
+ <WardPatientPendingOrder
42
+ key={`pending-order-type-${uuid}`}
43
+ wardPatient={wardPatient}
44
+ orderUuid={uuid}
45
+ label={label}
46
+ onOrderCount={handlePendingOrderCount}
47
+ />
48
+ ))}
49
+ {wardPatient?.inpatientRequest ? <WardPatientPendingTransfer wardPatient={wardPatient} /> : null}
50
+ </div>
51
+ );
52
+ };
53
+
54
+ export default PendingItemsRow;
@@ -6,7 +6,7 @@ export interface WardPatientAgeProps {
6
6
  }
7
7
 
8
8
  const WardPatientAge: React.FC<WardPatientAgeProps> = ({ patient }) => {
9
- return <div>{age(patient.person?.birthdate)}</div>;
9
+ return patient.person?.birthdate ? <div>{age(patient.person.birthdate)}</div> : null;
10
10
  };
11
11
 
12
12
  export default WardPatientAge;
@@ -1,15 +1,16 @@
1
- import { SkeletonText, Tag } from '@carbon/react';
2
- import { type Patient, translateFrom, type Visit, type OpenmrsResource } from '@openmrs/esm-framework';
3
- import React from 'react';
1
+ import { Tag } from '@carbon/react';
2
+ import { type OpenmrsResource, type Patient, type Visit } from '@openmrs/esm-framework';
3
+ import React, { type ReactNode } from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
- import { moduleName } from '../../constant';
6
5
  import { useObs } from '../../hooks/useObs';
6
+ import { useElementConfig } from '../../ward-view/ward-view.resource';
7
7
  import styles from '../ward-patient-card.scss';
8
- import { obsCustomRepresentation, useConceptToTagColorMap } from './ward-patient-obs.resource';
9
- import { type ColoredObsTagsCardRowConfigObject } from '../../config-schema-extension-colored-obs-tags';
8
+ import WardPatientSkeletonText from './ward-patient-skeleton-text';
9
+ import { getObsEncounterString, obsCustomRepresentation, useConceptToTagColorMap } from './ward-patient-obs.resource';
10
+ import WardPatientResponsiveTooltip from './ward-patient-responsive-tooltip';
10
11
 
11
12
  interface WardPatientCodedObsTagsProps {
12
- config: ColoredObsTagsCardRowConfigObject;
13
+ id: string;
13
14
  patient: Patient;
14
15
  visit: Visit;
15
16
  }
@@ -24,50 +25,72 @@ interface WardPatientCodedObsTagsProps {
24
25
  * @param config
25
26
  * @returns
26
27
  */
27
- const WardPatientCodedObsTags: React.FC<WardPatientCodedObsTagsProps> = ({ config, patient, visit }) => {
28
- const { conceptUuid, summaryLabel, summaryLabelColor, summaryLabelI18nModule } = config;
29
- const { data, isLoading } = useObs({ patient: patient.uuid, concept: conceptUuid }, obsCustomRepresentation);
28
+ const WardPatientCodedObsTags: React.FC<WardPatientCodedObsTagsProps> = ({ id, patient, visit }) => {
29
+ const config = useElementConfig('coloredObsTags', id);
30
+ const { conceptUuid, summaryLabel, summaryLabelColor } = config ?? {};
31
+ const { data, isLoading } = useObs(
32
+ { patient: patient.uuid, concept: conceptUuid },
33
+ conceptUuid != null,
34
+ obsCustomRepresentation,
35
+ );
30
36
  const { t } = useTranslation();
31
- const { data: conceptToTagColorMap } = useConceptToTagColorMap(config.tags);
37
+ const conceptToTagColorMap = useConceptToTagColorMap(config?.tags ?? []);
32
38
 
33
39
  if (isLoading) {
34
- return <SkeletonText />;
40
+ return (
41
+ <div className={styles.wardPatientCardRow}>
42
+ <WardPatientSkeletonText />
43
+ </div>
44
+ );
35
45
  } else {
36
- const obsToDisplay = data?.data?.results?.filter((o) => {
46
+ const obsToDisplay = data?.filter((o) => {
37
47
  const matchVisit = o.encounter.visit?.uuid == visit?.uuid;
38
- return matchVisit || visit == null; // TODO: remove visit == null hack when server API supports returning visit
48
+ return matchVisit;
39
49
  });
40
50
 
41
- const summaryLabelToDisplay =
42
- summaryLabel != null
43
- ? translateFrom(summaryLabelI18nModule ?? moduleName, summaryLabel)
44
- : obsToDisplay?.[0]?.concept?.display;
51
+ const summaryLabelToDisplay = summaryLabel != null ? t(summaryLabel) : obsToDisplay?.[0]?.concept?.display;
45
52
 
46
- const obsNodes = obsToDisplay?.map((o) => {
47
- const { display, uuid } = o.value as OpenmrsResource;
53
+ // for each obs configured to be displayed with a color, we create a tag for it
54
+ // for other obs not configured, we create a single summary tag for all of them.
55
+ const summaryTagTooltipText: ReactNode[] = [];
56
+ const coloredOpsTags = obsToDisplay
57
+ ?.map((o) => {
58
+ const { display, uuid } = o.value as OpenmrsResource;
48
59
 
49
- const color = conceptToTagColorMap?.get(uuid);
50
- if (color) {
51
- return (
52
- <Tag type={color} key={`ward-coded-obs-tag-${o.uuid}`}>
53
- {display}
54
- </Tag>
55
- );
56
- } else {
57
- return null;
58
- }
59
- });
60
+ const color = conceptToTagColorMap?.get(uuid);
61
+ if (color) {
62
+ return (
63
+ <WardPatientResponsiveTooltip tooltipContent={getObsEncounterString(o, t)}>
64
+ <Tag type={color} key={`ward-coded-obs-tag-${o.uuid}`}>
65
+ {display}
66
+ </Tag>
67
+ </WardPatientResponsiveTooltip>
68
+ );
69
+ } else {
70
+ summaryTagTooltipText.push(
71
+ <div key={uuid}>
72
+ {display} ({getObsEncounterString(o, t)})
73
+ </div>,
74
+ );
75
+ return null;
76
+ }
77
+ })
78
+ .filter((o) => o != null);
60
79
 
61
- const obsWithNoTagCount = obsNodes.filter((o) => o == null).length;
62
- if (obsNodes?.length > 0 || obsWithNoTagCount > 0) {
80
+ if (coloredOpsTags?.length > 0 || summaryTagTooltipText.length > 0) {
63
81
  return (
64
- <div>
82
+ <div className={styles.wardPatientCardRow}>
65
83
  <span className={styles.wardPatientObsLabel}>
66
- {obsNodes}
67
- {obsWithNoTagCount > 0 ? (
68
- <Tag type={summaryLabelColor}>
69
- {t('countItems', '{{count}} {{item}}', { count: obsWithNoTagCount, item: summaryLabelToDisplay })}
70
- </Tag>
84
+ {coloredOpsTags}
85
+ {summaryTagTooltipText.length > 0 ? (
86
+ <WardPatientResponsiveTooltip tooltipContent={summaryTagTooltipText}>
87
+ <Tag type={summaryLabelColor}>
88
+ {t('countItems', '{{count}} {{item}}', {
89
+ count: summaryTagTooltipText.length,
90
+ item: summaryLabelToDisplay,
91
+ })}
92
+ </Tag>
93
+ </WardPatientResponsiveTooltip>
71
94
  ) : null}
72
95
  </span>
73
96
  </div>
@@ -1,15 +1,15 @@
1
- import React from 'react';
2
- import styles from '../ward-patient-card.scss';
3
1
  import { type Patient } from '@openmrs/esm-framework';
4
- import { type AddressElementDefinition } from '../../config-schema';
2
+ import React from 'react';
3
+ import { useElementConfig } from '../../ward-view/ward-view.resource';
5
4
 
6
5
  export interface WardPatientAddressProps {
7
6
  patient: Patient;
8
- config: AddressElementDefinition;
7
+ id: string;
9
8
  }
10
9
 
11
- const WardPatientAddress: React.FC<WardPatientAddressProps> = ({ patient, config }) => {
10
+ const WardPatientAddress: React.FC<WardPatientAddressProps> = ({ patient, id }) => {
12
11
  const preferredAddress = patient?.person?.preferredAddress;
12
+ const config = useElementConfig("patientAddress", id);
13
13
 
14
14
  return (
15
15
  <>
@@ -1,52 +1,29 @@
1
+ import { type Patient, type PatientIdentifier, PatientBannerPatientIdentifier } from '@openmrs/esm-framework';
1
2
  import React from 'react';
2
- import { type IdentifierElementDefinition } from '../../config-schema';
3
- import { Tag } from '@carbon/react';
4
- import { type Patient, translateFrom, type PatientIdentifier } from '@openmrs/esm-framework';
5
- import { moduleName } from '../../constant';
6
- import { useTranslation } from 'react-i18next';
7
-
8
- /** Sort the identifiers by preferred first. The identifier with value of true
9
- * takes precedence over false. If both identifiers have same preferred value,
10
- * sort them by most recently created or changed. */
11
- const identifierCompareFunction = (pi1: PatientIdentifier, pi2: PatientIdentifier) => {
12
- let comp = (pi2.preferred ? 1 : 0) - (pi1.preferred ? 1 : 0);
13
-
14
- if (comp == 0) {
15
- const date1 = pi1.auditInfo.dateChanged ?? pi1.auditInfo.dateCreated;
16
- const date2 = pi2.auditInfo.dateChanged ?? pi2.auditInfo.dateCreated;
17
- comp = date2.localeCompare(date1);
18
- }
19
- return comp;
20
- };
3
+ import { useElementConfig } from '../../ward-view/ward-view.resource';
21
4
 
22
5
  export interface WardPatientIdentifierProps {
23
6
  patient: Patient;
24
- /** If the config is not passed, this will be the default identifier element, which uses the preferred identifier type. */
25
- config?: IdentifierElementDefinition;
7
+ id?: string;
26
8
  }
27
9
 
28
- const defaultConfig: IdentifierElementDefinition = {
29
- id: 'patient-identifier',
30
- identifierTypeUuid: null,
31
- };
10
+ const WardPatientIdentifier: React.FC<WardPatientIdentifierProps> = ({ id, patient }) => {
11
+ const config = useElementConfig('patientIdentifier', id);
12
+
13
+ const fhirIdentifiers: fhir.Identifier[] = patient.identifiers.map((identifier: PatientIdentifier) => ({
14
+ value: identifier.identifier,
15
+ type: {
16
+ text: identifier.identifierType.name,
17
+ coding: [
18
+ {
19
+ code: identifier.identifierType.uuid,
20
+ },
21
+ ],
22
+ },
23
+ }));
32
24
 
33
- const WardPatientIdentifier: React.FC<WardPatientIdentifierProps> = ({ config: configProp, patient }) => {
34
- const { t } = useTranslation();
35
- const config = configProp ?? defaultConfig;
36
- const { identifierTypeUuid, labelI18nModule: labelModule, label } = config;
37
- const patientIdentifiers = patient.identifiers.filter(
38
- (patientIdentifier: PatientIdentifier) =>
39
- identifierTypeUuid == null || patientIdentifier.identifierType?.uuid === identifierTypeUuid,
40
- );
41
- patientIdentifiers.sort(identifierCompareFunction);
42
- const patientIdentifier = patientIdentifiers[0];
43
- const labelToDisplay =
44
- label != null ? translateFrom(labelModule ?? moduleName, label) : patientIdentifier?.identifierType?.name;
45
25
  return (
46
- <div>
47
- {labelToDisplay ? <Tag>{t('identifierTypelabel', '{{label}}:', { label: labelToDisplay })}</Tag> : <></>}
48
- <span>{patientIdentifier?.identifier}</span>
49
- </div>
26
+ <PatientBannerPatientIdentifier identifier={fhirIdentifiers} showIdentifierLabel={config?.showIdentifierLabel} />
50
27
  );
51
28
  };
52
29
 
@@ -0,0 +1,19 @@
1
+ import React from 'react';
2
+ import { type InpatientAdmission } from '../../types';
3
+
4
+ export interface WardPatientIdentifierProps {
5
+ inpatientAdmission: InpatientAdmission;
6
+ }
7
+
8
+ const WardPatientLocation: React.FC<WardPatientIdentifierProps> = ({ inpatientAdmission }) => {
9
+ const locationDisplay = inpatientAdmission?.encounterAssigningToCurrentInpatientLocation.location?.display;
10
+ return locationDisplay ? (
11
+ <div>
12
+ <span>{locationDisplay}</span>
13
+ </div>
14
+ ) : (
15
+ <></>
16
+ );
17
+ };
18
+
19
+ export default WardPatientLocation;
@@ -1,52 +1,56 @@
1
- import { openmrsFetch, restBaseUrl, type Concept } from '@openmrs/esm-framework';
2
- import useSWRImmutable from 'swr/immutable';
3
- import { type TagConfigObject } from '../../config-schema-extension-colored-obs-tags';
1
+ import { restBaseUrl, useOpenmrsFetchAll, type Concept } from '@openmrs/esm-framework';
2
+ import { type Observation } from '../../types';
3
+ import { type TFunction } from 'i18next';
4
+ import { type ColoredObsTagConfig } from '../../config-schema';
4
5
 
5
6
  // prettier-ignore
6
7
  export const obsCustomRepresentation =
7
8
  'custom:(uuid,display,obsDatetime,value,' +
8
9
  'concept:(uuid,display),' +
9
- 'encounter:(uuid,display,' +
10
+ 'encounter:(uuid,display,encounterType,encounterDatetime,' +
10
11
  'visit:(uuid,display)))';
11
12
 
12
13
  // get the setMembers of a concept set
13
14
  const conceptSetCustomRepresentation = 'custom:(uuid,setMembers:(uuid))';
14
15
 
15
- export function useConceptToTagColorMap(tags: Array<TagConfigObject>) {
16
- // fetch the members of the concept sets and process the data
17
- // to return conceptToTagColorMap (wrapped in a promise).
18
- // Let swr cache the result of this function.
19
- const fetchAndMap = (url: string) => {
20
- const conceptSetToTagColorMap = new Map<string, string>();
21
- for (const tag of tags) {
22
- const { color, appliedToConceptSets } = tag;
23
- for (const answer of appliedToConceptSets ?? []) {
24
- if (!conceptSetToTagColorMap.has(answer)) {
25
- conceptSetToTagColorMap.set(answer, color);
26
- }
27
- }
28
- }
16
+ export function useConceptToTagColorMap(tags: Array<ColoredObsTagConfig> = []) {
17
+ // The TacConfigObject allows us to specify the mapping of
18
+ // concept sets to colors. However, we also need to build a map of
19
+ // concepts to colors. This function does that.
29
20
 
30
- return openmrsFetch<{ results: Array<Concept> }>(url).then((data) => {
31
- const conceptSets = data.data.results;
32
- const conceptToTagColorMap = new Map<string, string>();
33
- if (conceptSets) {
34
- for (const conceptSet of conceptSets) {
35
- for (const concept of conceptSet.setMembers) {
36
- if (!conceptToTagColorMap.has(concept.uuid)) {
37
- conceptToTagColorMap.set(concept.uuid, conceptSetToTagColorMap.get(conceptSet.uuid));
38
- }
39
- }
40
- }
21
+ // TODO: We should cache this map to be re-usable app-wide
22
+ const conceptSetToTagColorMap = new Map<string, string>();
23
+ for (const tag of tags) {
24
+ const { color, appliedToConceptSets } = tag;
25
+ for (const answer of appliedToConceptSets ?? []) {
26
+ if (!conceptSetToTagColorMap.has(answer)) {
27
+ conceptSetToTagColorMap.set(answer, color);
41
28
  }
42
-
43
- return conceptToTagColorMap;
44
- });
45
- };
29
+ }
30
+ }
46
31
 
47
32
  const conceptSetUuids = tags.flatMap((tag) => tag.appliedToConceptSets);
48
33
  const apiUrl = `${restBaseUrl}/concept?references=${conceptSetUuids.join()}&v=${conceptSetCustomRepresentation}`;
49
- const conceptToTagColorMap = useSWRImmutable(apiUrl, fetchAndMap);
34
+ const { data: conceptSets } = useOpenmrsFetchAll<Concept>(apiUrl);
35
+
36
+ const conceptToTagColorMap = new Map<string, string>();
37
+ if (conceptSets) {
38
+ for (const conceptSet of conceptSets) {
39
+ for (const concept of conceptSet.setMembers) {
40
+ if (!conceptToTagColorMap.has(concept.uuid)) {
41
+ conceptToTagColorMap.set(concept.uuid, conceptSetToTagColorMap.get(conceptSet.uuid));
42
+ }
43
+ }
44
+ }
45
+ }
50
46
 
51
47
  return conceptToTagColorMap;
52
48
  }
49
+
50
+ export function getObsEncounterString(obs: Observation, t: TFunction) {
51
+ return t('encounterDisplay', '{{encounterType}} {{encounterDate}}', {
52
+ encounterType: obs.encounter.encounterType.display,
53
+ encounterDate: new Date(obs.encounter.encounterDatetime).toLocaleDateString(),
54
+ interpolation: { escapeValue: false },
55
+ });
56
+ }
@@ -1,28 +1,36 @@
1
1
  import { SkeletonText } from '@carbon/react';
2
- import { type OpenmrsResource, type Patient, translateFrom, type Visit } from '@openmrs/esm-framework';
2
+ import { type OpenmrsResource, type Patient, type Visit } from '@openmrs/esm-framework';
3
3
  import React from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
- import { type ObsElementDefinition } from '../../config-schema';
5
+ import { type ObsElementConfig } from '../../config-schema';
6
6
  import { useObs } from '../../hooks/useObs';
7
7
  import styles from '../ward-patient-card.scss';
8
- import { moduleName } from '../../constant';
9
- import { obsCustomRepresentation } from './ward-patient-obs.resource';
8
+ import { getObsEncounterString, obsCustomRepresentation } from './ward-patient-obs.resource';
9
+ import WardPatientResponsiveTooltip from './ward-patient-responsive-tooltip';
10
+ import { useElementConfig } from '../../ward-view/ward-view.resource';
10
11
 
11
12
  export interface WardPatientObsProps {
12
- config: ObsElementDefinition;
13
+ id: string;
14
+ configOverride?: ObsElementConfig;
13
15
  patient: Patient;
14
16
  visit: Visit;
15
17
  }
16
18
 
17
- const WardPatientObs: React.FC<WardPatientObsProps> = ({ config, patient, visit }) => {
18
- const { conceptUuid, onlyWithinCurrentVisit, orderBy, limit, label, labelI18nModule: labelModule } = config;
19
- const { data, isLoading } = useObs({ patient: patient.uuid, concept: conceptUuid }, obsCustomRepresentation);
19
+ const WardPatientObs: React.FC<WardPatientObsProps> = ({ id, configOverride, patient, visit }) => {
20
+ const config: ObsElementConfig = useElementConfig('obs', id);
21
+ const configToUse = configOverride ?? config;
22
+ const { conceptUuid, onlyWithinCurrentVisit, orderBy, limit, label } = configToUse ?? {};
23
+ const { data, isLoading } = useObs(
24
+ { patient: patient.uuid, concept: conceptUuid },
25
+ conceptUuid != null,
26
+ obsCustomRepresentation,
27
+ );
20
28
  const { t } = useTranslation();
21
29
 
22
30
  if (isLoading) {
23
31
  return <SkeletonText />;
24
32
  } else {
25
- const obsToDisplay = data?.data?.results
33
+ const obsToDisplay = data
26
34
  ?.filter((o) => {
27
35
  const matchVisit = !onlyWithinCurrentVisit || o.encounter.visit?.uuid == visit?.uuid;
28
36
  return matchVisit;
@@ -32,13 +40,18 @@ const WardPatientObs: React.FC<WardPatientObsProps> = ({ config, patient, visit
32
40
  })
33
41
  ?.slice(0, limit == 0 ? Number.MAX_VALUE : limit);
34
42
 
35
- const labelToDisplay =
36
- label != null ? translateFrom(labelModule ?? moduleName, label) : obsToDisplay?.[0]?.concept?.display;
43
+ const labelToDisplay = label != null ? t(label) : obsToDisplay?.[0]?.concept?.display;
37
44
 
38
45
  const obsNodes = obsToDisplay?.map((o) => {
39
46
  const { value } = o;
40
47
  const display: any = (value as OpenmrsResource)?.display ?? o.value;
41
- return <span key={o.uuid}> {display} </span>;
48
+
49
+ const tooltipContent = getObsEncounterString(o, t);
50
+ return (
51
+ <WardPatientResponsiveTooltip key={o.uuid} tooltipContent={tooltipContent}>
52
+ <span title={tooltipContent}>{display} </span>
53
+ </WardPatientResponsiveTooltip>
54
+ );
42
55
  });
43
56
 
44
57
  if (obsNodes?.length > 0) {
@@ -47,7 +60,7 @@ const WardPatientObs: React.FC<WardPatientObsProps> = ({ config, patient, visit
47
60
  <span className={styles.wardPatientObsLabel}>
48
61
  {labelToDisplay ? t('labelColon', '{{label}}:', { label: labelToDisplay }) : ''}
49
62
  </span>
50
- {obsNodes}
63
+ <div className={styles.dotSeparatedChildren}>{obsNodes}</div>
51
64
  </div>
52
65
  );
53
66
  } else {
@@ -0,0 +1,45 @@
1
+ import React, { useEffect } from 'react';
2
+ import { ChemistryReference } from '@carbon/react/icons';
3
+ import styles from '../ward-patient-card.scss';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { type WardPatient } from '../../types';
6
+ import { usePatientPendingOrders } from '../../hooks/usePatientPendingOrders';
7
+
8
+ export interface WardPatientPendingOrderProps {
9
+ wardPatient: WardPatient;
10
+ orderUuid: string;
11
+ label: string;
12
+ onOrderCount: (count: number) => void; // New prop for notifying parent
13
+ }
14
+
15
+ export const WardPatientPendingOrder: React.FC<WardPatientPendingOrderProps> = ({
16
+ wardPatient,
17
+ orderUuid,
18
+ label,
19
+ onOrderCount,
20
+ }) => {
21
+ const { t } = useTranslation();
22
+ const { count, isLoading } = usePatientPendingOrders(
23
+ wardPatient?.patient?.uuid,
24
+ orderUuid,
25
+ wardPatient?.visit?.startDatetime.split('T')[0],
26
+ );
27
+
28
+ useEffect(() => {
29
+ if (!isLoading) {
30
+ onOrderCount(count); // Notify parent when count is available
31
+ }
32
+ }, [count, isLoading, onOrderCount]);
33
+
34
+ if (isLoading || !count || count == 0) {
35
+ return null;
36
+ }
37
+
38
+ const labelToDisplay = label ? t(label) : t('Orders', 'Orders');
39
+ return (
40
+ <div className={styles.wardPatientCardDispositionTypeContainer}>
41
+ <ChemistryReference className={styles.chemistryReferenceIcon} size="24" />
42
+ {count} {labelToDisplay}
43
+ </div>
44
+ );
45
+ };
@@ -0,0 +1,38 @@
1
+ import React, { useMemo } from 'react';
2
+ import { Movement } from '@carbon/react/icons';
3
+ import styles from '../ward-patient-card.scss';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { type WardPatient } from '../../types';
6
+
7
+ export interface WardPatientTransferProps {
8
+ wardPatient: WardPatient;
9
+ }
10
+
11
+ const WardPatientPendingTransfer: React.FC<WardPatientTransferProps> = ({ wardPatient }) => {
12
+ const { t } = useTranslation();
13
+
14
+ const { dispositionType, dispositionLocation } = wardPatient?.inpatientRequest;
15
+ const message = useMemo(() => {
16
+ if (dispositionType === 'TRANSFER') {
17
+ if (dispositionLocation) {
18
+ return t('transferToDispositionLocation', 'Transfer to {{location}}', { location: dispositionLocation.name });
19
+ }
20
+ return t('pendingTransfer', 'Pending Transfer');
21
+ }
22
+ if (dispositionType === 'DISCHARGE') {
23
+ return t('pendingDischarge', 'Pending Discharge');
24
+ }
25
+ return '';
26
+ }, [dispositionType, dispositionLocation]);
27
+
28
+ if (!(dispositionType === 'TRANSFER' || dispositionType === 'DISCHARGE')) return null;
29
+
30
+ return (
31
+ <div className={styles.wardPatientCardDispositionTypeContainer}>
32
+ <Movement className={styles.movementIcon} size="24" />
33
+ {message}
34
+ </div>
35
+ );
36
+ };
37
+
38
+ export default WardPatientPendingTransfer;
@@ -0,0 +1,32 @@
1
+ import { Toggletip, ToggletipButton, ToggletipContent, Tooltip } from '@carbon/react';
2
+ import { isDesktop, useLayoutType } from '@openmrs/esm-framework';
3
+ import React, { type ReactNode } from 'react';
4
+ import styles from '../ward-patient-card.scss';
5
+
6
+ interface WardPatientResponsiveTooltipProps {
7
+ children: ReactNode;
8
+ tooltipContent: ReactNode;
9
+ }
10
+
11
+ /**
12
+ * This component acts as a Tooltip on desktop and a Toggletip on mobile.
13
+ */
14
+ const WardPatientResponsiveTooltip: React.FC<WardPatientResponsiveTooltipProps> = ({ children, tooltipContent }) => {
15
+ const layout = useLayoutType();
16
+ if (isDesktop(layout)) {
17
+ return (
18
+ <Tooltip description={tooltipContent} className={styles.responsiveTooltip}>
19
+ {children}
20
+ </Tooltip>
21
+ );
22
+ } else {
23
+ return (
24
+ <Toggletip className={styles.responsiveTooltip}>
25
+ <ToggletipButton>{children}</ToggletipButton>
26
+ <ToggletipContent>{tooltipContent}</ToggletipContent>
27
+ </Toggletip>
28
+ );
29
+ }
30
+ };
31
+
32
+ export default WardPatientResponsiveTooltip;
@@ -0,0 +1,9 @@
1
+ import { SkeletonIcon } from '@carbon/react';
2
+ import React from 'react';
3
+ import styles from '../ward-patient-card.scss';
4
+
5
+ const WardPatientSkeletonText = () => {
6
+ return <SkeletonIcon className={styles.skeletonText} />;
7
+ };
8
+
9
+ export default WardPatientSkeletonText;