Skip to content

Commit

Permalink
(feat) O3-4219: Add ability to print patient prescriptions (#125)
Browse files Browse the repository at this point in the history
* Added preview model with printing capability

* Print preview and printing and printing done

* Added ability to select prescriptions to print

* wraped free text under translation.Extracted patient display processing logic into utility function

* Added all addtional instructions and indicating not refillable for prescription that are not refillable

* Updated display for none refillable prescriptions to no refills

* Captitalize patient name from styles, refactored patient name extractor from display

* Fixed class name spelling

* fixed variable names casing and naming, stylings and extracting of handlers funtions and wrapping them in useCallback for persisting avoiding reconstructions

* Misc tweaks

---------

Co-authored-by: Dennis Kigen <[email protected]>
  • Loading branch information
Omoshlawi and denniskigen authored Dec 17, 2024
1 parent b2a39f6 commit dd031d5
Show file tree
Hide file tree
Showing 21 changed files with 947 additions and 517 deletions.
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,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 Expand Up @@ -79,7 +80,7 @@
"eslint-plugin-unused-imports": "^2.0.0",
"husky": "^6.0.0",
"i18next": "^21.10.0",
"i18next-parser": "^6.6.0",
"i18next-parser": "^9.0.2",
"identity-obj-proxy": "^3.0.0",
"jest": "^28.1.3",
"jest-cli": "^28.1.3",
Expand Down
2 changes: 1 addition & 1 deletion src/dispensing-tiles/dispensing-tiles.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import styles from './dispensing-tiles.scss';

const DispensingTiles: React.FC = () => {
const { t } = useTranslation();
const { metrics, isError, isLoading } = useMetrics();
const { metrics, error, isLoading } = useMetrics();

if (isLoading) {
return <DataTableSkeleton role="progressbar" />;
Expand Down
2 changes: 1 addition & 1 deletion src/dispensing-tiles/dispensing-tiles.resource.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export function useMetrics() {

return {
metrics: metrics,
isError: error,
error,
isLoading: !data && !error,
};
}
Expand Down
22 changes: 0 additions & 22 deletions src/dispensing.test.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,3 @@
/**
* This is the root test for this page. It simply checks that the page
* renders. If the components of your page are highly interdependent,
* (e.g., if the `Hello` component had state that communicated
* information between `Greeter` and `PatientGetter`) then you might
* want to do most of your testing here. If those components are
* instead quite independent (as is the case in this example), then
* it would make more sense to test those components independently.
*
* The key thing to remember, always, is: write tests that behave like
* users. They should *look* for elements by their visual
* characteristics, *interact* with them, and (mostly) *assert* based
* on things that would be visually apparent to a user.
*
* To learn more about how we do testing, see the following resources:
* https://kentcdodds.com/blog/how-to-know-what-to-test
* https://kentcdodds.com/blog/testing-implementation-details
* https://kentcdodds.com/blog/common-mistakes-with-react-testing-library
*
* Kent C. Dodds is the inventor of `@testing-library`:
* https://testing-library.com/docs/guiding-principles
*/
import React from 'react';
import { render } from '@testing-library/react';
import Dispensing from './dispensing.component';
Expand Down
12 changes: 5 additions & 7 deletions src/history/history-and-comments.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const HistoryAndComments: React.FC<{
const { t } = useTranslation();
const session = useSession();
const config = useConfig<PharmacyConfig>();
const { medicationRequestBundles, prescriptionDate, isError, isLoading } = usePrescriptionDetails(
const { medicationRequestBundles, prescriptionDate, error, isLoading } = usePrescriptionDetails(
encounterUuid,
config.refreshInterval,
);
Expand All @@ -60,7 +60,7 @@ const HistoryAndComments: React.FC<{
(performer) =>
performer?.actor?.reference?.length > 1 &&
performer.actor.reference.split('/')[1] === session.currentProvider.uuid,
) != null
) !== null
) {
return true;
}
Expand Down Expand Up @@ -133,7 +133,7 @@ const HistoryAndComments: React.FC<{
props: Record<string, unknown>;
};
const workspaceTitle = getWorkspaceTitle(medicationDispense);
launchWorkspace(workspaceName, { workspaceTitle, ...props, });
launchWorkspace(workspaceName, { workspaceTitle, ...props });
};

if (!editable && !deletable) {
Expand All @@ -145,9 +145,7 @@ const HistoryAndComments: React.FC<{
flipped={true}
className={styles.medicationEventActionMenu}>
{editable && (
<OverflowMenuItem
onClick={handleEdit}
itemText={t('editRecord', 'Edit Record')}></OverflowMenuItem>
<OverflowMenuItem onClick={handleEdit} itemText={t('editRecord', 'Edit record')}></OverflowMenuItem>
)}
{deletable && (
<OverflowMenuItem
Expand Down Expand Up @@ -215,7 +213,7 @@ const HistoryAndComments: React.FC<{
return (
<div className={styles.historyAndCommentsContainer}>
{isLoading && <DataTableSkeleton role="progressbar" />}
{isError && <p>{t('error', 'Error')}</p>}
{error && <p>{t('error', 'Error')}</p>}
{medicationRequestBundles &&
medicationRequestBundles
.flatMap((medicationDispenseBundle) => medicationDispenseBundle.dispenses)
Expand Down
13 changes: 8 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import DispensingDashboardComponent from './dashboard/dispensing-dashboard.compo
import DispensingLinkComponent from './dispensing-link.component';
import DispensingLinkHomepageComponent from './dashboard/dispensing-dashboard-link.component';
import PauseActionButton from './components/prescription-actions/pause-action-button.component';
import PrescriptionPrintPreviewModal from './print-prescription/prescription-print-preview.modal';

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

Expand All @@ -17,16 +18,16 @@ const options = {
moduleName,
};

export function startupApp() {
defineConfigSchema(moduleName, configSchema);
}

export const dispensing = getSyncLifecycle(DispensingComponent, options);

export const dispensingLink = getSyncLifecycle(DispensingLinkComponent, options);

export const dispensingDashboard = getSyncLifecycle(DispensingDashboardComponent, options);

export function startupApp() {
defineConfigSchema(moduleName, configSchema);
}

export const dispensingDashboardLink = getSyncLifecycle(DispensingLinkHomepageComponent, options);

// Prescription action buttons
Expand All @@ -37,7 +38,9 @@ export const pauseActionButton = getSyncLifecycle(PauseActionButton, options);
// Dispensing workspace
// t('closePrescription', 'Close prescription')
export const closeDispenseWorkspace = getAsyncLifecycle(() => import('./forms/close-dispense-form.workspace'), options);
// t('dispensDPrescription', 'Dispense prescription')
// t('dispensePrescription', 'Dispense prescription')
export const dispenseWorkspace = getAsyncLifecycle(() => import('./forms/dispense-form.workspace'), options);
// t('pausePrescription', 'Pause prescription')
export const pauseDispenseWorkspace = getAsyncLifecycle(() => import('./forms/pause-dispense-form.workspace'), options);

export const printPrescriptionPreviewModal = getSyncLifecycle(PrescriptionPrintPreviewModal, options);
2 changes: 1 addition & 1 deletion src/location/location.resource.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export function useLocationForFiltering(config: PharmacyConfig) {

return {
filterLocations,
isError: error,
error,
isLoading: !filterLocations && !error,
};
}
4 changes: 2 additions & 2 deletions src/medication-dispense/medication-dispense.resource.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { fhirBaseUrl, restBaseUrl, openmrsFetch, type Session } from '@openmrs/esm-framework';
import dayjs from 'dayjs';
import useSWR from 'swr';
import { fhirBaseUrl, restBaseUrl, openmrsFetch, type Session } from '@openmrs/esm-framework';
import {
type MedicationDispense,
type MedicationDispenseStatus,
Expand Down Expand Up @@ -51,8 +51,8 @@ export function useOrderConfig() {
);
return {
orderConfigObject: data ? data.data : null,
error,
isLoading: !data && !error,
isError: error,
isValidating,
};
}
Expand Down
4 changes: 2 additions & 2 deletions src/medication-request/medication-request.resource.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ export function usePrescriptionDetails(encounterUuid: string, refreshInterval =
return {
medicationRequestBundles,
prescriptionDate,
isError: error,
error,
isLoading,
};
}
Expand All @@ -193,7 +193,7 @@ export function usePatientAllergies(patientUuid: string, refreshInterval) {
return {
allergies,
totalAllergies: data?.data.total,
isError: error,
error,
};
}

Expand Down
24 changes: 24 additions & 0 deletions src/prescriptions/prescription-actions.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react';
import { Layer } from '@carbon/react';
import PrescriptionPrintAction from '../print-prescription/prescription-print-action.component';
import styles from './prescription-actions.scss';

type PrescriptionsActionsFooterProps = {
encounterUuid: string;
patientUuid: string;
};

const PrescriptionsActionsFooter: React.FC<PrescriptionsActionsFooterProps> = ({ encounterUuid, patientUuid }) => {
return (
<Layer className={styles.actionsContainer}>
<div className={styles.actionCluster}>
{/* Left buttons */}
<PrescriptionPrintAction encounterUuid={encounterUuid} patientUuid={patientUuid} />
</div>

<div className={styles.actionCluster}>{/* Right buttons */}</div>
</Layer>
);
};

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

.actionsContainer {
flex-direction: row;
display: flex;
justify-content: space-between;
}

.actionCluster {
gap: layout.$spacing-03;
display: flex;
flex-direction: row;
}
11 changes: 4 additions & 7 deletions src/prescriptions/prescription-details.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { PRIVILEGE_CREATE_DISPENSE } from '../constants';
import { usePatientAllergies, usePrescriptionDetails } from '../medication-request/medication-request.resource';
import ActionButtons from '../components/action-buttons.component';
import MedicationEvent from '../components/medication-event.component';
import PrescriptionsActionsFooter from './prescription-actions.component';
import styles from './prescription-details.scss';

const PrescriptionDetails: React.FC<{
Expand All @@ -20,10 +21,7 @@ const PrescriptionDetails: React.FC<{
const config = useConfig<PharmacyConfig>();
const [isAllergiesLoading, setAllergiesLoadingStatus] = useState(true);
const { allergies, totalAllergies } = usePatientAllergies(patientUuid, config.refreshInterval);
const { medicationRequestBundles, isError, isLoading } = usePrescriptionDetails(
encounterUuid,
config.refreshInterval,
);
const { medicationRequestBundles, error, isLoading } = usePrescriptionDetails(encounterUuid, config.refreshInterval);

useEffect(() => {
if (typeof totalAllergies == 'number') {
Expand Down Expand Up @@ -89,11 +87,9 @@ const PrescriptionDetails: React.FC<{
</div>
</Tile>
)}

<h5 style={{ paddingTop: '8px', paddingBottom: '8px', fontSize: '0.9rem' }}>{t('prescribed', 'Prescribed')}</h5>

{isLoading && <DataTableSkeleton role="progressbar" />}
{isError && <p>{t('error', 'Error')}</p>}
{error && <p>{t('error', 'Error')}</p>}
{medicationRequestBundles &&
medicationRequestBundles.map((bundle) => {
return (
Expand All @@ -113,6 +109,7 @@ const PrescriptionDetails: React.FC<{
</Tile>
);
})}
<PrescriptionsActionsFooter encounterUuid={encounterUuid} patientUuid={patientUuid} />
</div>
);
};
Expand Down
30 changes: 30 additions & 0 deletions src/print-prescription/prescription-print-action.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React, { useCallback } 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;
};

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

const handleClick = useCallback(() => {
const dispose = showModal('prescription-print-preview-modal', {
onClose: () => dispose(),
encounterUuid,
patientUuid,
});
}, [encounterUuid, patientUuid]);

return (
<Button renderIcon={Printer} iconDescription={t('print', 'Print')} onClick={handleClick} kind="ghost">
{t('printPrescriptions', 'Print prescriptions')}
</Button>
);
};

export default PrescriptionPrintAction;
Loading

0 comments on commit dd031d5

Please sign in to comment.