@palladium-ethiopia/esm-clinical-workflow-app 5.4.2-pre.26 → 5.4.2-pre.31

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 (74) hide show
  1. package/.turbo/turbo-build.log +5 -5
  2. package/dist/152.js +1 -1
  3. package/dist/152.js.map +1 -1
  4. package/dist/159.js +1 -1
  5. package/dist/159.js.map +1 -1
  6. package/dist/160.js +1 -0
  7. package/dist/160.js.map +1 -0
  8. package/dist/164.js +1 -1
  9. package/dist/164.js.map +1 -1
  10. package/dist/208.js +1 -1
  11. package/dist/208.js.map +1 -1
  12. package/dist/209.js +1 -1
  13. package/dist/209.js.map +1 -1
  14. package/dist/313.js +1 -0
  15. package/dist/313.js.map +1 -0
  16. package/dist/330.js +1 -0
  17. package/dist/330.js.map +1 -0
  18. package/dist/363.js +1 -1
  19. package/dist/363.js.map +1 -1
  20. package/dist/416.js +1 -0
  21. package/dist/416.js.map +1 -0
  22. package/dist/442.js +1 -1
  23. package/dist/442.js.map +1 -1
  24. package/dist/466.js +1 -1
  25. package/dist/466.js.map +1 -1
  26. package/dist/485.js +1 -0
  27. package/dist/485.js.map +1 -0
  28. package/dist/534.js +1 -1
  29. package/dist/534.js.map +1 -1
  30. package/dist/61.js +1 -1
  31. package/dist/61.js.map +1 -1
  32. package/dist/677.js +1 -1
  33. package/dist/677.js.map +1 -1
  34. package/dist/689.js +1 -1
  35. package/dist/689.js.map +1 -1
  36. package/dist/697.js +1 -1
  37. package/dist/697.js.map +1 -1
  38. package/dist/712.js +1 -1
  39. package/dist/712.js.map +1 -1
  40. package/dist/771.js +1 -1
  41. package/dist/771.js.map +1 -1
  42. package/dist/789.js +1 -1
  43. package/dist/789.js.map +1 -1
  44. package/dist/914.js +9 -9
  45. package/dist/914.js.map +1 -1
  46. package/dist/926.js +2 -2
  47. package/dist/926.js.map +1 -1
  48. package/dist/ethiopia-esm-clinical-workflow-app.js +5 -5
  49. package/dist/ethiopia-esm-clinical-workflow-app.js.buildmanifest.json +144 -48
  50. package/dist/ethiopia-esm-clinical-workflow-app.js.map +1 -1
  51. package/dist/main.js +13 -13
  52. package/dist/main.js.map +1 -1
  53. package/dist/routes.json +1 -1
  54. package/package.json +1 -1
  55. package/src/config-schema.ts +21 -0
  56. package/src/index.ts +23 -0
  57. package/src/patient-chart/visit/visit-history-table/diagnosis-tags.module.scss +1 -1
  58. package/src/patient-notes/visit-notes-form.scss +8 -8
  59. package/src/patient-transfer/confirm-transfer-dialog.modal.tsx +58 -0
  60. package/src/patient-transfer/patient-transfer-action-button.extension.tsx +126 -0
  61. package/src/patient-transfer/queue-table-transfer-column.component.tsx +40 -0
  62. package/src/patient-transfer/transfer-data.resource.ts +66 -0
  63. package/src/patient-transfer/transfer-details.modal.tsx +42 -0
  64. package/src/patient-transfer/transfer-details.scss +18 -0
  65. package/src/patient-transfer/transfer-notes-overview.extension.tsx +63 -0
  66. package/src/patient-transfer/transfer-notes-overview.scss +88 -0
  67. package/src/patient-transfer/transfer-notes-table.component.tsx +205 -0
  68. package/src/patient-transfer/transfer-notes.resource.ts +93 -0
  69. package/src/routes.json +37 -0
  70. package/src/types/index.ts +15 -0
  71. package/translations/am.json +8 -0
  72. package/translations/en.json +8 -0
  73. package/dist/825.js +0 -1
  74. package/dist/825.js.map +0 -1
package/dist/routes.json CHANGED
@@ -1 +1 @@
1
- {"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"fhir2":">=1.2","webservices.rest":"^2.24.0"},"pages":[],"extensions":[{"name":"clinical-workflow-triage-dashboard-link","component":"triageDashboardLink","slot":"homepage-dashboard-slot","order":0,"meta":{"name":"clinical-workflow-triage","slot":"clinical-workflow-triage-dashboard-slot","title":"clinical-workflow-triage"}},{"name":"clinical-workflow-triage-dashboard","component":"triageDashboard","slot":"clinical-workflow-triage-dashboard-slot"},{"name":"ewf-mru-dashboard-link","component":"mruLeftPanelLink","slot":"homepage-dashboard-slot","order":1,"meta":{"name":"mru","title":"MRU","path":"mru","slot":"mru-dashboard-slot"}},{"name":"ewf-mru-dashboard","component":"mruDashboard","slot":"mru-dashboard-slot","online":true,"offline":false},{"name":"patient-scoreboard-link","component":"patientScoreboardLink","slot":"homepage-dashboard-slot","order":1,"meta":{"name":"patient-scoreboard","title":"Patient Scoreboard","path":"patient-scoreboard","slot":"patient-scoreboard-slot"}},{"name":"patient-scoreboard","component":"patientScoreboard","slot":"patient-scoreboard-slot","online":true,"offline":false},{"name":"past-visits-detail-overview-shadow","slot":"patient-chart-encounters-dashboard-slot","component":"pastVisitsDetailOverviewShadow","order":0,"meta":{"title":"Visits","view":"visits"},"online":true,"offline":true}],"workspaces":[{"name":"triage-workspace","component":"triageWorkspace","title":"Triage Form","canMaximize":true,"type":"clinical-form"},{"name":"patient-registration-workspace","component":"patientRegistrationWorkspace","title":"Patient Registration","canMaximize":true,"type":"registration-form"},{"name":"billing-information-workspace","component":"billingInformationWorkspace","title":"Billing Information Workspace","type":"clinical-form"}],"workspaces2":[{"name":"clinical-workflow-patient-form-entry-workspace","component":"@openmrs/esm-patient-forms-app#exportedPatientFormEntryWorkspace","window":"clinical-workflow-window"},{"name":"visit-notes-form-shadow-workspace","component":"visitNotesFormWorkspace","window":"visit-note-shadow"}],"workspaceWindows2":[{"name":"clinical-workflow-window","group":"clinical-workflow-group","width":"wider","canMaximize":true},{"name":"visit-note-shadow","group":"patient-chart","icon":"visitNoteActionButton","order":3}],"workspaceGroups2":[{"name":"clinical-workflow-group","overlay":true,"persistence":"closable"}],"version":"5.4.2-pre.26"}
1
+ {"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"fhir2":">=1.2","webservices.rest":"^2.24.0"},"pages":[],"extensions":[{"name":"clinical-workflow-triage-dashboard-link","component":"triageDashboardLink","slot":"homepage-dashboard-slot","order":0,"meta":{"name":"clinical-workflow-triage","slot":"clinical-workflow-triage-dashboard-slot","title":"clinical-workflow-triage"}},{"name":"clinical-workflow-triage-dashboard","component":"triageDashboard","slot":"clinical-workflow-triage-dashboard-slot"},{"name":"ewf-mru-dashboard-link","component":"mruLeftPanelLink","slot":"homepage-dashboard-slot","order":1,"meta":{"name":"mru","title":"MRU","path":"mru","slot":"mru-dashboard-slot"}},{"name":"ewf-mru-dashboard","component":"mruDashboard","slot":"mru-dashboard-slot","online":true,"offline":false},{"name":"patient-scoreboard-link","component":"patientScoreboardLink","slot":"homepage-dashboard-slot","order":1,"meta":{"name":"patient-scoreboard","title":"Patient Scoreboard","path":"patient-scoreboard","slot":"patient-scoreboard-slot"}},{"name":"patient-scoreboard","component":"patientScoreboard","slot":"patient-scoreboard-slot","online":true,"offline":false},{"name":"past-visits-detail-overview-shadow","slot":"patient-chart-encounters-dashboard-slot","component":"pastVisitsDetailOverviewShadow","order":0,"meta":{"title":"Visits","view":"visits"},"online":true,"offline":true},{"name":"queue-table-transfer-column","component":"queueTableTransferColumn","slot":"queue-table-transfer-status-slot"},{"name":"transfer-notes-overview-widget","component":"transferNotesOverview","slot":"patient-chart-summary-dashboard-slot","meta":{"fullWidth":false},"order":6}],"workspaces":[{"name":"triage-workspace","component":"triageWorkspace","title":"Triage Form","canMaximize":true,"type":"clinical-form"},{"name":"patient-registration-workspace","component":"patientRegistrationWorkspace","title":"Patient Registration","canMaximize":true,"type":"registration-form"},{"name":"billing-information-workspace","component":"billingInformationWorkspace","title":"Billing Information Workspace","type":"clinical-form"}],"workspaces2":[{"name":"clinical-workflow-patient-form-entry-workspace","component":"@openmrs/esm-patient-forms-app#exportedPatientFormEntryWorkspace","window":"clinical-workflow-window"},{"name":"patient-transfer-form-entry-workspace","component":"@openmrs/esm-patient-forms-app#exportedPatientFormEntryWorkspace","window":"patient-transfer"},{"name":"visit-notes-form-shadow-workspace","component":"visitNotesFormWorkspace","window":"visit-note-shadow"}],"workspaceWindows2":[{"name":"clinical-workflow-window","group":"clinical-workflow-group","width":"wider","canMaximize":true},{"name":"visit-note-shadow","group":"patient-chart","icon":"visitNoteActionButton","order":3},{"name":"patient-transfer","group":"patient-chart","icon":"patientTransferActionButton","order":4,"width":"wider","canMaximize":true}],"workspaceGroups2":[{"name":"clinical-workflow-group","overlay":true,"persistence":"closable"}],"modals":[{"name":"confirm-transfer-dialog","component":"confirmTransferDialog"},{"name":"patient-transfer-details-modal","component":"patientTransferDetailsModal"}],"version":"5.4.2-pre.31"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@palladium-ethiopia/esm-clinical-workflow-app",
3
- "version": "5.4.2-pre.26",
3
+ "version": "5.4.2-pre.31",
4
4
  "description": "Express workflow app for OpenMRS 3",
5
5
  "keywords": [
6
6
  "openmrs",
@@ -95,6 +95,21 @@ export const configSchema = {
95
95
  _description: "UUID for the 'Drug' order type to fetch medications",
96
96
  _default: '131168f4-15f5-102d-96e4-000c29c2a5d7',
97
97
  },
98
+ patientTransferFormUuid: {
99
+ _type: Type.UUID,
100
+ _description: 'UUID of the patient transfer form',
101
+ _default: '94d5788f-6aaf-4ef6-b56e-1c71749cfa3e',
102
+ },
103
+ transferEncounterTypeUuid: {
104
+ _type: Type.UUID,
105
+ _description: 'UUID of the Intra-Hospital Transfer encounter type',
106
+ _default: '7b68d557-85ef-4fc8-b767-4fa4f5eb5c23',
107
+ },
108
+ transferNoteConceptUuid: {
109
+ _type: Type.UUID,
110
+ _description: 'UUID of the Transfer Note concept',
111
+ _default: 'f4162fe3-f7e3-4062-9bb3-aa1a4b1044a5',
112
+ },
98
113
  };
99
114
 
100
115
  export type ClinicalWorkflowConfig = {
@@ -115,6 +130,9 @@ export type ClinicalWorkflowConfig = {
115
130
  identifierSourceUuid: string;
116
131
  defaultIdentifierTypeUuid: string;
117
132
  medicoLegalCasesAttributeTypeUuid: string;
133
+ patientTransferFormUuid: string;
134
+ transferEncounterTypeUuid: string;
135
+ transferNoteConceptUuid: string;
118
136
  };
119
137
 
120
138
  export interface VisitNoteConfig {
@@ -164,4 +182,7 @@ export interface ChartConfig {
164
182
  }>;
165
183
  otherConceptUuid: string;
166
184
  drugOrderTypeUUID: string;
185
+ patientTransferFormUuid: string;
186
+ transferEncounterTypeUuid: string;
187
+ transferNoteConceptUuid: string;
167
188
  }
package/src/index.ts CHANGED
@@ -7,6 +7,7 @@ import { spaBasePath } from './constants';
7
7
  import BillingInformationWorkspace from './mru/billing-information/billing-information.workspace';
8
8
  import PatientScoreboard from './patient-scoreboard/patient-scoreboard.component';
9
9
  import visitNotesActionButtonExtension from './patient-notes/visit-note-action-button.extension';
10
+ import patientTransferActionButtonExtension from './patient-transfer/patient-transfer-action-button.extension';
10
11
  import pastVisitsOverviewComponent from './patient-chart/visit/visits-widget/visit-detail-overview.component';
11
12
 
12
13
  const moduleName = '@ethiopia/esm-clinical-workflow-app';
@@ -71,6 +72,13 @@ export const patientScoreboard = getSyncLifecycle(PatientScoreboard, options);
71
72
 
72
73
  export const visitNoteActionButton = getSyncLifecycle(visitNotesActionButtonExtension, options);
73
74
 
75
+ export const patientTransferActionButton = getSyncLifecycle(patientTransferActionButtonExtension, options);
76
+
77
+ export const confirmTransferDialog = getAsyncLifecycle(
78
+ () => import('./patient-transfer/confirm-transfer-dialog.modal'),
79
+ options,
80
+ );
81
+
74
82
  export const visitNotesFormWorkspace = getAsyncLifecycle(
75
83
  () => import('./patient-notes/visit-notes-form-shadow.workspace'),
76
84
  options,
@@ -80,3 +88,18 @@ export const pastVisitsDetailOverviewShadow = getSyncLifecycle(pastVisitsOvervie
80
88
  featureName: 'visits-detail-overview',
81
89
  moduleName,
82
90
  });
91
+
92
+ export const queueTableTransferColumn = getAsyncLifecycle(
93
+ () => import('./patient-transfer/queue-table-transfer-column.component'),
94
+ options,
95
+ );
96
+
97
+ export const patientTransferDetailsModal = getAsyncLifecycle(
98
+ () => import('./patient-transfer/transfer-details.modal'),
99
+ options,
100
+ );
101
+
102
+ export const transferNotesOverview = getAsyncLifecycle(
103
+ () => import('./patient-transfer/transfer-notes-overview.extension'),
104
+ options,
105
+ );
@@ -32,7 +32,7 @@
32
32
  .tagContent {
33
33
  display: flex;
34
34
  align-items: center;
35
- gap: layout.$spacing-02;
35
+ gap: layout.$spacing-02;
36
36
  width: 100%;
37
37
  }
38
38
 
@@ -27,7 +27,7 @@
27
27
 
28
28
  .diagnosisList {
29
29
  background-color: colors.$white;
30
- max-height: layout.rem(320px);
30
+ max-height: layout.rem(320px);
31
31
  overflow-y: auto;
32
32
  border: 1px solid colors.$gray-20;
33
33
  display: flex;
@@ -160,10 +160,10 @@
160
160
  }
161
161
 
162
162
  .diagnoserrorOutline {
163
- max-width: layout.rem(320px);
163
+ max-width: layout.rem(320px);
164
164
 
165
165
  :global(.cds--search-input):focus {
166
- outline: 2px solid colors.$red-60;
166
+ outline: 2px solid colors.$red-60;
167
167
  }
168
168
 
169
169
  :global(.cds--search-magnifier) {
@@ -265,7 +265,7 @@
265
265
  border-radius: 2px;
266
266
  transition: all 0.1s ease;
267
267
  cursor: pointer;
268
- min-height: layout.$spacing-08;
268
+ min-height: layout.$spacing-08;
269
269
 
270
270
  &:hover {
271
271
  background-color: colors.$gray-10;
@@ -287,11 +287,11 @@
287
287
  min-width: layout.$spacing-07;
288
288
  flex-shrink: 0;
289
289
  padding: layout.$spacing-02 layout.$spacing-03;
290
- height: layout.$spacing-07;
290
+ height: layout.$spacing-07;
291
291
  }
292
292
 
293
293
  .certaintyDropdown {
294
- position: relative;
294
+ position: relative;
295
295
  z-index: 10;
296
296
  margin-top: layout.$spacing-02;
297
297
  background: colors.$white;
@@ -443,11 +443,11 @@
443
443
 
444
444
  .diagnosisRow {
445
445
  padding: layout.$spacing-03;
446
- min-height: layout.$spacing-09;
446
+ min-height: layout.$spacing-09;
447
447
  }
448
448
 
449
449
  .certaintyOption {
450
450
  padding: layout.$spacing-03;
451
451
  min-height: layout.$spacing-09;
452
452
  }
453
- }
453
+ }
@@ -0,0 +1,58 @@
1
+ import React, { useState } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { Button, ModalBody, ModalFooter, ModalHeader } from '@carbon/react';
4
+ import { showModal, type Visit } from '@openmrs/esm-framework';
5
+
6
+ interface ConfirmTransferDialogProps {
7
+ closeModal: () => void;
8
+ activeVisit: Visit;
9
+ }
10
+
11
+ const ConfirmTransferDialog: React.FC<ConfirmTransferDialogProps> = ({ closeModal, activeVisit }) => {
12
+ const { t } = useTranslation();
13
+ const [isProcessing, setIsProcessing] = useState(false);
14
+
15
+ const handleConfirm = () => {
16
+ if (isProcessing) {
17
+ return;
18
+ }
19
+ setIsProcessing(true);
20
+
21
+ // 1. Initiate closing the current modal
22
+ closeModal();
23
+
24
+ // 2. The "Double Frame" Guarantee:
25
+ // First rAF: Wait for the close command to be acknowledged
26
+ window.requestAnimationFrame(() => {
27
+ // Second rAF: Wait for the browser to actually remove the element and repaint
28
+ window.requestAnimationFrame(() => {
29
+ const dispose = showModal('transition-patient-to-latest-queue-modal', {
30
+ activeVisit,
31
+ closeModal: () => {
32
+ setIsProcessing(false);
33
+ dispose();
34
+ },
35
+ });
36
+ });
37
+ });
38
+ };
39
+
40
+ return (
41
+ <>
42
+ <ModalHeader closeModal={closeModal}>{t('transferFormSaved', 'Patient transfer form saved')}</ModalHeader>
43
+ <ModalBody>
44
+ <p>{t('moveToQueueQuestion', 'Do you want to move the patient to a queue now?')}</p>
45
+ </ModalBody>
46
+ <ModalFooter>
47
+ <Button kind="secondary" onClick={closeModal} size="lg">
48
+ {t('cancel', 'Cancel')}
49
+ </Button>
50
+ <Button autoFocus kind="primary" onClick={handleConfirm} size="lg" disabled={isProcessing}>
51
+ {t('yesMoveToQueue', 'Yes, move to queue')}
52
+ </Button>
53
+ </ModalFooter>
54
+ </>
55
+ );
56
+ };
57
+
58
+ export default ConfirmTransferDialog;
@@ -0,0 +1,126 @@
1
+ import React, { type ComponentProps, useCallback } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { ArrowsHorizontal } from '@carbon/react/icons';
4
+ import { ActionMenuButton2, Encounter, launchWorkspace2, showModal, useConfig } from '@openmrs/esm-framework';
5
+ import {
6
+ useStartVisitIfNeeded,
7
+ type PatientChartWorkspaceActionButtonProps,
8
+ type PatientWorkspaceGroupProps,
9
+ } from '@openmrs/esm-patient-common-lib';
10
+ import type { ClinicalWorkflowConfig } from '../config-schema';
11
+
12
+ const PatientTransferActionButton: React.FC<PatientChartWorkspaceActionButtonProps> = (props) => {
13
+ const { t } = useTranslation();
14
+ const config = useConfig<ClinicalWorkflowConfig>();
15
+
16
+ const groupProps = props?.groupProps;
17
+ const patientUuid = groupProps?.patientUuid ?? '';
18
+ const patient = groupProps?.patient;
19
+ const visitContext = groupProps?.visitContext;
20
+ const mutateVisitContext = groupProps?.mutateVisitContext;
21
+ const transferFormUuid = config?.patientTransferFormUuid;
22
+
23
+ const startVisitIfNeeded = useStartVisitIfNeeded(patientUuid);
24
+
25
+ const handlePostResponse = useCallback(
26
+ (encounter: Encounter) => {
27
+ if (!visitContext) {
28
+ return;
29
+ }
30
+
31
+ // Show confirmation dialog asking if physician wants to move patient to queue
32
+ const dispose = showModal('confirm-transfer-dialog', {
33
+ activeVisit: visitContext,
34
+ closeModal: () => dispose(),
35
+ });
36
+ },
37
+ [visitContext],
38
+ );
39
+
40
+ const handleLaunchWorkspace = useCallback(async () => {
41
+ const didStartVisit = await startVisitIfNeeded();
42
+ if (!didStartVisit) {
43
+ return;
44
+ }
45
+
46
+ if (!visitContext?.uuid || !visitContext?.visitType?.uuid) {
47
+ console.error('Invalid visit context: visit UUID or visit type UUID is missing');
48
+ return;
49
+ }
50
+
51
+ if (!patient) {
52
+ console.error('Patient data is not available');
53
+ return;
54
+ }
55
+
56
+ launchWorkspace2<
57
+ {
58
+ form: { visitUuid: string; uuid: string; visitTypeUuid: string };
59
+ encounterUuid: string;
60
+ handlePostResponse?: (encounter: Encounter) => void;
61
+ },
62
+ {
63
+ patient: typeof patient;
64
+ patientUuid: string;
65
+ visitContext: typeof visitContext;
66
+ mutateVisitContext: typeof mutateVisitContext | null;
67
+ },
68
+ PatientWorkspaceGroupProps
69
+ >(
70
+ 'patient-transfer-form-entry-workspace',
71
+ {
72
+ form: {
73
+ visitUuid: visitContext.uuid,
74
+ uuid: transferFormUuid ?? '',
75
+ visitTypeUuid: visitContext.visitType.uuid,
76
+ },
77
+ encounterUuid: '',
78
+ handlePostResponse,
79
+ },
80
+ {
81
+ patient: patient!,
82
+ patientUuid,
83
+ visitContext,
84
+ mutateVisitContext: mutateVisitContext || null,
85
+ },
86
+ );
87
+ }, [
88
+ patientUuid,
89
+ patient,
90
+ visitContext,
91
+ mutateVisitContext,
92
+ transferFormUuid,
93
+ handlePostResponse,
94
+ startVisitIfNeeded,
95
+ ]);
96
+
97
+ if (!groupProps?.patientUuid || !patient) {
98
+ return null;
99
+ }
100
+
101
+ return (
102
+ <ActionMenuButton2
103
+ icon={(iconProps: ComponentProps<typeof ArrowsHorizontal>) => <ArrowsHorizontal {...iconProps} />}
104
+ label={t('transferPatient', 'Transfer Patient')}
105
+ workspaceToLaunch={{
106
+ workspaceName: 'patient-form-entry-workspace' as const,
107
+ workspaceProps: {
108
+ form: {
109
+ uuid: transferFormUuid ?? '',
110
+ display: t('transferPatient', 'Transfer Patient'),
111
+ name: 'Transfer Patient',
112
+ },
113
+ encounterUuid: '',
114
+ },
115
+ }}
116
+ onBeforeWorkspaceLaunch={async () => {
117
+ handleLaunchWorkspace().catch((error) => {
118
+ console.error('Error launching transfer form workspace:', error);
119
+ });
120
+ return false;
121
+ }}
122
+ />
123
+ );
124
+ };
125
+
126
+ export default PatientTransferActionButton;
@@ -0,0 +1,40 @@
1
+ import React from 'react';
2
+ import { Button, Tag } from '@carbon/react';
3
+ import { showModal } from '@openmrs/esm-framework';
4
+ import { useTransferData } from './transfer-data.resource';
5
+ import type { QueueEntry } from '../types';
6
+
7
+ interface QueueTableTransferColumnProps {
8
+ queueEntry: QueueEntry;
9
+ }
10
+
11
+ /**
12
+ * Queue table column component that displays transfer status
13
+ * and allows viewing transfer details in a modal
14
+ */
15
+ const QueueTableTransferColumn: React.FC<QueueTableTransferColumnProps> = ({ queueEntry }) => {
16
+ const { transferData, isLoading } = useTransferData(queueEntry.visit?.uuid, queueEntry.patient?.uuid);
17
+
18
+ if (isLoading) {
19
+ return <span>...</span>;
20
+ }
21
+
22
+ if (!transferData) {
23
+ return <Tag type="gray">No Transfer</Tag>;
24
+ }
25
+
26
+ const handleViewTransfer = () => {
27
+ const dispose = showModal('patient-transfer-details-modal', {
28
+ transferData,
29
+ closeModal: () => dispose(),
30
+ });
31
+ };
32
+
33
+ return (
34
+ <Button kind="ghost" size="sm" onClick={handleViewTransfer}>
35
+ View Transfer
36
+ </Button>
37
+ );
38
+ };
39
+
40
+ export default QueueTableTransferColumn;
@@ -0,0 +1,66 @@
1
+ import { openmrsFetch, restBaseUrl, useConfig, type Encounter, type Obs } from '@openmrs/esm-framework';
2
+ import useSWR from 'swr';
3
+ import { useMemo } from 'react';
4
+ import type { ClinicalWorkflowConfig } from '../config-schema';
5
+
6
+ export interface TransferData {
7
+ uuid: string;
8
+ encounterDatetime: string;
9
+ transferDate: string;
10
+ fromLocation: string;
11
+ note: string;
12
+ }
13
+
14
+ export function useTransferData(visitUuid: string, patientUuid: string) {
15
+ const config = useConfig<ClinicalWorkflowConfig>();
16
+ const { transferEncounterTypeUuid, transferNoteConceptUuid } = config;
17
+ const url = visitUuid
18
+ ? `${restBaseUrl}/encounter?patient=${patientUuid}&visit=${visitUuid}&encounterType=${transferEncounterTypeUuid}&v=custom:(uuid,encounterDatetime,location:(uuid,display),obs:(uuid,concept:(uuid,display),value,obsDatetime))`
19
+ : null;
20
+
21
+ const { data, error, isLoading } = useSWR<{ data: { results: Encounter[] } }>(url, openmrsFetch);
22
+
23
+ const transferData = useMemo(() => {
24
+ if (!data?.data?.results?.length) {
25
+ return null;
26
+ }
27
+
28
+ const sortedResults = data.data.results.sort(
29
+ (a, b) => new Date(b.encounterDatetime).getTime() - new Date(a.encounterDatetime).getTime(),
30
+ );
31
+ const transferEncounter = sortedResults[0];
32
+
33
+ return parseTransferEncounter(transferEncounter, transferNoteConceptUuid);
34
+ }, [data, transferNoteConceptUuid]);
35
+
36
+ return {
37
+ transferData,
38
+ isLoading,
39
+ error,
40
+ };
41
+ }
42
+
43
+ function parseTransferEncounter(encounter: Encounter, transferNoteConceptUuid: string): TransferData {
44
+ const obs = encounter.obs || [];
45
+
46
+ return {
47
+ uuid: encounter.uuid,
48
+ encounterDatetime: encounter.encounterDatetime,
49
+ transferDate: encounter.encounterDatetime,
50
+ fromLocation: encounter.location?.display || 'Not specified',
51
+ note: findObsValue(obs, transferNoteConceptUuid) || 'Not specified',
52
+ };
53
+ }
54
+
55
+ function findObsValue(obs: Obs[], conceptUuid: string): string {
56
+ const observation = obs.find((o) => o.concept?.uuid === conceptUuid);
57
+ if (!observation) {
58
+ return '';
59
+ }
60
+
61
+ if (typeof observation.value === 'object' && observation.value !== null) {
62
+ return (observation.value as any).display || String(observation.value);
63
+ }
64
+
65
+ return String(observation.value || '');
66
+ }
@@ -0,0 +1,42 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { ModalBody, ModalHeader, Button, ModalFooter } from '@carbon/react';
4
+ import { formatDate, parseDate } from '@openmrs/esm-framework';
5
+ import type { TransferData } from './transfer-data.resource';
6
+ import styles from './transfer-details.scss';
7
+
8
+ interface TransferDetailsModalProps {
9
+ transferData: TransferData;
10
+ closeModal: () => void;
11
+ }
12
+
13
+ const TransferDetailsModal: React.FC<TransferDetailsModalProps> = ({ transferData, closeModal }) => {
14
+ const { t } = useTranslation();
15
+
16
+ return (
17
+ <>
18
+ <ModalHeader closeModal={closeModal} title={t('patientTransferInformation', 'Patient Transfer Information')} />
19
+ <ModalBody>
20
+ <div className={styles.container}>
21
+ <div style={{ marginBottom: '1rem' }}>
22
+ <strong>{t('transferDate', 'Transfer Date')}:</strong>{' '}
23
+ {formatDate(parseDate(transferData.transferDate), { mode: 'wide' })}
24
+ </div>
25
+ <div style={{ marginBottom: '1rem' }}>
26
+ <strong>{t('fromLocation', 'Transfer From')}:</strong> {transferData.fromLocation}
27
+ </div>
28
+ <div style={{ marginBottom: '1rem' }}>
29
+ <strong>{t('transferNote', 'Transfer Note')}:</strong> {transferData.note}
30
+ </div>
31
+ </div>
32
+ </ModalBody>
33
+ <ModalFooter>
34
+ <Button kind="secondary" onClick={closeModal}>
35
+ {t('close', 'Close')}
36
+ </Button>
37
+ </ModalFooter>
38
+ </>
39
+ );
40
+ };
41
+
42
+ export default TransferDetailsModal;
@@ -0,0 +1,18 @@
1
+ @use '@carbon/layout';
2
+ @use '@carbon/type';
3
+ @use '@carbon/colors';
4
+ @use '@openmrs/esm-styleguide/src/vars' as *;
5
+
6
+ .container {
7
+ padding: layout.$spacing-05;
8
+ }
9
+
10
+ .header {
11
+ margin-bottom: layout.$spacing-06;
12
+
13
+ h3 {
14
+ @include type.type-style('heading-03');
15
+ font-weight: 600;
16
+ margin: 0;
17
+ }
18
+ }
@@ -0,0 +1,63 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { DataTableSkeleton, InlineLoading } from '@carbon/react';
4
+ import { useLayoutType } from '@openmrs/esm-framework';
5
+ import { CardHeader, EmptyState, ErrorState, usePatientChartStore } from '@openmrs/esm-patient-common-lib';
6
+ import { useTransferNotes } from './transfer-notes.resource';
7
+ import TransferNotesTable from './transfer-notes-table.component';
8
+ import styles from './transfer-notes-overview.scss';
9
+
10
+ interface TransferNotesOverviewProps {
11
+ patientUuid: string;
12
+ patient: fhir.Patient;
13
+ basePath: string;
14
+ }
15
+
16
+ /**
17
+ * This extension displays transfer notes in the patient chart.
18
+ * It only shows transfer notes from the current visit.
19
+ * It uses the patient chart store and MUST only be mounted within the patient chart.
20
+ */
21
+ const TransferNotesOverview: React.FC<TransferNotesOverviewProps> = ({ patientUuid, patient, basePath }) => {
22
+ const pageSize = 5;
23
+ const { t } = useTranslation();
24
+ const pageUrl = `\${openmrsSpaBase}/patient/${patient.id}/chart/Forms & Notes`;
25
+ const urlLabel = t('seeAll', 'See all');
26
+
27
+ // Get current visit from patient chart store
28
+ const { visitContext } = usePatientChartStore(patientUuid);
29
+ const visitUuid = visitContext?.uuid || null;
30
+
31
+ const displayText = t('transferNotes', 'Transfer notes');
32
+ const headerTitle = t('transferNotes', 'Transfer notes');
33
+ const { transferNotes, error, isLoading, isValidating } = useTransferNotes(patientUuid, visitUuid);
34
+ const layout = useLayoutType();
35
+ const isDesktop = layout === 'large-desktop' || layout === 'small-desktop';
36
+
37
+ // Don't show the widget if there's no current visit
38
+ if (!visitUuid) {
39
+ return null;
40
+ }
41
+
42
+ if (isLoading) {
43
+ return <DataTableSkeleton role="progressbar" compact={isDesktop} zebra />;
44
+ }
45
+ if (error) {
46
+ return <ErrorState error={error} headerTitle={headerTitle} />;
47
+ }
48
+ // Only show the widget if there are transfer notes for the current visit
49
+ if (!transferNotes?.length) {
50
+ return null;
51
+ }
52
+
53
+ return (
54
+ <div className={styles.widgetCard}>
55
+ <CardHeader title={headerTitle}>
56
+ <span>{isValidating ? <InlineLoading /> : null}</span>
57
+ </CardHeader>
58
+ <TransferNotesTable notes={transferNotes} pageSize={pageSize} urlLabel={urlLabel} pageUrl={pageUrl} />
59
+ </div>
60
+ );
61
+ };
62
+
63
+ export default TransferNotesOverview;
@@ -0,0 +1,88 @@
1
+ @use '@carbon/layout';
2
+ @use '@carbon/type';
3
+ @use '@carbon/colors';
4
+ @use '@openmrs/esm-styleguide/src/vars' as *;
5
+
6
+ .widgetCard {
7
+ background-color: $ui-background;
8
+ border: 1px solid colors.$gray-20;
9
+ position: relative;
10
+ }
11
+
12
+ .tableContainer {
13
+ padding: 0 layout.$spacing-05;
14
+ }
15
+
16
+ .container {
17
+ display: flex;
18
+ flex-direction: column;
19
+ margin: layout.$spacing-03;
20
+ }
21
+
22
+ .copy {
23
+ @include type.type-style('body-01');
24
+ display: flex;
25
+ flex-direction: column;
26
+ }
27
+
28
+ .content {
29
+ @include type.type-style('body-01');
30
+ white-space: pre-wrap;
31
+ word-break: break-word;
32
+ }
33
+
34
+ .observationRow {
35
+ display: flex;
36
+ flex-direction: column;
37
+ margin-bottom: layout.$spacing-03;
38
+ padding-bottom: layout.$spacing-03;
39
+ border-bottom: 1px solid colors.$gray-20;
40
+
41
+ &:last-of-type {
42
+ margin-bottom: 0;
43
+ padding-bottom: 0;
44
+ border-bottom: none;
45
+ }
46
+ }
47
+
48
+ .observationLabel {
49
+ @include type.type-style('label-01');
50
+ font-weight: 600;
51
+ margin-bottom: layout.$spacing-02;
52
+ color: $text-02;
53
+ letter-spacing: 0.01em;
54
+ }
55
+
56
+ .observationValue {
57
+ @include type.type-style('body-01');
58
+ white-space: pre-wrap;
59
+ word-break: break-word;
60
+ line-height: 1.5;
61
+ }
62
+
63
+ .metadata {
64
+ @include type.type-style('label-01');
65
+ color: $text-02;
66
+ margin-top: layout.$spacing-05;
67
+ padding-top: layout.$spacing-03;
68
+ border-top: 1px solid colors.$gray-20;
69
+ }
70
+
71
+ .expandedRow {
72
+ > td {
73
+ padding-left: layout.$spacing-08 !important;
74
+ }
75
+
76
+ > td > div {
77
+ max-height: max-content !important;
78
+ }
79
+
80
+ th[colspan] td[colspan] > div:first-child {
81
+ padding: 0 layout.$spacing-05;
82
+ }
83
+ }
84
+
85
+ .hiddenRow {
86
+ display: none;
87
+ }
88
+