@openmrs/esm-ward-app 7.0.1-pre.60 → 7.1.0

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 (77) hide show
  1. package/.turbo/turbo-build.log +23 -27
  2. package/dist/130.js +1 -1
  3. package/dist/130.js.map +1 -1
  4. package/dist/152.js +1 -0
  5. package/dist/152.js.map +1 -0
  6. package/dist/255.js +2 -0
  7. package/dist/{697.js.LICENSE.txt → 255.js.LICENSE.txt} +0 -6
  8. package/dist/255.js.map +1 -0
  9. package/dist/303.js +1 -0
  10. package/dist/303.js.map +1 -0
  11. package/dist/331.js +1 -0
  12. package/dist/331.js.map +1 -0
  13. package/dist/430.js +1 -0
  14. package/dist/430.js.map +1 -0
  15. package/dist/574.js +1 -1
  16. package/dist/747.js +1 -0
  17. package/dist/747.js.map +1 -0
  18. package/dist/836.js +2 -0
  19. package/dist/836.js.LICENSE.txt +5 -0
  20. package/dist/836.js.map +1 -0
  21. package/dist/main.js +1 -1
  22. package/dist/main.js.map +1 -1
  23. package/dist/openmrs-esm-ward-app.js +1 -1
  24. package/dist/openmrs-esm-ward-app.js.buildmanifest.json +190 -46
  25. package/dist/openmrs-esm-ward-app.js.map +1 -1
  26. package/dist/routes.json +1 -1
  27. package/package.json +2 -3
  28. package/src/beds/occupied-bed.component.tsx +7 -12
  29. package/src/beds/occupied-bed.scss +1 -1
  30. package/src/beds/occupied-bed.test.tsx +14 -5
  31. package/src/config-schema.ts +173 -7
  32. package/src/constant.ts +1 -0
  33. package/src/createDashboardLink.component.tsx +42 -0
  34. package/src/hooks/useAdmissionLocation.ts +4 -5
  35. package/src/hooks/useAdmittedPatients.ts +13 -0
  36. package/src/hooks/useConcept.ts +11 -0
  37. package/src/hooks/useInpatientRequest.ts +15 -0
  38. package/src/hooks/useLocation.test.ts +38 -0
  39. package/src/hooks/useLocation.ts +9 -0
  40. package/src/hooks/useObs.ts +21 -0
  41. package/src/hooks/useWardLocation.test.ts +78 -0
  42. package/src/hooks/useWardLocation.ts +26 -0
  43. package/src/index.ts +24 -3
  44. package/src/root.component.tsx +2 -1
  45. package/src/routes.json +37 -7
  46. package/src/types/index.ts +40 -2
  47. package/src/ward-patient-card/row-elements/ward-patient-age.tsx +3 -1
  48. package/src/ward-patient-card/row-elements/ward-patient-bed-number.tsx +3 -0
  49. package/src/ward-patient-card/row-elements/ward-patient-coded-obs-tags.tsx +80 -0
  50. package/src/ward-patient-card/row-elements/ward-patient-header-address.tsx +3 -1
  51. package/src/ward-patient-card/row-elements/ward-patient-obs.resource.ts +52 -0
  52. package/src/ward-patient-card/row-elements/ward-patient-obs.tsx +58 -0
  53. package/src/ward-patient-card/ward-patient-card-row.resources.tsx +12 -5
  54. package/src/ward-patient-card/ward-patient-card.scss +43 -0
  55. package/src/ward-patient-card/ward-patient-card.tsx +12 -2
  56. package/src/ward-patient-workspace/ward-patient-action-button.extension.tsx +18 -0
  57. package/src/ward-patient-workspace/ward-patient.style.scss +11 -0
  58. package/src/ward-patient-workspace/ward-patient.workspace.tsx +79 -0
  59. package/src/ward-view/ward-bed.component.tsx +6 -9
  60. package/src/ward-view/ward-view.component.tsx +80 -39
  61. package/src/ward-view/ward-view.scss +0 -15
  62. package/src/ward-view/ward-view.test.tsx +38 -25
  63. package/src/ward-view-header/admission-requests-bar.component.tsx +50 -0
  64. package/src/ward-view-header/admission-requests-bar.test.tsx +43 -0
  65. package/src/ward-view-header/admission-requests.scss +33 -0
  66. package/src/ward-view-header/ward-view-header.component.tsx +18 -0
  67. package/src/ward-view-header/ward-view-header.scss +8 -0
  68. package/src/ward-workspace/admission-request-card.component.tsx +23 -0
  69. package/src/ward-workspace/admission-request-card.scss +34 -0
  70. package/src/ward-workspace/admission-request-workspace.test.tsx +32 -0
  71. package/src/ward-workspace/admission-requests-workspace.component.tsx +21 -0
  72. package/src/ward-workspace/admission-requests-workspace.scss +13 -0
  73. package/translations/en.json +6 -1
  74. package/dist/49.js +0 -1
  75. package/dist/49.js.map +0 -1
  76. package/dist/697.js +0 -2
  77. package/dist/697.js.map +0 -1
@@ -10,6 +10,7 @@ import type React from 'react';
10
10
 
11
11
  export interface WardPatientCardProps {
12
12
  patient: Patient;
13
+ visit: Visit;
13
14
  bed: Bed;
14
15
  }
15
16
 
@@ -21,10 +22,16 @@ export const patientCardElementTypes = [
21
22
  'patient-name',
22
23
  'patient-age',
23
24
  'patient-address',
25
+ 'patient-obs',
26
+ 'patient-coded-obs-tags',
24
27
  'admission-time',
25
28
  ] as const;
26
29
  export type PatientCardElementType = (typeof patientCardElementTypes)[number];
27
30
 
31
+ // a Ward Patient can either be a patient that is already admitted or a
32
+ // patient that is awaiting admission
33
+ export type WardPatient = (AdmittedPatient & { admitted: true }) | (InpatientRequest & { admitted: false });
34
+
28
35
  // server-side types defined in openmrs-module-bedmanagement:
29
36
 
30
37
  export interface AdmissionLocation {
@@ -52,7 +59,7 @@ export interface BedLayout {
52
59
  status: BedStatus;
53
60
  bedType: BedType;
54
61
  location: string;
55
- patient: Patient;
62
+ patients: Patient[];
56
63
  bedTagMaps: BedTagMap[];
57
64
  }
58
65
 
@@ -76,6 +83,36 @@ interface BedTagMap {
76
83
 
77
84
  export type BedStatus = 'AVAILABLE' | 'OCCUPIED';
78
85
 
86
+ // server-side types defined in openmrs-module-emrapi:
87
+
88
+ export type DispositionType = 'ADMISSION' | 'TRANSFER' | 'DISCHARGE';
89
+
90
+ // InpatientRequest[] returned by:
91
+ // GET /rest/emrapi/inpatient/admissionRequests
92
+ // GET /rest/emrapi/inpatient/transferRequests
93
+ // GET /rest/emrapi/inpatient/admissionAndTransferRequests
94
+ export interface InpatientRequest {
95
+ patient: Patient;
96
+ visit: Visit;
97
+ type: DispositionType;
98
+
99
+ // as of now, these fields are not included in the backend
100
+ encounter?: Encounter;
101
+ dispositionObs?: Observation;
102
+ dispositionLocation?: Location;
103
+ dispositionDate?: Date;
104
+ }
105
+
106
+ // AdmittedPatient[] returned by:
107
+ // GET /rest/emrapi/inpatient/visits
108
+ export interface AdmittedPatient {
109
+ patient: Patient;
110
+ visit: Visit;
111
+ currentLocation: Location;
112
+ timeSinceAdmissionInMinutes: number;
113
+ timeAtInpatientLocationInMinutes: number;
114
+ }
115
+
79
116
  // TODO: Move these types to esm-core
80
117
  export interface Observation extends OpenmrsResourceStrict {
81
118
  concept: OpenmrsResource;
@@ -83,6 +120,7 @@ export interface Observation extends OpenmrsResourceStrict {
83
120
  obsDatetime: string;
84
121
  accessionNumber: string;
85
122
  obsGroup: Observation;
123
+ value: number | string | boolean | OpenmrsResource;
86
124
  valueCodedName: OpenmrsResource; // ConceptName
87
125
  groupMembers: Array<Observation>;
88
126
  comment: string;
@@ -98,7 +136,7 @@ export interface Encounter extends OpenmrsResourceStrict {
98
136
  location?: Location;
99
137
  form?: OpenmrsResource;
100
138
  encounterType?: EncounterType;
101
- obs?: Observation;
139
+ obs?: Array<Observation>;
102
140
  orders?: any;
103
141
  voided?: boolean;
104
142
  visit?: Visit;
@@ -6,12 +6,14 @@ const WardPatientAge: WardPatientCardElement = ({ patient }) => {
6
6
  const { t } = useTranslation();
7
7
  // TODO: BED-10
8
8
  // make the backend return patient.person.birthdate so we can use the age() function to calculate age
9
- return (
9
+ return patient?.person?.age ? (
10
10
  <div>
11
11
  {t('yearsOld', '{{age}} yrs', {
12
12
  age: patient?.person?.age,
13
13
  })}
14
14
  </div>
15
+ ) : (
16
+ <></>
15
17
  );
16
18
  };
17
19
 
@@ -3,6 +3,9 @@ import styles from '../ward-patient-card.scss';
3
3
  import { type WardPatientCardElement } from '../../types';
4
4
 
5
5
  const WardPatientBedNumber: WardPatientCardElement = ({ bed }) => {
6
+ if (!bed) {
7
+ return <></>;
8
+ }
6
9
  return (
7
10
  <div className={styles.bedNumberBox}>
8
11
  <span className={styles.wardPatientBedNumber}>{bed.bedNumber}</span>
@@ -0,0 +1,80 @@
1
+ import { SkeletonText, Tag } from '@carbon/react';
2
+ import { translateFrom, type OpenmrsResource } from '@openmrs/esm-framework';
3
+ import React from 'react';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { type PatientCodedObsTagsElementConfig } from '../../config-schema';
6
+ import { moduleName } from '../../constant';
7
+ import { useObs } from '../../hooks/useObs';
8
+ import { type WardPatientCardElement } from '../../types';
9
+ import styles from '../ward-patient-card.scss';
10
+ import { obsCustomRepresentation, useConceptToTagColorMap } from './ward-patient-obs.resource';
11
+
12
+ /**
13
+ * The WardPatientCodedObsTags displays observations of coded values of a particular concept in the active visit as tags.
14
+ * Typically, these are taken from checkbox fields from a form. Each answer value can either be configured
15
+ * to show as its own tag, or collapsed into a summary tag show the number of these values present.
16
+ *
17
+ * This is a rather specialized element;
18
+ * for a more general display of obs value, use WardPatientObs instead.
19
+ * @param config
20
+ * @returns
21
+ */
22
+ const wardPatientCodedObsTags = (config: PatientCodedObsTagsElementConfig) => {
23
+ const WardPatientCodedObsTags: WardPatientCardElement = ({ patient, visit }) => {
24
+ const { conceptUuid, summaryLabel, summaryLabelColor, summaryLabelI18nModule } = config;
25
+ const { data, isLoading } = useObs({ patient: patient.uuid, concept: conceptUuid }, obsCustomRepresentation);
26
+ const { t } = useTranslation();
27
+ const { data: conceptToTagColorMap } = useConceptToTagColorMap(config);
28
+
29
+ if (isLoading) {
30
+ return <SkeletonText />;
31
+ } else {
32
+ const obsToDisplay = data?.data?.results?.filter((o) => {
33
+ const matchVisit = o.encounter.visit?.uuid == visit?.uuid;
34
+ return matchVisit || visit == null; // TODO: remove visit == null hack when server API supports returning visit
35
+ });
36
+
37
+ const summaryLabelToDisplay =
38
+ summaryLabel != null
39
+ ? translateFrom(summaryLabelI18nModule ?? moduleName, summaryLabel)
40
+ : obsToDisplay?.[0]?.concept?.display;
41
+
42
+ const obsNodes = obsToDisplay?.map((o) => {
43
+ const { display, uuid } = o.value as OpenmrsResource;
44
+
45
+ const color = conceptToTagColorMap?.get(uuid);
46
+ if (color) {
47
+ return (
48
+ <Tag type={color} key={`ward-coded-obs-tag-${o.uuid}`}>
49
+ {display}
50
+ </Tag>
51
+ );
52
+ } else {
53
+ return null;
54
+ }
55
+ });
56
+
57
+ const obsWithNoTagCount = obsNodes.filter((o) => o == null).length;
58
+ if (obsNodes?.length > 0 || obsWithNoTagCount > 0) {
59
+ return (
60
+ <div>
61
+ <span className={styles.wardPatientObsLabel}>
62
+ {obsNodes}
63
+ {obsWithNoTagCount > 0 ? (
64
+ <Tag type={summaryLabelColor}>
65
+ {t('countItems', '{{count}} {{item}}', { count: obsWithNoTagCount, item: summaryLabelToDisplay })}
66
+ </Tag>
67
+ ) : null}
68
+ </span>
69
+ </div>
70
+ );
71
+ } else {
72
+ return null;
73
+ }
74
+ }
75
+ };
76
+
77
+ return WardPatientCodedObsTags;
78
+ };
79
+
80
+ export default wardPatientCodedObsTags;
@@ -11,7 +11,9 @@ const wardPatientAddress = (config: PatientAddressElementConfig) => {
11
11
 
12
12
  return (
13
13
  <div className={styles.wardPatientAddress}>
14
- {addressFields?.map((field, i) => <div key={i}>{preferredAddress?.[field] as string}</div>)}
14
+ {addressFields?.map((field, i) =>
15
+ preferredAddress?.[field] ? <div key={i}>{preferredAddress?.[field] as string}</div> : <></>,
16
+ )}
15
17
  </div>
16
18
  );
17
19
  };
@@ -0,0 +1,52 @@
1
+ import { openmrsFetch, restBaseUrl, type Concept } from '@openmrs/esm-framework';
2
+ import useSWRImmutable from 'swr/immutable';
3
+ import { type PatientCodedObsTagsElementConfig } from '../../config-schema';
4
+
5
+ // prettier-ignore
6
+ export const obsCustomRepresentation =
7
+ 'custom:(uuid,display,obsDatetime,value,' +
8
+ 'concept:(uuid,display),' +
9
+ 'encounter:(uuid,display,' +
10
+ 'visit:(uuid,display)))';
11
+
12
+ // get the setMembers of a concept set
13
+ const conceptSetCustomRepresentation = 'custom:(uuid,setMembers:(uuid))';
14
+
15
+ export function useConceptToTagColorMap(codedObsTagsConfig: PatientCodedObsTagsElementConfig) {
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 codedObsTagsConfig.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
+ }
29
+
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
+ }
41
+ }
42
+
43
+ return conceptToTagColorMap;
44
+ });
45
+ };
46
+
47
+ const conceptSetUuids = codedObsTagsConfig.tags.flatMap((tag) => tag.appliedToConceptSets);
48
+ const apiUrl = `${restBaseUrl}/concept?references=${conceptSetUuids.join()}&v=${conceptSetCustomRepresentation}`;
49
+ const conceptToTagColorMap = useSWRImmutable(apiUrl, fetchAndMap);
50
+
51
+ return conceptToTagColorMap;
52
+ }
@@ -0,0 +1,58 @@
1
+ import { SkeletonText, Tag } from '@carbon/react';
2
+ import { type OpenmrsResource, translateFrom } from '@openmrs/esm-framework';
3
+ import React from 'react';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { type PatientObsElementConfig } from '../../config-schema';
6
+ import { useObs } from '../../hooks/useObs';
7
+ import { type WardPatientCardElement } from '../../types';
8
+ import styles from '../ward-patient-card.scss';
9
+ import { moduleName } from '../../constant';
10
+ import { obsCustomRepresentation } from './ward-patient-obs.resource';
11
+
12
+ const wardPatientObs = (config: PatientObsElementConfig) => {
13
+ const WardPatientObs: WardPatientCardElement = ({ patient, visit }) => {
14
+ const { conceptUuid, onlyWithinCurrentVisit, orderBy, limit, label, labelI18nModule: labelModule } = config;
15
+ const { data, isLoading } = useObs({ patient: patient.uuid, concept: conceptUuid }, obsCustomRepresentation);
16
+ const { t } = useTranslation();
17
+
18
+ if (isLoading) {
19
+ return <SkeletonText />;
20
+ } else {
21
+ const obsToDisplay = data?.data?.results
22
+ ?.filter((o) => {
23
+ const matchVisit = !onlyWithinCurrentVisit || o.encounter.visit?.uuid == visit?.uuid;
24
+ return matchVisit;
25
+ })
26
+ ?.sort((obsA, obsB) => {
27
+ return (orderBy == 'descending' ? -1 : 1) * obsA.obsDatetime.localeCompare(obsB.obsDatetime);
28
+ })
29
+ ?.slice(0, limit ?? Number.MAX_VALUE);
30
+
31
+ const labelToDisplay =
32
+ label != null ? translateFrom(labelModule ?? moduleName, label) : obsToDisplay?.[0]?.concept?.display;
33
+
34
+ const obsNodes = obsToDisplay?.map((o) => {
35
+ const { value } = o;
36
+ const display: any = (value as OpenmrsResource)?.display ?? o.value;
37
+ return <span key={o.uuid}> {display} </span>;
38
+ });
39
+
40
+ if (obsNodes?.length > 0) {
41
+ return (
42
+ <div>
43
+ <span className={styles.wardPatientObsLabel}>
44
+ {labelToDisplay ? t('labelColon', '{{label}}:', { label: labelToDisplay }) : ''}
45
+ </span>
46
+ {obsNodes}
47
+ </div>
48
+ );
49
+ } else {
50
+ return null;
51
+ }
52
+ }
53
+ };
54
+
55
+ return WardPatientObs;
56
+ };
57
+
58
+ export default wardPatientObs;
@@ -13,6 +13,8 @@ import wardPatientAddress from './row-elements/ward-patient-header-address';
13
13
  import WardPatientName from './row-elements/ward-patient-name';
14
14
  import React from 'react';
15
15
  import styles from './ward-patient-card.scss';
16
+ import wardPatientObs from './row-elements/ward-patient-obs';
17
+ import wardPatientCodedObsTags from './row-elements/ward-patient-coded-obs-tags';
16
18
 
17
19
  export function usePatientCardRows(location: string) {
18
20
  const { wardPatientCards } = useConfig<WardConfigObject>();
@@ -48,11 +50,11 @@ export function usePatientCardRows(location: string) {
48
50
  return slot;
49
51
  });
50
52
 
51
- const WardPatientCardRow: React.FC<WardPatientCardProps> = ({ patient, bed }) => {
53
+ const WardPatientCardRow: React.FC<WardPatientCardProps> = (props) => {
52
54
  return (
53
- <div className={rowType == 'header' ? styles.wardPatientCardHeader : ''}>
55
+ <div className={styles.wardPatientCardRow + ' ' + (rowType == 'header' ? styles.wardPatientCardHeader : '')}>
54
56
  {patientCardElements.map((PatientCardElement, i) => (
55
- <PatientCardElement patient={patient} bed={bed} key={i} />
57
+ <PatientCardElement {...props} key={i} />
56
58
  ))}
57
59
  </div>
58
60
  );
@@ -78,8 +80,13 @@ function getPatientCardElementFromDefinition(
78
80
  case 'patient-age':
79
81
  return WardPatientAge;
80
82
  case 'patient-address': {
81
- // TODO: configure address fields to pass in
82
- return wardPatientAddress(config);
83
+ return wardPatientAddress(config.address);
84
+ }
85
+ case 'patient-obs': {
86
+ return wardPatientObs(config.obs);
87
+ }
88
+ case 'patient-coded-obs-tags': {
89
+ return wardPatientCodedObsTags(config.codedObsTags);
83
90
  }
84
91
  }
85
92
  }
@@ -1,5 +1,6 @@
1
1
  @use '@carbon/styles/scss/spacing';
2
2
  @use '@carbon/styles/scss/type';
3
+ @use '@carbon/colors';
3
4
  @import '~@openmrs/esm-styleguide/src/vars';
4
5
 
5
6
  .wardPatientCard {
@@ -11,9 +12,47 @@
11
12
  align-items: center;
12
13
  gap: spacing.$spacing-02;
13
14
  background-color: $ui-02;
15
+
16
+ position: relative; // this allows positioning the button correctly
17
+
18
+ > .wardPatientCardRow:not(:first-child) {
19
+ border-top: 1px colors.$gray-20 solid;
20
+ }
21
+ }
22
+
23
+ .wardPatientCardButton {
24
+ border: none;
25
+ padding: 0;
26
+
27
+ &::before {
28
+ content: '';
29
+ position: absolute;
30
+ inset: 0;
31
+ z-index: 1;
32
+ cursor: pointer;
33
+ border: 2px solid transparent;
34
+ transition: border-color 200ms;
35
+ }
36
+
37
+ &:hover::before,
38
+ &:focus::before {
39
+ border-color: $interactive-01;
40
+ }
41
+
42
+ &:focus {
43
+ outline: none;
44
+ }
45
+ }
46
+
47
+ .wardPatientCardRow {
48
+ width: 100%;
14
49
  padding: spacing.$spacing-04;
15
50
  }
16
51
 
52
+ .wardPatientCardRow:empty {
53
+ display: none;
54
+ }
55
+
17
56
  .wardPatientCardHeader {
18
57
  @extend .dotSeparatedChildren;
19
58
  display: flex;
@@ -59,6 +98,10 @@
59
98
  gap: spacing.$spacing-02;
60
99
  }
61
100
 
101
+ .wardPatientObsLabel {
102
+ padding-right: spacing.$spacing-02;
103
+ }
104
+
62
105
  .dotSeparatedChildren {
63
106
  > div:not(div:first-of-type) {
64
107
  display: flex;
@@ -3,6 +3,8 @@ import { useParams } from 'react-router-dom';
3
3
  import { type WardPatientCardProps } from '../types';
4
4
  import { usePatientCardRows } from './ward-patient-card-row.resources';
5
5
  import styles from './ward-patient-card.scss';
6
+ import { getPatientName, launchWorkspace } from '@openmrs/esm-framework';
7
+ import { type WardPatientWorkspaceProps } from '../ward-patient-workspace/ward-patient.workspace';
6
8
 
7
9
  const WardPatientCard: React.FC<WardPatientCardProps> = (props) => {
8
10
  const { locationUuid } = useParams();
@@ -10,9 +12,17 @@ const WardPatientCard: React.FC<WardPatientCardProps> = (props) => {
10
12
 
11
13
  return (
12
14
  <div className={styles.wardPatientCard}>
13
- {patientCardRows.map((WardPatientCardRow) => (
14
- <WardPatientCardRow {...props} />
15
+ {patientCardRows.map((WardPatientCardRow, i) => (
16
+ <WardPatientCardRow key={i} {...props} />
15
17
  ))}
18
+ <button
19
+ className={styles.wardPatientCardButton}
20
+ onClick={() =>
21
+ launchWorkspace<WardPatientWorkspaceProps>('ward-patient-workspace', { patientUuid: props.patient.uuid })
22
+ }>
23
+ {/* Name will not be displayed; just there for a11y */}
24
+ {getPatientName(props.patient.person)}
25
+ </button>
16
26
  </div>
17
27
  );
18
28
  };
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { UserAvatarIcon } from '@openmrs/esm-framework';
4
+ import { ActionMenuButton, launchWorkspace } from '@openmrs/esm-framework';
5
+
6
+ export default function WardPatientActionButton() {
7
+ const { t } = useTranslation();
8
+
9
+ return (
10
+ <ActionMenuButton
11
+ getIcon={(props) => <UserAvatarIcon {...props} />}
12
+ label={t('Patient', 'patient')}
13
+ iconDescription={t('Patient', 'patient')}
14
+ handler={() => launchWorkspace('ward-patient-workspace')}
15
+ type={'ward'}
16
+ />
17
+ );
18
+ }
@@ -0,0 +1,11 @@
1
+ @use '@carbon/styles/scss/spacing';
2
+ @use '@carbon/styles/scss/type';
3
+
4
+ .workspaceContainer {
5
+ min-height: var(--desktop-workspace-window-height);
6
+ }
7
+
8
+ .headerPatientDetail {
9
+ @include type.type-style('body-compact-02');
10
+ margin: 0 spacing.$spacing-02;
11
+ }
@@ -0,0 +1,79 @@
1
+ import React, { useEffect, useMemo } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { InlineNotification } from '@carbon/react';
4
+ import { InlineLoading } from '@carbon/react';
5
+ import {
6
+ type DefaultWorkspaceProps,
7
+ ExtensionSlot,
8
+ attach,
9
+ getPatientName,
10
+ usePatient,
11
+ age,
12
+ } from '@openmrs/esm-framework';
13
+ import styles from './ward-patient.style.scss';
14
+
15
+ attach('ward-patient-workspace-header-slot', 'patient-vitals-info');
16
+
17
+ export interface WardPatientWorkspaceProps extends DefaultWorkspaceProps {
18
+ patientUuid: string;
19
+ }
20
+
21
+ export default function WardPatientWorkspace({ patientUuid, setTitle }: WardPatientWorkspaceProps) {
22
+ const { t } = useTranslation();
23
+ const { patient, isLoading, error } = usePatient(patientUuid);
24
+
25
+ useEffect(() => {
26
+ if (isLoading) {
27
+ setTitle(t('wardPatientWorkspaceTitle', 'Ward Patient'), <InlineLoading />);
28
+ } else if (patient) {
29
+ setTitle(getPatientName(patient), <PatientWorkspaceTitle patient={patient} />);
30
+ } else if (error) {
31
+ setTitle(t('wardPatientWorkspaceTitle', 'Ward Patient'));
32
+ }
33
+ }, [patient]);
34
+
35
+ return (
36
+ <div className={styles.workspaceContainer}>
37
+ {isLoading ? (
38
+ <InlineLoading />
39
+ ) : patient ? (
40
+ <WardPatientWorkspaceView patient={patient} />
41
+ ) : error ? (
42
+ <InlineNotification>{error.message}</InlineNotification>
43
+ ) : (
44
+ <InlineNotification>
45
+ {t('failedToLoadPatientWorkspace', 'Ward patient workspace has failed to load.')}
46
+ </InlineNotification>
47
+ )}
48
+ </div>
49
+ );
50
+ }
51
+
52
+ interface WardPatientWorkspaceViewProps {
53
+ patient: fhir.Patient;
54
+ }
55
+
56
+ function WardPatientWorkspaceView({ patient }: WardPatientWorkspaceViewProps) {
57
+ const extensionSlotState = useMemo(() => ({ patient, patientUuid: patient.id }), [patient]);
58
+
59
+ return (
60
+ <>
61
+ <div>
62
+ <ExtensionSlot name="ward-patient-workspace-header-slot" state={extensionSlotState} />
63
+ </div>
64
+ <div>
65
+ <ExtensionSlot name="ward-patient-workspace-content-slot" state={extensionSlotState} />
66
+ </div>
67
+ </>
68
+ );
69
+ }
70
+
71
+ function PatientWorkspaceTitle({ patient }: { patient: fhir.Patient }) {
72
+ return (
73
+ <>
74
+ <div>{getPatientName(patient)} &nbsp;</div>
75
+ <div className={styles.headerPatientDetail}>&middot; &nbsp; {patient.gender}</div>
76
+ <div className={styles.headerPatientDetail}>&middot; &nbsp; {age(patient.birthDate)}</div>
77
+ </>
78
+ );
79
+ }
@@ -1,18 +1,15 @@
1
- import { type Patient } from '@openmrs/esm-framework';
1
+ import { type Visit, type Patient } from '@openmrs/esm-framework';
2
2
  import React from 'react';
3
3
  import EmptyBed from '../beds/empty-bed.component';
4
- import { type Bed } from '../types';
4
+ import { type WardPatient, type Bed } from '../types';
5
5
  import OccupiedBed from '../beds/occupied-bed.component';
6
-
7
- interface WardBedProps {
6
+ export interface WardBedProps {
7
+ wardPatients: Array<WardPatient>;
8
8
  bed: Bed;
9
- patients: Patient[];
10
9
  }
11
10
 
12
- const WardBed: React.FC<WardBedProps> = ({ bed, patients }) => {
13
- return patients?.length > 0 ?
14
- <OccupiedBed bed={bed} patients={patients} />
15
- : <EmptyBed bed={bed} />;
11
+ const WardBed: React.FC<WardBedProps> = ({ bed, wardPatients }) => {
12
+ return wardPatients?.length > 0 ? <OccupiedBed bed={bed} wardPatients={wardPatients} /> : <EmptyBed bed={bed} />;
16
13
  };
17
14
 
18
15
  export default WardBed;