@pih/esm-commons-app 2.1.1-pre.15 → 2.1.1-pre.151

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 (125) hide show
  1. package/.turbo/turbo-build.log +5 -35
  2. package/dist/117.js +1 -0
  3. package/dist/117.js.map +1 -0
  4. package/dist/125.js +1 -0
  5. package/dist/125.js.map +1 -0
  6. package/dist/163.js +1 -0
  7. package/dist/163.js.map +1 -0
  8. package/dist/240.js +1 -0
  9. package/dist/240.js.map +1 -0
  10. package/dist/294.js +1 -0
  11. package/dist/294.js.map +1 -0
  12. package/dist/324.js +1 -0
  13. package/dist/324.js.map +1 -0
  14. package/dist/349.js +1 -0
  15. package/dist/349.js.map +1 -0
  16. package/dist/378.js +1 -0
  17. package/dist/378.js.map +1 -0
  18. package/dist/466.js +1 -0
  19. package/dist/466.js.map +1 -0
  20. package/dist/508.js +1 -0
  21. package/dist/508.js.map +1 -0
  22. package/dist/61.js +1 -0
  23. package/dist/61.js.map +1 -0
  24. package/dist/614.js +33 -0
  25. package/dist/614.js.map +1 -0
  26. package/dist/646.js +17 -0
  27. package/dist/646.js.map +1 -0
  28. package/dist/689.js +1 -0
  29. package/dist/689.js.map +1 -0
  30. package/dist/701.js +1 -0
  31. package/dist/701.js.map +1 -0
  32. package/dist/712.js +1 -0
  33. package/dist/712.js.map +1 -0
  34. package/dist/72.js +1 -0
  35. package/dist/72.js.map +1 -0
  36. package/dist/771.js +1 -0
  37. package/dist/771.js.map +1 -0
  38. package/dist/82.js +11 -0
  39. package/dist/82.js.map +1 -0
  40. package/dist/830.js +1 -0
  41. package/dist/830.js.map +1 -0
  42. package/dist/852.js +1 -0
  43. package/dist/852.js.map +1 -0
  44. package/dist/855.js +1 -0
  45. package/dist/855.js.map +1 -0
  46. package/dist/859.js +1 -0
  47. package/dist/859.js.map +1 -0
  48. package/dist/867.js +1 -0
  49. package/dist/867.js.map +1 -0
  50. package/dist/88.js +1 -0
  51. package/dist/88.js.map +1 -0
  52. package/dist/912.js +1 -0
  53. package/dist/912.js.map +1 -0
  54. package/dist/913.js +1 -0
  55. package/dist/913.js.map +1 -0
  56. package/dist/985.js +1 -0
  57. package/dist/985.js.map +1 -0
  58. package/dist/989.js +1 -0
  59. package/dist/989.js.map +1 -0
  60. package/dist/main.js +3 -1
  61. package/dist/main.js.map +1 -1
  62. package/dist/pih-esm-commons-app.js +3 -1
  63. package/dist/pih-esm-commons-app.js.buildmanifest.json +570 -110
  64. package/dist/pih-esm-commons-app.js.map +1 -1
  65. package/dist/routes.json +1 -1
  66. package/package.json +7 -10
  67. package/rspack.config.js +1 -0
  68. package/src/config-schema.ts +85 -0
  69. package/src/declarations.d.ts +2 -0
  70. package/src/dispensing/zebra-label-print-action.extension.tsx +66 -0
  71. package/src/dispensing/zebra-label-print.resource.ts +49 -0
  72. package/src/dispensing/zebra-pdf-print.ts +108 -0
  73. package/src/hooks/useConceptReferenceRanges.ts +128 -0
  74. package/src/hooks/useEncounters.ts +22 -0
  75. package/src/hooks/useVisitAttribute.ts +37 -0
  76. package/src/index.ts +39 -12
  77. package/src/routes.json +65 -11
  78. package/src/service-queues-app/maternal-triage-form.workspace.tsx +111 -0
  79. package/src/service-queues-app/maternal-triage-queue-actions.extension.tsx +132 -0
  80. package/src/service-queues-app/maternal-triage-queue-actions.resource.ts +84 -0
  81. package/src/service-queues-app/maternal-trial-queue-actions.scss +14 -0
  82. package/src/service-queues-app/types.ts +58 -0
  83. package/src/ward-app/VitalsTable.tsx +361 -0
  84. package/src/ward-app/inborn-outborn-tag-row.extension.tsx +79 -0
  85. package/src/ward-app/inborn-outborn-tag-row.scss +57 -0
  86. package/src/ward-app/o2-iframe.component.tsx +13 -4
  87. package/src/ward-app/o2-pregnancy-infant-dashboard.extension.tsx +29 -10
  88. package/src/ward-app/o2-pregnancy-infant-dashboard.scss +5 -0
  89. package/src/ward-app/o2-visit-summary-action-button.component.tsx +17 -0
  90. package/src/ward-app/o2-visit-summary-workspace.component.tsx +104 -26
  91. package/src/ward-app/o2-vital-signs-action-button.component.tsx +17 -0
  92. package/src/ward-app/o2-vital-signs-workspace.component.tsx +65 -0
  93. package/src/ward-app/types.ts +7 -4
  94. package/translations/en.json +24 -3
  95. package/translations/es.json +7 -1
  96. package/translations/fr.json +7 -1
  97. package/dist/138.js +0 -1
  98. package/dist/138.js.map +0 -1
  99. package/dist/291.js +0 -1
  100. package/dist/291.js.map +0 -1
  101. package/dist/300.js +0 -1
  102. package/dist/372.js +0 -1
  103. package/dist/372.js.map +0 -1
  104. package/dist/41.js +0 -2
  105. package/dist/41.js.LICENSE.txt +0 -9
  106. package/dist/41.js.map +0 -1
  107. package/dist/411.js +0 -2
  108. package/dist/411.js.LICENSE.txt +0 -5
  109. package/dist/411.js.map +0 -1
  110. package/dist/449.js +0 -2
  111. package/dist/449.js.LICENSE.txt +0 -9
  112. package/dist/449.js.map +0 -1
  113. package/dist/55.js +0 -1
  114. package/dist/641.js +0 -1
  115. package/dist/641.js.map +0 -1
  116. package/dist/660.js +0 -1
  117. package/dist/660.js.map +0 -1
  118. package/dist/831.js +0 -2
  119. package/dist/831.js.LICENSE.txt +0 -5
  120. package/dist/831.js.map +0 -1
  121. package/dist/90.js +0 -1
  122. package/dist/90.js.map +0 -1
  123. package/dist/99.js +0 -1
  124. package/src/ward-app/o2-visit-summary-action-button.extension.tsx +0 -18
  125. package/webpack.config.js +0 -1
@@ -0,0 +1,22 @@
1
+ import { restBaseUrl, useOpenmrsFetchAll } from '@openmrs/esm-framework';
2
+ import { type Encounter } from '../service-queues-app/types';
3
+
4
+ interface EncounterSearchParams {
5
+ patient: string;
6
+ visit?: string;
7
+ encounterType?: string;
8
+ }
9
+
10
+ export function useEncounters(params: EncounterSearchParams) {
11
+ const url = `${restBaseUrl}/encounter?`;
12
+ const searchParams = new URLSearchParams();
13
+ searchParams.append('patient', params.patient);
14
+ if (params.visit) {
15
+ searchParams.append('visit', params.visit);
16
+ }
17
+ if (params.encounterType) {
18
+ searchParams.append('encounterType', params.encounterType);
19
+ }
20
+
21
+ return useOpenmrsFetchAll<Encounter>(url + searchParams.toString());
22
+ }
@@ -0,0 +1,37 @@
1
+ import { restBaseUrl, useOpenmrsFetchAll } from '@openmrs/esm-framework';
2
+
3
+ export interface VisitAttribute {
4
+ uuid: string;
5
+ display?: string;
6
+ value: boolean | string | number;
7
+ attributeType: {
8
+ uuid: string;
9
+ display?: string;
10
+ };
11
+ }
12
+
13
+ // prettier-ignore
14
+ const visitAttributeCustomRepresentation =
15
+ 'custom:(uuid,value,attributeType:(uuid,display))';
16
+
17
+ /**
18
+ * Fetches the attribute of a given type for a given visit.
19
+ *
20
+ * The default visit representation used by the ward admission/request hooks does not include
21
+ * attribute values, so this hook queries the visit's attribute subresource directly and returns the
22
+ * single attribute matching `attributeTypeUuid` (or `undefined` if the visit has no such attribute).
23
+ *
24
+ * @param visitUuid the visit whose attributes to fetch. When falsy, no request is made.
25
+ * @param attributeTypeUuid the visit attribute type to look for.
26
+ */
27
+ export function useVisitAttribute(visitUuid: string, attributeTypeUuid: string) {
28
+ const apiUrl = visitUuid
29
+ ? `${restBaseUrl}/visit/${visitUuid}/attribute?v=${visitAttributeCustomRepresentation}`
30
+ : null;
31
+
32
+ const { data, ...rest } = useOpenmrsFetchAll<VisitAttribute>(apiUrl);
33
+
34
+ const attribute = data?.find((attr) => attr.attributeType?.uuid === attributeTypeUuid);
35
+
36
+ return { attribute, value: attribute?.value, ...rest };
37
+ }
package/src/index.ts CHANGED
@@ -5,31 +5,58 @@
5
5
  * microfrontend.
6
6
  */
7
7
 
8
- import { getAsyncLifecycle } from '@openmrs/esm-framework';
8
+ import { getAsyncLifecycle, defineConfigSchema } from '@openmrs/esm-framework';
9
+ import { configSchema } from './config-schema';
9
10
 
10
11
  const moduleName = '@pih/esm-commons-app';
11
- const options = {
12
- featureName: 'commons',
13
- moduleName,
14
- };
15
12
 
16
13
  export const importTranslation = require.context('../translations', false, /.json$/, 'lazy');
17
14
 
18
- // export const root = getAsyncLifecycle(() => import('./root.component'), options);
19
-
20
15
  export const o2VisitSummaryWorkspaceSideRailIcon = getAsyncLifecycle(
21
- () => import('./ward-app/o2-visit-summary-action-button.extension'),
22
- options,
16
+ () => import('./ward-app/o2-visit-summary-action-button.component'),
17
+ { featureName: 'o2VisitSummaryWorkspaceSideRailIcon', moduleName },
23
18
  );
24
19
 
25
20
  export const o2VisitSummaryWorkspace = getAsyncLifecycle(
26
21
  () => import('./ward-app/o2-visit-summary-workspace.component'),
27
- options,
22
+ { featureName: 'o2VisitSummaryWorkspace', moduleName },
23
+ );
24
+
25
+ export const o2VitalSignsWorkspaceSideRailIcon = getAsyncLifecycle(
26
+ () => import('./ward-app/o2-vital-signs-action-button.component'),
27
+ { featureName: 'o2VitalSignsWorkspaceSideRailIcon', moduleName },
28
28
  );
29
29
 
30
+ export const o2VitalSignsWorkspace = getAsyncLifecycle(() => import('./ward-app/o2-vital-signs-workspace.component'), {
31
+ featureName: 'o2VitalSignsWorkspace',
32
+ moduleName,
33
+ });
34
+
30
35
  export const o2PregnancyInfantDashboard = getAsyncLifecycle(
31
36
  () => import('./ward-app/o2-pregnancy-infant-dashboard.extension'),
32
- options,
37
+ { featureName: 'o2PregnancyInfantDashboard', moduleName },
38
+ );
39
+
40
+ export const maternalTriageFormWorkspace = getAsyncLifecycle(
41
+ () => import('./service-queues-app/maternal-triage-form.workspace'),
42
+ { featureName: 'maternalTriageFormWorkspace', moduleName },
43
+ );
44
+
45
+ export const triageWaitingQueueActions = getAsyncLifecycle(
46
+ () => import('./service-queues-app/maternal-triage-queue-actions.extension'),
47
+ { featureName: 'triageWaitingQueueActions', moduleName },
33
48
  );
34
49
 
35
- export function startupApp() {}
50
+ export const zebraLabelPrintAction = getAsyncLifecycle(
51
+ () => import('./dispensing/zebra-label-print-action.extension'),
52
+ { featureName: 'zebraLabelPrintAction', moduleName },
53
+ );
54
+
55
+ export const inbornOutbornTag = getAsyncLifecycle(() => import('./ward-app/inborn-outborn-tag-row.extension'), {
56
+ featureName: 'inbornOutbornTag',
57
+ moduleName,
58
+ });
59
+
60
+ export function startupApp() {
61
+ defineConfigSchema(moduleName, configSchema);
62
+ }
package/src/routes.json CHANGED
@@ -1,28 +1,82 @@
1
1
  {
2
2
  "$schema": "https://json.openmrs.org/routes.schema.json",
3
3
  "pages": [
4
- ],
5
- "extensions": [
6
4
  {
7
- "name": "o2-visit-summary-workspace-siderail-button",
8
- "component": "o2VisitSummaryWorkspaceSideRailIcon",
9
- "slot": "action-menu-ward-patient-items-slot"
5
+ "component": "root",
6
+ "route": "appointments"
7
+ },
8
+ {
9
+ "component": "root",
10
+ "route": "service-queues"
10
11
  },
12
+ {
13
+ "component": "root",
14
+ "route": "dispensing2"
15
+ },
16
+ {
17
+ "component": "root",
18
+ "route": "ward"
19
+ }
20
+ ],
21
+ "extensions": [
11
22
  {
12
23
  "name": "o2-pregnancy-infant-dashboard",
13
24
  "component": "o2PregnancyInfantDashboard",
14
25
  "slot": "ward-patient-workspace-content-slot"
26
+ },
27
+ {
28
+ "name": "triage-waiting-queue-actions",
29
+ "component": "triageWaitingQueueActions",
30
+ "slot": "queue-table-triage-waiting-queue-actions-slot"
31
+ },
32
+ {
33
+ "name": "zebra-label-print-action",
34
+ "component": "zebraLabelPrintAction",
35
+ "slot": "medication-dispense-action-slot"
36
+ },
37
+ {
38
+ "name": "inborn-outborn-tag",
39
+ "component": "inbornOutbornTag",
40
+ "slot": "maternal-ward-patient-card-header-slot"
15
41
  }
16
42
  ],
17
- "workspaces": [
43
+ "workspaces2": [
18
44
  {
19
45
  "name": "o2-visit-summary-workspace",
20
46
  "component": "o2VisitSummaryWorkspace",
21
- "title": "visitSummary",
22
- "type": "o2-visit-summary",
23
- "sidebarFamily": "ward-patient",
24
- "hasOwnSidebar": true,
25
- "width": "extra-wide"
47
+ "window": "o2-visit-summary-window"
48
+ },
49
+ {
50
+ "name": "o2-vital-signs-workspace",
51
+ "component": "o2VitalSignsWorkspace",
52
+ "window": "o2-vital-signs-window"
53
+ },
54
+ {
55
+ "name": "maternal-triage-form-workspace",
56
+ "component": "maternalTriageFormWorkspace",
57
+ "window": "maternal-triage-form-window"
58
+ }
59
+ ],
60
+ "workspaceWindows2": [
61
+ {
62
+ "name": "o2-visit-summary-window",
63
+ "icon": "o2VisitSummaryWorkspaceSideRailIcon",
64
+ "group": "ward-patient",
65
+ "width": "extra-wide",
66
+ "canMaximize": true
67
+ },
68
+ {
69
+ "name": "o2-vital-signs-window",
70
+ "icon": "o2VitalSignsWorkspaceSideRailIcon",
71
+ "group": "ward-patient",
72
+ "width": "extra-wide",
73
+ "canMaximize": true
74
+ },
75
+ {
76
+ "name": "maternal-triage-form-window",
77
+ "group": "service-queues",
78
+ "width": "extra-wide",
79
+ "canMaximize": true
26
80
  }
27
81
  ]
28
82
  }
@@ -0,0 +1,111 @@
1
+ import { type Patient, showSnackbar, Workspace2, type Workspace2DefinitionProps } from '@openmrs/esm-framework';
2
+ import React, { useEffect } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import O2IFrame from '../ward-app/o2-iframe.component';
5
+ import { updateQueueEntry, useMutateQueueEntries } from './maternal-triage-queue-actions.resource';
6
+ import { type QueueEntry } from './types';
7
+ import { useEncounters } from '../hooks/useEncounters';
8
+ import { Loading } from '@carbon/react';
9
+ import { InlineNotification } from '@carbon/react';
10
+
11
+ interface MaternalTriageFormWorkspaceProps {
12
+ queueEntry: QueueEntry;
13
+ patient: Patient;
14
+ }
15
+
16
+ const MATERNAL_TRIAGE_FORM_ENCOUNTER_TYPE = '41911448-71a1-43d7-bba8-dc86339850da';
17
+
18
+ /**
19
+ * Workspace to display the Maternal Triage HTML Form, rendered in a iframe
20
+ */
21
+ const MaternalTriageFormWorkspace: React.FC<Workspace2DefinitionProps<MaternalTriageFormWorkspaceProps>> = ({
22
+ workspaceProps: { queueEntry, patient },
23
+ closeWorkspace,
24
+ }) => {
25
+ const { t } = useTranslation();
26
+ const { mutateQueueEntries } = useMutateQueueEntries();
27
+ const {
28
+ data: filledOutTriageForms,
29
+ isLoading,
30
+ error,
31
+ } = useEncounters({
32
+ patient: patient.uuid,
33
+ visit: queueEntry.visit.uuid,
34
+ encounterType: MATERNAL_TRIAGE_FORM_ENCOUNTER_TYPE,
35
+ });
36
+
37
+ useEffect(() => {
38
+ const onMessage = async (event: MessageEvent) => {
39
+ if (event.data == 'triageFormSubmitted') {
40
+ const endQueueEntry = () => {
41
+ return updateQueueEntry(queueEntry.uuid, {
42
+ endedAt: new Date().toISOString(),
43
+ });
44
+ };
45
+
46
+ await endQueueEntry();
47
+ await mutateQueueEntries();
48
+ closeWorkspace({ discardUnsavedChanges: true });
49
+ showSnackbar({
50
+ isLowContrast: true,
51
+ kind: 'success',
52
+ title: t('triageFormSubmitted', 'Triage form successfully submitted for {{patient}}.', {
53
+ patient: patient.person.display,
54
+ }),
55
+ });
56
+ }
57
+ };
58
+
59
+ window.addEventListener('message', onMessage);
60
+ return () => window.removeEventListener('message', onMessage);
61
+ }, []);
62
+
63
+ const patientUuid = patient.uuid;
64
+ const visitUuid = queueEntry.visit.uuid;
65
+
66
+ const elementsToHide = [
67
+ 'header',
68
+ '#breadcrumbs',
69
+
70
+ // prevent O2 success toast from showing; After submitting form for a patient,
71
+ // the success toast for that patient shows when opening form for another patient
72
+ '.toast-type-success',
73
+ // UHM-8822: remove Cancel button from the triage form
74
+ '.cancel',
75
+ ];
76
+
77
+ const customJavaScript = `
78
+ htmlForm.setSuccessFunction(() => window.top.postMessage('triageFormSubmitted'))
79
+ `;
80
+
81
+ if (isLoading) {
82
+ return <Loading withOverlay={false} small />;
83
+ } else if (error) {
84
+ return (
85
+ <InlineNotification
86
+ kind="error"
87
+ lowContrast={true}
88
+ hideCloseButton={true}
89
+ title={t('errorLoadingPatientForm', 'Error loading patient form')}
90
+ />
91
+ );
92
+ } else {
93
+ // If patient does not have triage form filled out already, we load a new one,
94
+ // else, we load the last filled out form to edit
95
+ const src =
96
+ filledOutTriageForms.length == 0
97
+ ? `${window.openmrsBase}/htmlformentryui/htmlform/enterHtmlFormWithStandardUi.page?patientId=${patientUuid}&visitId=${visitUuid}&definitionUiResource=file:configuration/pih/htmlforms/triage.xml&returnUrl=%2Fopenmrs%2Fcoreapps%2Fclinicianfacing%2Fpatient.page%3FpatientId%3D${patientUuid}%26`
98
+ : `${window.openmrsBase}/htmlformentryui/htmlform/editHtmlFormWithStandardUi.page?patientId=${patientUuid}&encounterId=${filledOutTriageForms[filledOutTriageForms.length - 1].uuid}`;
99
+
100
+ return (
101
+ <Workspace2
102
+ title={t('maternalTriageForm', 'Maternal triage form')}
103
+ hasUnsavedChanges={true} // assume there are unsaved changes to prevent accidental closure
104
+ >
105
+ <O2IFrame key={patientUuid} src={src} elementsToHide={elementsToHide} customJavaScript={customJavaScript} />
106
+ </Workspace2>
107
+ );
108
+ }
109
+ };
110
+
111
+ export default MaternalTriageFormWorkspace;
@@ -0,0 +1,132 @@
1
+ import { Button, OverflowMenu, OverflowMenuItem } from '@carbon/react';
2
+ import { isDesktop, launchWorkspace2, showModal, useConfig, useLayoutType } from '@openmrs/esm-framework';
3
+ import React from 'react';
4
+ import { useTranslation } from 'react-i18next';
5
+ import styles from './maternal-trial-queue-actions.scss';
6
+ import { type QueueEntry } from './types';
7
+ import { transitionQueueEntry, useMutateQueueEntries } from './maternal-triage-queue-actions.resource';
8
+
9
+ // types taken from esm-service-queues-app
10
+ interface QueueTableCellComponentProps {
11
+ queueEntry: QueueEntry;
12
+ }
13
+
14
+ interface ConfigObject {
15
+ concepts: {
16
+ defaultTransitionStatus: string;
17
+ };
18
+ // other fields not shown
19
+ }
20
+
21
+ /**
22
+ * This extension provides an extra "Triage" action, along with the other standard actions for
23
+ * queue entries in the queues table. The Triage action opens the maternal triage form, and also
24
+ * has the side effect of putting the patient's queue entry into the "in service" status, if they have
25
+ * not yet already
26
+ * @param param0
27
+ * @returns
28
+ */
29
+ const MaternalTriageQueueActions: React.FC<QueueTableCellComponentProps> = ({ queueEntry }) => {
30
+ const { t } = useTranslation();
31
+ const layout = useLayoutType();
32
+
33
+ const { mutateQueueEntries } = useMutateQueueEntries();
34
+ const { concepts } = useConfig<ConfigObject>({ externalModuleName: '@openmrs/esm-service-queues-app' });
35
+ const inServiceStatus = concepts.defaultTransitionStatus;
36
+
37
+ const { patient } = queueEntry;
38
+
39
+ return (
40
+ <div className={styles.actionsCell}>
41
+ <Button
42
+ kind="ghost"
43
+ aria-label={t('triage', 'Triage')}
44
+ onClick={async () => {
45
+ if (queueEntry.status.uuid !== inServiceStatus) {
46
+ const res = await transitionQueueEntry({
47
+ queueEntryToTransition: queueEntry.uuid,
48
+ newStatus: inServiceStatus,
49
+ });
50
+ mutateQueueEntries();
51
+ const updatedQueueEntry = { ...res.data, visit: queueEntry.visit };
52
+ launchWorkspace2('maternal-triage-form-workspace', { queueEntry: updatedQueueEntry, patient });
53
+ } else {
54
+ launchWorkspace2('maternal-triage-form-workspace', { queueEntry, patient });
55
+ }
56
+ }}
57
+ size={isDesktop(layout) ? 'sm' : 'lg'}>
58
+ {t('triage', 'Triage')}
59
+ </Button>
60
+
61
+ <OverflowMenu aria-label="Actions menu" size={isDesktop(layout) ? 'sm' : 'lg'} align="left" flipped>
62
+ <OverflowMenuItem
63
+ className={styles.menuItem}
64
+ aria-label={t('transition', 'Transition')}
65
+ hasDivider
66
+ onClick={() => {
67
+ const dispose = showModal('transition-queue-entry-modal', {
68
+ closeModal: () => dispose(),
69
+ queueEntry,
70
+ });
71
+ }}
72
+ itemText={t('transition', 'Transition')}
73
+ />
74
+ <OverflowMenuItem
75
+ className={styles.menuItem}
76
+ aria-label={t('edit', 'Edit')}
77
+ hasDivider
78
+ onClick={() => {
79
+ const dispose = showModal('edit-queue-entry-modal', {
80
+ closeModal: () => dispose(),
81
+ queueEntry,
82
+ });
83
+ }}
84
+ itemText={t('edit', 'Edit')}
85
+ />
86
+ <OverflowMenuItem
87
+ className={styles.menuItem}
88
+ aria-label={t('removePatient', 'Remove patient')}
89
+ hasDivider
90
+ onClick={() => {
91
+ const dispose = showModal('remove-queue-entry-modal', {
92
+ closeModal: () => dispose(),
93
+ queueEntry,
94
+ });
95
+ }}
96
+ itemText={t('removePatient', 'Remove patient')}
97
+ />
98
+ {queueEntry.previousQueueEntry == null ? (
99
+ <OverflowMenuItem
100
+ className={styles.menuItem}
101
+ aria-label={t('delete', 'Delete')}
102
+ hasDivider
103
+ isDelete
104
+ onClick={() => {
105
+ const dispose = showModal('delete-queue-entry-modal', {
106
+ closeModal: () => dispose(),
107
+ queueEntry,
108
+ });
109
+ }}
110
+ itemText={t('delete', 'Delete')}
111
+ />
112
+ ) : (
113
+ <OverflowMenuItem
114
+ className={styles.menuItem}
115
+ aria-label={t('undoTransition', 'Undo transition')}
116
+ hasDivider
117
+ isDelete
118
+ onClick={() => {
119
+ const dispose = showModal('undo-transition-queue-entry-modal', {
120
+ closeModal: () => dispose(),
121
+ queueEntry,
122
+ });
123
+ }}
124
+ itemText={t('undoTransition', 'Undo transition')}
125
+ />
126
+ )}
127
+ </OverflowMenu>
128
+ </div>
129
+ );
130
+ };
131
+
132
+ export default MaternalTriageQueueActions;
@@ -0,0 +1,84 @@
1
+ import { type Concept, type FetchResponse, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
2
+ import { useSWRConfig } from 'swr';
3
+ import { type QueueEntry } from './types';
4
+ import { useCallback, useMemo } from 'react';
5
+
6
+ // Hooks here copied from esm-service-queues-app
7
+
8
+ export function useMutateQueueEntries() {
9
+ const { mutate, cache } = useSWRConfig();
10
+ const mutateQueueEntries = useCallback(() => {
11
+ const promises: Promise<unknown>[] = [];
12
+ for (const key of cache.keys()) {
13
+ if (key.includes(`${restBaseUrl}/queue-entry`) || key.includes(`${restBaseUrl}/visit-queue-entry`)) {
14
+ promises.push(mutate(key));
15
+ }
16
+ }
17
+ return Promise.all(promises);
18
+ }, [mutate, cache]);
19
+
20
+ return useMemo(
21
+ () => ({
22
+ mutateQueueEntries,
23
+ }),
24
+ [mutateQueueEntries],
25
+ );
26
+ }
27
+
28
+ interface TransitionQueueEntryParams {
29
+ queueEntryToTransition: string;
30
+ transitionDate?: string;
31
+ newQueue?: string;
32
+ newStatus?: string;
33
+ newPriority?: string;
34
+ newPriorityComment?: string;
35
+ }
36
+
37
+ /**
38
+ * A transition is defined as an action that ends a current queue entry and immediately starts a new one
39
+ * with (slightly) different values. For now, this could be used to transition the queue entry's status,
40
+ * priority or queue. This allows us to keep a history of queue entries through a patient's visit.
41
+ * Note that there are some use cases (like RDE or data correction) where a transition is NOT appropriate.
42
+ * @param params
43
+ * @param abortController
44
+ * @returns
45
+ */
46
+ export function transitionQueueEntry(
47
+ params: TransitionQueueEntryParams,
48
+ abortController?: AbortController,
49
+ ): Promise<FetchResponse<QueueEntry>> {
50
+ return openmrsFetch(`${restBaseUrl}/queue-entry/transition`, {
51
+ method: 'POST',
52
+ headers: {
53
+ 'Content-Type': 'application/json',
54
+ },
55
+ signal: abortController?.signal,
56
+ body: params,
57
+ });
58
+ }
59
+
60
+ interface UpdateQueueEntryParams {
61
+ status?: Concept;
62
+ priority?: Concept;
63
+ priorityComment?: string;
64
+ sortWeight?: number;
65
+ startedAt?: string;
66
+ endedAt?: string;
67
+ loationWaitingFor?: Location;
68
+ providerWaitingFor?: Location;
69
+ }
70
+
71
+ export function updateQueueEntry(
72
+ queueEntryUuid: string,
73
+ params: UpdateQueueEntryParams,
74
+ abortController?: AbortController,
75
+ ) {
76
+ return openmrsFetch(`${restBaseUrl}/queue-entry/${queueEntryUuid}`, {
77
+ method: 'POST',
78
+ headers: {
79
+ 'Content-Type': 'application/json',
80
+ },
81
+ signal: abortController?.signal,
82
+ body: params,
83
+ });
84
+ }
@@ -0,0 +1,14 @@
1
+ .actionCellContainer {
2
+ white-space: nowrap;
3
+ }
4
+
5
+ .menuItem {
6
+ max-width: none;
7
+ }
8
+
9
+ .actionsCell {
10
+ display: flex;
11
+ justify-content: flex-end;
12
+ height: 100%;
13
+ align-items: center;
14
+ }
@@ -0,0 +1,58 @@
1
+ import { type Concept, type OpenmrsResource, type Patient, type Visit } from '@openmrs/esm-framework';
2
+
3
+ export interface Queue {
4
+ uuid: string;
5
+ display: string;
6
+ name: string;
7
+ description: string;
8
+ location: Location;
9
+ service: Concept;
10
+ allowedPriorities: Array<Concept>;
11
+ allowedStatuses: Array<Concept>;
12
+ }
13
+
14
+ export interface QueueEntry {
15
+ uuid: string;
16
+ display: string;
17
+ endedAt: string;
18
+ locationWaitingFor: Location;
19
+ patient: Patient;
20
+ priority: Concept;
21
+ priorityComment: string | null;
22
+ providerWaitingFor: Provider;
23
+ queue: Queue;
24
+ startedAt: string;
25
+ status: Concept;
26
+ visit: Visit;
27
+ sortWeight: number;
28
+ queueComingFrom: Queue;
29
+ previousQueueEntry: QueueEntry;
30
+ }
31
+
32
+ export interface Provider extends OpenmrsResource {}
33
+
34
+ export interface Encounter {
35
+ uuid: string;
36
+ encounterDateTime: string;
37
+ encounterProviders: Array<{
38
+ uuid: string;
39
+ display: string;
40
+ encounterRole: {
41
+ uuid: string;
42
+ display: string;
43
+ };
44
+ provider: {
45
+ uuid: string;
46
+ person: {
47
+ uuid: string;
48
+ display: string;
49
+ };
50
+ };
51
+ }>;
52
+ encounterType: {
53
+ uuid: string;
54
+ display: string;
55
+ };
56
+ obs: Array<OpenmrsResource>;
57
+ orders: Array<OpenmrsResource>;
58
+ }