Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(feat)O3 4219 add ability to print patient prescriptions #125

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@
"dependencies": {
"@carbon/react": "1.12.0",
"classnames": "^2.5.1",
"lodash-es": "^4.17.21"
"lodash-es": "^4.17.21",
"react-to-print": "^2.14.13"
},
"peerDependencies": {
"@openmrs/esm-framework": "*",
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import dispensingComponent from './dispensing.component';
import dispensingLinkComponent from './dispensing-link.component';
import dispensingDashboardComponent from './dashboard/dispensing-dashboard.component';
import dispensingLinkHomepageComponent from './dashboard/dispensing-dashboard-link.component';
import PrescriptionPrintPreviewModal from './print-prescription/prescription-print-preview.modal';

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

Expand All @@ -25,3 +26,4 @@ export function startupApp() {
}

export const dispensingDashboardLink = getSyncLifecycle(dispensingLinkHomepageComponent, options);
export const printPrescriptionPreviewModal = getSyncLifecycle(PrescriptionPrintPreviewModal, options);
9 changes: 8 additions & 1 deletion src/prescriptions/prescription-tab-panel.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import PrescriptionExpanded from './prescription-expanded.component';
import { usePrescriptionsTable } from '../medication-request/medication-request.resource';
import { type PharmacyConfig } from '../config-schema';
import styles from './prescriptions.scss';
import PrescriptionPrintAction from '../print-prescription/prescription-print-action.component';

interface PrescriptionTabPanelProps {
searchTerm: string;
Expand Down Expand Up @@ -53,6 +54,7 @@ const PrescriptionTabPanel: React.FC<PrescriptionTabPanelProps> = ({ searchTerm,
{ header: t('drugs', 'Drugs'), key: 'drugs' },
{ header: t('lastDispenser', 'Last dispenser'), key: 'lastDispenser' },
{ header: t('status', 'Status'), key: 'status' },
{ header: t('actions', 'Actions'), key: 'actions' },
];

// add the locations column, if enabled
Expand All @@ -66,14 +68,19 @@ const PrescriptionTabPanel: React.FC<PrescriptionTabPanelProps> = ({ searchTerm,
setNextOffSet(0);
}, [searchTerm]);

const rows = prescriptionsTableRows?.map((row) => ({
...row,
actions: <PrescriptionPrintAction encounterUuid={row.id} patientUuid={row.patient.uuid} status={row.status} />,
}));

return (
<TabPanel>
<div className={styles.patientListTableContainer}>
{isLoading && <DataTableSkeleton role="progressbar" />}
{error && <p>Error</p>}
{prescriptionsTableRows && (
<>
<DataTable rows={prescriptionsTableRows} headers={columns} isSortable>
<DataTable rows={rows} headers={columns} isSortable>
{({ rows, headers, getExpandHeaderProps, getHeaderProps, getRowProps, getTableProps }) => (
<TableContainer>
<Table {...getTableProps()} useZebraStyles>
Expand Down
34 changes: 34 additions & 0 deletions src/print-prescription/prescription-print-action.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react';
import { Button } from '@carbon/react';
import { Printer } from '@carbon/react/icons';
import { useTranslation } from 'react-i18next';
import { showModal } from '@openmrs/esm-framework';

type PrescriptionPrintActionProps = {
encounterUuid: string;
patientUuid: string;
status: string;
};

const PrescriptionPrintAction: React.FC<PrescriptionPrintActionProps> = ({ encounterUuid, patientUuid }) => {
const { t } = useTranslation();

return (
<Button
renderIcon={Printer}
iconDescription={t('print', 'Print')}
hasIconOnly
onClick={() => {
const dispose = showModal('prescription-print-preview-modal', {
onClose: () => dispose(),
encounterUuid,
patientUuid,
status,
});
}}
kind="ghost"
/>
);
};

export default PrescriptionPrintAction;
83 changes: 83 additions & 0 deletions src/print-prescription/prescription-print-preview.modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import {
Button,
ButtonSet,
InlineLoading,
InlineNotification,
ModalBody,
ModalFooter,
ModalHeader,
} from '@carbon/react';
import { ErrorState } from '@openmrs/esm-framework';
import React, { useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useReactToPrint } from 'react-to-print';
import { usePrescriptionDetails } from '../medication-request/medication-request.resource';
import PrescriptionsPrintout from './prescription-printout.component';
import styles from './print-prescription.scss';

type PrescriptionPrintPreviewModalProps = {
onClose: () => void;
encounterUuid: string;
patientUuid: string;
status: string;
};

const PrescriptionPrintPreviewModal: React.FC<PrescriptionPrintPreviewModalProps> = ({
onClose,
encounterUuid,
patientUuid,
status,
}) => {
const { t } = useTranslation();
const { medicationRequestBundles, isError, isLoading } = usePrescriptionDetails(encounterUuid);
const componentRef = useRef<HTMLDivElement>(null);
const [printError, setPrintError] = useState<string | null>(null);
const handlePrint = useReactToPrint({
content: () => componentRef.current,
onBeforeGetContent: () => {
setPrintError(null);
},
onPrintError: (error) => {
setPrintError(t('printError', 'An error occurred while printing. Please try again.'));
},
copyStyles: true,
});

return (
<>
<ModalHeader closeModal={onClose} className={styles.title}>
{t('printPrescriptions', 'Print Prescriptions')}
</ModalHeader>
<ModalBody>
{isLoading && (
<InlineLoading
status="active"
iconDescription="Loading"
description={t('loading', 'Loading prescriptions') + '....'}
/>
)}
{isError && <ErrorState error={isError} headerTitle={t('error', 'Error')} />}
{!isLoading && medicationRequestBundles?.length > 0 && (
<div ref={componentRef}>
<PrescriptionsPrintout medicationrequests={medicationRequestBundles} />
</div>
)}
{printError && (
<InlineNotification kind="error" title={t('printErrorTitle', 'Print Error')} subtitle={printError} />
)}
</ModalBody>
<ModalFooter>
<ButtonSet className={styles.btnSet}>
<Button kind="secondary" onClick={onClose} type="button">
{t('cancel', 'Cancel')}
</Button>
<Button kind="primary" onClick={handlePrint} type="button" disabled={isLoading}>
{t('print', 'Print')}
</Button>
</ButtonSet>
</ModalFooter>
</>
);
};

export default PrescriptionPrintPreviewModal;
124 changes: 124 additions & 0 deletions src/print-prescription/prescription-printout.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { Layer, StructuredListBody, StructuredListCell, StructuredListRow, StructuredListWrapper } from '@carbon/react';
import { formatDate, parseDate, useSession } from '@openmrs/esm-framework';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { type DosageInstruction, type MedicationRequestBundle, type Quantity } from '../types';
import {
getDosageInstruction,
getMedicationDisplay,
getMedicationReferenceOrCodeableConcept,
getQuantity,
getRefillsAllowed,
} from '../utils';
import styles from './print-prescription.scss';

type PrescriptionsPrintoutProps = {
medicationrequests: Array<MedicationRequestBundle>;
};

const PrescriptionsPrintout: React.FC<PrescriptionsPrintoutProps> = ({ medicationrequests }) => {
const { t } = useTranslation();
const {
sessionLocation: { display: facilityName },
} = useSession();
const patient = medicationrequests[0]?.request?.subject;
return (
<Layer className={styles.printOutContainer}>
<StructuredListWrapper>
{/* <StructuredListHead>
<StructuredListRow head>
<StructuredListCell head>Patient Name Here</StructuredListCell>
<StructuredListCell head>Facility name here</StructuredListCell>
</StructuredListRow>
</StructuredListHead> */}
<StructuredListBody>
<StructuredListRow head>
<StructuredListCell head>
<br />
<br />
<p className={styles.printoutTitle}>Prescription Instructions</p>
{patient && (
<p className={styles.faintText} style={{ textAlign: 'center' }}>
{patient.display
.match(/^([A-Za-z\s]+)\s\(/)
?.at(1)
?.toUpperCase()}
</p>
)}
<br />
<br />
</StructuredListCell>
</StructuredListRow>
{medicationrequests?.map((request, index) => {
const medicationEvent = request.request;
const dosageInstruction: DosageInstruction = getDosageInstruction(medicationEvent.dosageInstruction);
const quantity: Quantity = getQuantity(medicationEvent);
const refillsAllowed: number = getRefillsAllowed(medicationEvent);
return (
<div key={index}>
{dosageInstruction && (
<StructuredListRow>
<StructuredListCell>
<p>
<strong>
{getMedicationDisplay(getMedicationReferenceOrCodeableConcept(medicationEvent))}
</strong>
</p>
<br />
<p>
<span className={styles.faintText}>{t('dose', 'Dose')}</span>
{': '}
<span>
{dosageInstruction?.doseAndRate?.map((doseAndRate, index) => {
return (
<span key={index}>
{doseAndRate?.doseQuantity?.value} {doseAndRate?.doseQuantity?.unit}
</span>
);
})}
</span>{' '}
&mdash; {dosageInstruction?.route?.text} &mdash; {dosageInstruction?.timing?.code?.text}{' '}
{dosageInstruction?.timing?.repeat?.duration
? 'for ' +
dosageInstruction?.timing?.repeat?.duration +
' ' +
dosageInstruction?.timing?.repeat?.durationUnit
: ' '}
{quantity && (
<p>
<span className={styles.faintText}>{t('quantity', 'Quantity')}</span>
{': '}
<span>
{quantity.value} {quantity.unit}
</span>
</p>
)}
</p>
<p>
<span className={styles.faintText}>{t('datePrescribed', 'Date prescribed')}</span>
{': '} <span>{formatDate(parseDate((request.request as any).authoredOn))}</span>
</p>
{(refillsAllowed || refillsAllowed === 0) && (
<p>
<span className={styles.faintText}>{t('refills', 'Refills')}</span>
{': '} <span>{refillsAllowed}</span>
</p>
)}
{dosageInstruction?.text && <p>{dosageInstruction.text}</p>}
{dosageInstruction?.additionalInstruction?.length > 0 && (
<p>{dosageInstruction?.additionalInstruction[0].text}</p>
)}
</StructuredListCell>
</StructuredListRow>
)}
</div>
);
})}
<p className={styles.facilityName}>{facilityName}</p>
</StructuredListBody>
</StructuredListWrapper>
</Layer>
);
};

export default PrescriptionsPrintout;
35 changes: 35 additions & 0 deletions src/print-prescription/print-prescription.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
@use '@carbon/layout';
@use '@carbon/type';
@use '@carbon/colors';

.title {
@include type.type-style('heading-02');
}

.btnSet {
width: 100%;
}

.faintText {
color: colors.$gray-50;
@include type.type-style('heading-01');
}

.printOutContainer {
background-color: colors.$gray-20;
padding: layout.$spacing-05;
width: 320px;
}

.printoutTitle {
@include type.type-style('heading-03');
text-align: center;
font-weight: bold;
}

.facilityName {
color: colors.$gray-50;
text-align: end;
padding: layout.$spacing-05 0 layout.$spacing-05 0;
@include type.type-style('label-01');
}
8 changes: 7 additions & 1 deletion src/routes.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,11 @@
"title": "Dispensing"
}
}
],
"modals": [
{
"name": "prescription-print-preview-modal",
"component": "printPrescriptionPreviewModal"
}
]
}
}
11 changes: 11 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2945,6 +2945,7 @@ __metadata:
react-dom: "npm:^18.2.0"
react-i18next: "npm:^11.7.0"
react-router-dom: "npm:^6.3.0"
react-to-print: "npm:^2.14.13"
rxjs: "npm:^6.6.7"
swr: "npm:^2.2.5"
typescript: "npm:^4.3.2"
Expand Down Expand Up @@ -14212,6 +14213,16 @@ __metadata:
languageName: node
linkType: hard

"react-to-print@npm:^2.14.13":
version: 2.15.1
resolution: "react-to-print@npm:2.15.1"
peerDependencies:
react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
checksum: 10/0786fd5dd055b5b50f9a0dba99f0ccb30b9407b8e77748f5fc2557446ed0d0c65759e927cc2cdaa9e34f286838bbb230d3b0ba03d20b9754d0469fc4dac7c529
languageName: node
linkType: hard

"react@npm:^18.1.0, react@npm:^18.2.0":
version: 18.2.0
resolution: "react@npm:18.2.0"
Expand Down
Loading