Skip to content

Commit

Permalink
Merge pull request #9 from PIH/SL-833
Browse files Browse the repository at this point in the history
(feat) SL-833 add triage actions to queue entries in triage queue table
  • Loading branch information
chibongho authored Jan 13, 2025
2 parents a32151b + 3c784ab commit 4c5ff83
Show file tree
Hide file tree
Showing 10 changed files with 444 additions and 9 deletions.
22 changes: 22 additions & 0 deletions packages/esm-commons-app/src/hooks/useEncounters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { restBaseUrl, useOpenmrsFetchAll } from '@openmrs/esm-framework';
import { type Encounter } from '../service-queues-app/types';

interface EncounterSearchParams {
patient: string;
visit?: string;
encounterType?: string;
}

export function useEncounters(params: EncounterSearchParams) {
const url = `${restBaseUrl}/encounter?`;
const searchParams = new URLSearchParams();
searchParams.append('patient', params.patient);
if (params.visit) {
searchParams.append('visit', params.visit);
}
if (params.encounterType) {
searchParams.append('encounterType', params.encounterType);
}

return useOpenmrsFetchAll<Encounter>(url + searchParams.toString());
}
20 changes: 13 additions & 7 deletions packages/esm-commons-app/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,34 @@
import { getAsyncLifecycle } from '@openmrs/esm-framework';

const moduleName = '@pih/esm-commons-app';
const options = {
featureName: 'commons',
moduleName,
};

export const importTranslation = require.context('../translations', false, /.json$/, 'lazy');

// export const root = getAsyncLifecycle(() => import('./root.component'), options);

export const o2VisitSummaryWorkspaceSideRailIcon = getAsyncLifecycle(
() => import('./ward-app/o2-visit-summary-action-button.extension'),
options,
{ featureName: 'o2VisitSummaryWorkspaceSideRailIcon', moduleName },
);

export const o2VisitSummaryWorkspace = getAsyncLifecycle(
() => import('./ward-app/o2-visit-summary-workspace.component'),
options,
{ featureName: 'o2VisitSummaryWorkspace', moduleName },
);

export const o2PregnancyInfantDashboard = getAsyncLifecycle(
() => import('./ward-app/o2-pregnancy-infant-dashboard.extension'),
options,
{ featureName: 'o2PregnancyInfantDashboard', moduleName },
);

export const maternalTriageFormWorkspace = getAsyncLifecycle(
() => import('./service-queues-app/maternal-triage-form.workspace'),
{ featureName: 'maternalTriageFormWorkspace', moduleName },
);

export const triageWaitingQueueActions = getAsyncLifecycle(
() => import('./service-queues-app/maternal-triage-queue-actions.extension'),
{ featureName: 'triageWaitingQueueActions', moduleName },
);

export function startupApp() {}
13 changes: 13 additions & 0 deletions packages/esm-commons-app/src/routes.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
"name": "o2-pregnancy-infant-dashboard",
"component": "o2PregnancyInfantDashboard",
"slot": "ward-patient-workspace-content-slot"
},
{
"name": "triage-waiting-queue-actions",
"component": "triageWaitingQueueActions",
"slot": "queue-table-triage-waiting-queue-actions-slot"
}
],
"workspaces": [
Expand All @@ -25,6 +30,14 @@
"width": "extra-wide",
"groups": ["ward-patient"],
"canMaximize": true
},
{
"name": "maternal-triage-form-workspace",
"component": "maternalTriageFormWorkspace",
"title": "maternalTriageForm",
"type": "maternal-triage-form",
"width": "extra-wide",
"canMaximize": true
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { type DefaultWorkspaceProps, type Patient, showSnackbar } from '@openmrs/esm-framework';
import React, { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import O2IFrame from '../ward-app/o2-iframe.component';
import { updateQueueEntry, useMutateQueueEntries } from './maternal-triage-queue-actions.resource';
import { type QueueEntry } from './types';
import { useEncounters } from '../hooks/useEncounters';
import { Loading } from '@carbon/react';
import { InlineNotification } from '@carbon/react';

interface MaternalTriageFormWorkspaceProps extends DefaultWorkspaceProps {
queueEntry: QueueEntry;
patient: Patient;
}

const MATERNAL_TRIAGE_FORM_ENCOUNTER_TYPE = '41911448-71a1-43d7-bba8-dc86339850da';

/**
* Workspace to display the Maternal Triage HTML Form, rendered in a iframe
*/
const MaternalTriageFormWorkspace: React.FC<MaternalTriageFormWorkspaceProps> = ({
queueEntry,
patient,
closeWorkspace,
}) => {
const { t } = useTranslation();
const { mutateQueueEntries } = useMutateQueueEntries();
const {
data: filledOutTriageForms,
isLoading,
error,
} = useEncounters({
patient: patient.uuid,
visit: queueEntry.visit.uuid,
encounterType: MATERNAL_TRIAGE_FORM_ENCOUNTER_TYPE,
});

useEffect(() => {
const onMessage = async (event: MessageEvent) => {
if (event.data == 'triageFormSubmitted') {
const endQueueEntry = () => {
return updateQueueEntry(queueEntry.uuid, {
endedAt: new Date().toISOString(),
});
};

await endQueueEntry();
await mutateQueueEntries();
closeWorkspace();
showSnackbar({
isLowContrast: true,
kind: 'success',
title: t('triageFormSubmitted', 'Triage form successfully submitted for {{patient}}.', {
patient: patient.person.display,
}),
});
}
};

window.addEventListener('message', onMessage);
return () => window.removeEventListener('message', onMessage);
}, []);

const patientUuid = patient.uuid;
const visitUuid = queueEntry.visit.id;

const elementsToHide = [
'header',
'#breadcrumbs',

// prevent O2 success toast from showing; After submitting form for a patient,
// the success toast for that patient shows when opening form for another patient
'.toast-type-success',
];

const customJavaScript = `
htmlForm.setSuccessFunction(() => window.top.postMessage('triageFormSubmitted'))
`;

if (isLoading) {
return <Loading withOverlay={false} small />;
} else if (error) {
return (
<InlineNotification
kind="error"
lowContrast={true}
hideCloseButton={true}
title={t('errorLoadingPatientForm', 'Error loading patient form')}
/>
);
} else {
// If patient does not have triage form filled out already, we load a new one,
// else, we load the last filled out form to edit
const src =
filledOutTriageForms.length == 0
? `${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`
: `${window.openmrsBase}/htmlformentryui/htmlform/editHtmlFormWithStandardUi.page?patientId=${patientUuid}&encounterId=${filledOutTriageForms[filledOutTriageForms.length - 1].uuid}`;

return <O2IFrame key={patientUuid} src={src} elementsToHide={elementsToHide} customJavaScript={customJavaScript} />;
}
};

export default MaternalTriageFormWorkspace;
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { Button, OverflowMenu, OverflowMenuItem } from '@carbon/react';
import { isDesktop, launchWorkspace, showModal, useConfig, useLayoutType } from '@openmrs/esm-framework';
import React from 'react';
import { useTranslation } from 'react-i18next';
import styles from './maternal-trial-queue-actions.scss';
import { type QueueEntry } from './types';
import { transitionQueueEntry, useMutateQueueEntries } from './maternal-triage-queue-actions.resource';

// types taken from esm-service-queues-app
interface QueueTableCellComponentProps {
queueEntry: QueueEntry;
}

interface ConfigObject {
concepts: {
defaultTransitionStatus: string;
};
// other fields not shown
}

/**
* This extension provides an extra "Triage" action, along with the other standard actions for
* queue entries in the queues table. The Triage action opens the maternal triage form, and also
* has the side effect of putting the patient's queue entry into the "in service" status, if they have
* not yet already
* @param param0
* @returns
*/
const MaternalTriageQueueActions: React.FC<QueueTableCellComponentProps> = ({ queueEntry }) => {
const { t } = useTranslation();
const layout = useLayoutType();

const { mutateQueueEntries } = useMutateQueueEntries();
const { concepts } = useConfig<ConfigObject>({ externalModuleName: '@openmrs/esm-service-queues-app' });
const inServiceStatus = concepts.defaultTransitionStatus;

const { patient } = queueEntry;

return (
<div className={styles.actionsCell}>
<Button
kind="ghost"
aria-label={t('triage', 'Triage')}
onClick={async () => {
if (queueEntry.status.uuid !== inServiceStatus) {
const res = await transitionQueueEntry({
queueEntryToTransition: queueEntry.uuid,
newStatus: inServiceStatus,
});
mutateQueueEntries();
launchWorkspace('maternal-triage-form-workspace', { queueEntry: res.data, patient });
} else {
launchWorkspace('maternal-triage-form-workspace', { queueEntry, patient });
}
}}
size={isDesktop(layout) ? 'sm' : 'lg'}>
{t('triage', 'Triage')}
</Button>

<OverflowMenu aria-label="Actions menu" size={isDesktop(layout) ? 'sm' : 'lg'} align="left" flipped>
<OverflowMenuItem
className={styles.menuItem}
aria-label={t('transition', 'Transition')}
hasDivider
onClick={() => {
const dispose = showModal('transition-queue-entry-modal', {
closeModal: () => dispose(),
queueEntry,
});
}}
itemText={t('transition', 'Transition')}
/>
<OverflowMenuItem
className={styles.menuItem}
aria-label={t('edit', 'Edit')}
hasDivider
onClick={() => {
const dispose = showModal('edit-queue-entry-modal', {
closeModal: () => dispose(),
queueEntry,
});
}}
itemText={t('edit', 'Edit')}
/>
<OverflowMenuItem
className={styles.menuItem}
aria-label={t('removePatient', 'Remove patient')}
hasDivider
onClick={() => {
const dispose = showModal('end-queue-entry-modal', {
closeModal: () => dispose(),
queueEntry,
});
}}
itemText={t('removePatient', 'Remove patient')}
/>
{queueEntry.previousQueueEntry == null ? (
<OverflowMenuItem
className={styles.menuItem}
aria-label={t('delete', 'Delete')}
hasDivider
isDelete
onClick={() => {
const dispose = showModal('void-queue-entry-modal', {
closeModal: () => dispose(),
queueEntry,
});
}}
itemText={t('delete', 'Delete')}
/>
) : (
<OverflowMenuItem
className={styles.menuItem}
aria-label={t('undoTransition', 'Undo transition')}
hasDivider
isDelete
onClick={() => {
const dispose = showModal('undo-transition-queue-entry-modal', {
closeModal: () => dispose(),
queueEntry,
});
}}
itemText={t('undoTransition', 'Undo transition')}
/>
)}
</OverflowMenu>
</div>
);
};

export default MaternalTriageQueueActions;
Loading

0 comments on commit 4c5ff83

Please sign in to comment.