diff --git a/package.json b/package.json index 8754f71b2b..db838568cb 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "@hookform/resolvers": "^3.3.1", "classnames": "^2.3.2", "react-hook-form": "^7.46.2", - "react-to-print": "^2.14.13", + "react-to-print": "^3.0.0-beta-1", "zod": "^3.22.2" }, "devDependencies": { diff --git a/packages/esm-patient-banner-app/src/banner-tags/print-identifier-sticker-content.component.tsx b/packages/esm-patient-banner-app/src/banner-tags/print-identifier-sticker-content.component.tsx new file mode 100644 index 0000000000..710ca77098 --- /dev/null +++ b/packages/esm-patient-banner-app/src/banner-tags/print-identifier-sticker-content.component.tsx @@ -0,0 +1,68 @@ +import React, { forwardRef, useEffect, useImperativeHandle, useRef } from 'react'; +import { useConfig } from '@openmrs/esm-framework'; +import { type ConfigObject } from '../config-schema'; +import IdentifierSticker from './print-identifier-sticker.component'; +import styles from './print-identifier-sticker-content.scss'; + +interface PrintIdentifierStickerContentProps { + labels: Array<{}>; + numberOfLabelColumns: number; + numberOfLabelRowsPerPage: number; + patient: fhir.Patient; +} + +const PrintIdentifierStickerContent = forwardRef<HTMLDivElement, PrintIdentifierStickerContentProps>( + ({ labels, numberOfLabelColumns, numberOfLabelRowsPerPage, patient }, ref) => { + const { printIdentifierStickerWidth, printIdentifierStickerHeight, printIdentifierStickerPaperSize } = + useConfig<ConfigObject>(); + const divRef = useRef<HTMLDivElement>(); + + useImperativeHandle(ref, () => divRef.current, []); + + useEffect(() => { + if (divRef.current) { + const style = divRef.current.style; + style.setProperty('--omrs-print-label-paper-size', printIdentifierStickerPaperSize); + style.setProperty('--omrs-print-label-colums', numberOfLabelColumns.toString()); + style.setProperty('--omrs-print-label-rows', numberOfLabelRowsPerPage.toString()); + style.setProperty('--omrs-print-label-sticker-height', printIdentifierStickerHeight); + style.setProperty('--omrs-print-label-sticker-width', printIdentifierStickerWidth); + } + }, [ + numberOfLabelColumns, + numberOfLabelRowsPerPage, + printIdentifierStickerHeight, + printIdentifierStickerPaperSize, + printIdentifierStickerWidth, + ]); + + const maxLabelsPerPage = numberOfLabelRowsPerPage * numberOfLabelColumns; + const pages: Array<typeof labels> = []; + + for (let i = 0; i < labels.length; i += maxLabelsPerPage) { + pages.push(labels.slice(i, i + maxLabelsPerPage)); + } + + if (numberOfLabelColumns < 1 || numberOfLabelRowsPerPage < 1 || labels.length < 1) { + return; + } + + return ( + <div ref={divRef} className={styles.printRoot}> + {pages.map((pageLabels, pageIndex) => ( + <div key={pageIndex} className={pageIndex < pages.length - 1 ? styles.pageBreak : ''}> + <div className={styles.labelsContainer}> + {pageLabels.map((label, index) => ( + <div key={index} className={styles.printContainer}> + <IdentifierSticker patient={patient} /> + </div> + ))} + </div> + </div> + ))} + </div> + ); + }, +); + +export default PrintIdentifierStickerContent; diff --git a/packages/esm-patient-banner-app/src/banner-tags/print-identifier-sticker-content.scss b/packages/esm-patient-banner-app/src/banner-tags/print-identifier-sticker-content.scss new file mode 100644 index 0000000000..427dfc991b --- /dev/null +++ b/packages/esm-patient-banner-app/src/banner-tags/print-identifier-sticker-content.scss @@ -0,0 +1,41 @@ +@use '@carbon/layout'; +@use '@carbon/type'; +@use '@openmrs/esm-styleguide/src/vars' as *; + +.printRoot { + @media print { + @page { + size: var(--omrs-print-label-paper-size, auto); + } + + html, + body { + height: initial !important; + overflow: initial !important; + background-color: white; + } + } + + .labelsContainer { + grid-template-columns: repeat(var(--omrs-print-label-colums, 1), 1fr); + grid-template-rows: repeat(var(--omrs-print-label-rows, 1), auto); + } +} + +.printContainer { + height: var(--omrs-print-label-sticker-height, 11rem); + width: var(--omrs-print-label-sticker-width, 13rem); + background-color: $ui-01; +} + +.pageBreak { + page-break-after: always; +} + +.labelsContainer { + display: grid; + column-gap: 1.3rem; + row-gap: 1rem; + place-items: center; + background-color: white; +} diff --git a/packages/esm-patient-banner-app/src/banner-tags/print-identifier-sticker-modal.test.tsx b/packages/esm-patient-banner-app/src/banner-tags/print-identifier-sticker-modal.test.tsx index 9092801ae8..42a742c18d 100644 --- a/packages/esm-patient-banner-app/src/banner-tags/print-identifier-sticker-modal.test.tsx +++ b/packages/esm-patient-banner-app/src/banner-tags/print-identifier-sticker-modal.test.tsx @@ -1,64 +1,46 @@ import React from 'react'; import userEvent from '@testing-library/user-event'; import { render, screen } from '@testing-library/react'; -import { useReactToPrint } from 'react-to-print'; import { getDefaultsFromConfigSchema, useConfig } from '@openmrs/esm-framework'; import { configSchema, type ConfigObject } from '../config-schema'; import { mockPatient } from 'tools'; -import PrintIdentifierSticker from './print-identifier-sticker.modal'; +import PrintIdentifierStickerModal from './print-identifier-sticker.modal'; const mockCloseModal = jest.fn(); -const mockUseReactToPrint = jest.mocked(useReactToPrint); const mockUseConfig = jest.mocked(useConfig<ConfigObject>); -jest.mock('react-to-print', () => { - const originalModule = jest.requireActual('react-to-print'); - - return { - ...originalModule, - useReactToPrint: jest.fn(), - }; -}); - mockUseConfig.mockReturnValue({ ...getDefaultsFromConfigSchema(configSchema), printIdentifierStickerFields: ['name', 'identifier', 'age', 'dateOfBirth', 'gender'], }); describe('PrintIdentifierSticker', () => { - test('renders the component', () => { - render(<PrintIdentifierSticker patient={mockPatient} closeModal={mockCloseModal} />); - - expect(screen.getByText(/Print Identifier Sticker/i)).toBeInTheDocument(); - expect(screen.getByText('John Wilson')).toBeInTheDocument(); - expect(screen.getByText('100GEJ')).toBeInTheDocument(); - expect(screen.getByText('1972-04-04')).toBeInTheDocument(); - }); - - test('calls closeModal when cancel button is clicked', async () => { + test('renders a modal with patient details and print options', async () => { const user = userEvent.setup(); - render(<PrintIdentifierSticker patient={mockPatient} closeModal={mockCloseModal} />); - - const cancelButton = screen.getByRole('button', { name: /Cancel/i }); - expect(cancelButton).toBeInTheDocument(); - - await user.click(cancelButton); - expect(mockCloseModal).toHaveBeenCalled(); - }); - - test('calls the print function when print button is clicked', async () => { - const handlePrint = jest.fn(); - mockUseReactToPrint.mockReturnValue(handlePrint); - - const user = userEvent.setup(); - - render(<PrintIdentifierSticker patient={mockPatient} closeModal={mockCloseModal} />); - - const printButton = screen.getByRole('button', { name: /Print/i }); - expect(printButton).toBeInTheDocument(); - - await user.click(printButton); - expect(handlePrint).toHaveBeenCalled(); + render(<PrintIdentifierStickerModal patient={mockPatient} closeModal={mockCloseModal} />); + + expect(screen.getByRole('heading', { name: /print identifier sticker/i })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /show preview/i })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /cancel/i })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /print/i })).toBeInTheDocument(); + expect(screen.getByRole('spinbutton', { name: /no. of patient id sticker columns/i })).toHaveValue(3); + expect(screen.getByRole('spinbutton', { name: /no. of patient id sticker rows per page/i })).toHaveValue(5); + expect(screen.getByRole('spinbutton', { name: /no. of patient id stickers/i })).toHaveValue(30); + + const modalBody = screen.getByRole('region'); + expect(modalBody).toHaveTextContent(/john wilson/i); + expect(modalBody).toHaveTextContent(/old identification number/i); + expect(modalBody).toHaveTextContent(/100732he/i); + expect(modalBody).toHaveTextContent(/openmrs id/i); + expect(modalBody).toHaveTextContent(/100gej/i); + expect(modalBody).toHaveTextContent(/sex/i); + expect(modalBody).toHaveTextContent(/male/i); + expect(modalBody).toHaveTextContent(/dob/i); + expect(modalBody).toHaveTextContent(/1972-04-04/i); + expect(modalBody).toHaveTextContent(/age/i); + expect(modalBody).toHaveTextContent(/52 yrs/i); + await user.click(screen.getByRole('button', { name: /show preview/i })); + expect(screen.getByRole('button', { name: /hide preview/i })).toBeInTheDocument(); }); }); diff --git a/packages/esm-patient-banner-app/src/banner-tags/print-identifier-sticker.component.tsx b/packages/esm-patient-banner-app/src/banner-tags/print-identifier-sticker.component.tsx new file mode 100644 index 0000000000..ae24657d18 --- /dev/null +++ b/packages/esm-patient-banner-app/src/banner-tags/print-identifier-sticker.component.tsx @@ -0,0 +1,75 @@ +import React, { forwardRef, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { age, getPatientName, useConfig, getCoreTranslation } from '@openmrs/esm-framework'; +import { type ConfigObject } from '../config-schema'; +import styles from './print-identifier-sticker.scss'; + +interface IdentifierStickerProps { + patient: fhir.Patient; +} + +const getGender = (gender: string): string => { + switch (gender) { + case 'male': + return getCoreTranslation('male', 'Male'); + case 'female': + return getCoreTranslation('female', 'Female'); + case 'other': + return getCoreTranslation('other', 'Other'); + case 'unknown': + return getCoreTranslation('unknown', 'Unknown'); + default: + return gender; + } +}; + +const IdentifierSticker = forwardRef<HTMLDivElement, IdentifierStickerProps>(({ patient }, ref) => { + const { t } = useTranslation(); + const { printIdentifierStickerFields, excludePatientIdentifierCodeTypes } = useConfig<ConfigObject>(); + + const patientDetails = useMemo(() => { + if (!patient) { + return {}; + } + + const identifiers = + patient.identifier?.filter( + (identifier) => !excludePatientIdentifierCodeTypes?.uuids.includes(identifier.type.coding[0].code), + ) ?? []; + + return { + address: patient.address, + age: age(patient.birthDate), + dateOfBirth: patient.birthDate, + gender: getGender(patient.gender), + id: patient.id, + identifiers: [...identifiers], + name: patient ? getPatientName(patient) : '', + photo: patient.photo, + }; + }, [excludePatientIdentifierCodeTypes?.uuids, patient]); + + return ( + <div ref={ref} className={styles.stickerContainer}> + {printIdentifierStickerFields.includes('name') && <div className={styles.patientName}>{patientDetails.name}</div>} + {patientDetails.identifiers.map((identifier) => { + return ( + <p key={identifier?.id}> + {identifier?.type?.text}: <span className="patient-identifier">{identifier?.value}</span> + </p> + ); + })} + <p> + {getCoreTranslation('sex', 'Sex')}: <span className="patient-gender">{patientDetails.gender}</span> + </p> + <p> + {t('dob', 'DOB')}: <span className="patient-dob">{patientDetails.dateOfBirth}</span> + </p> + <p> + {getCoreTranslation('age', 'Age')}: <span className="patient-age">{patientDetails.age}</span> + </p> + </div> + ); +}); + +export default IdentifierSticker; diff --git a/packages/esm-patient-banner-app/src/banner-tags/print-identifier-sticker.modal.tsx b/packages/esm-patient-banner-app/src/banner-tags/print-identifier-sticker.modal.tsx index 47dac29857..9402352c3b 100644 --- a/packages/esm-patient-banner-app/src/banner-tags/print-identifier-sticker.modal.tsx +++ b/packages/esm-patient-banner-app/src/banner-tags/print-identifier-sticker.modal.tsx @@ -1,108 +1,47 @@ -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { useTranslation, type TFunction } from 'react-i18next'; -import { useReactToPrint } from 'react-to-print'; -import { Button, InlineLoading, ModalBody, ModalFooter, ModalHeader } from '@carbon/react'; -import { age, getPatientName, showSnackbar, useConfig, getCoreTranslation } from '@openmrs/esm-framework'; +import React, { useCallback, useRef, useState } from 'react'; +import classNames from 'classnames'; +import { useTranslation } from 'react-i18next'; +import { useReactToPrint, type UseReactToPrintOptions } from 'react-to-print'; +import { Button, InlineLoading, ModalBody, ModalFooter, ModalHeader, NumberInput, Stack } from '@carbon/react'; +import { getPatientName, showSnackbar, useConfig, getCoreTranslation } from '@openmrs/esm-framework'; import { type ConfigObject } from '../config-schema'; +import IdentifierSticker from './print-identifier-sticker.component'; +import PrintIdentifierStickerContent from './print-identifier-sticker-content.component'; import styles from './print-identifier-sticker.scss'; -interface PrintIdentifierStickerProps { +interface PrintIdentifierStickerModalProps { closeModal: () => void; patient: fhir.Patient; } -interface PrintComponentProps extends Partial<ConfigObject> { - patientDetails: { - address?: fhir.Address[]; - age?: string; - dateOfBirth?: string; - gender?: string; - id?: string; - identifiers?: fhir.Identifier[]; - name?: string; - photo?: fhir.Attachment[]; - }; - t: TFunction; -} - -const PrintIdentifierSticker: React.FC<PrintIdentifierStickerProps> = ({ closeModal, patient }) => { +const PrintIdentifierStickerModal: React.FC<PrintIdentifierStickerModalProps> = ({ closeModal, patient }) => { const { t } = useTranslation(); - const { printIdentifierStickerFields, printIdentifierStickerSize, excludePatientIdentifierCodeTypes } = + const { numberOfPatientIdStickers, numberOfPatientIdStickerRowsPerPage, numberOfPatientIdStickerColumns } = useConfig<ConfigObject>(); - const contentToPrintRef = useRef(null); - const onBeforeGetContentResolve = useRef<() => void | null>(null); + const contentToPrintRef = useRef<HTMLDivElement | null>(null); + const [numberOfLabelColumns, setNumberOfLabelColumns] = useState<number>(numberOfPatientIdStickerColumns); + const [numberOfLabelRowsPerPage, setNumberOfLabelRowsPerPage] = useState<number>(numberOfPatientIdStickerRowsPerPage); + const [numberOfLabels, setNumberOfLabels] = useState<number>(numberOfPatientIdStickers); const [isPrinting, setIsPrinting] = useState(false); const headerTitle = t('patientIdentifierSticker', 'Patient identifier sticker'); + const [isPreviewVisible, setIsPreviewVisible] = useState(false); - useEffect(() => { - if (isPrinting && onBeforeGetContentResolve.current) { - onBeforeGetContentResolve.current(); - } - }, [isPrinting]); - - const patientDetails = useMemo(() => { - if (!patient) { - return {}; - } - - const getGender = (gender: string): string => { - switch (gender) { - case 'male': - return getCoreTranslation('male', 'Male'); - case 'female': - return getCoreTranslation('female', 'Female'); - case 'other': - return getCoreTranslation('other', 'Other'); - case 'unknown': - return getCoreTranslation('unknown', 'Unknown'); - default: - return gender; - } - }; + const labels = Array.from({ length: numberOfLabels }); - const identifiers = - patient.identifier?.filter( - (identifier) => !excludePatientIdentifierCodeTypes?.uuids.includes(identifier.type.coding[0].code), - ) ?? []; - - return { - address: patient.address, - age: age(patient.birthDate), - dateOfBirth: patient.birthDate, - gender: getGender(patient.gender), - id: patient.id, - identifiers: [...identifiers], - name: patient ? getPatientName(patient) : '', - photo: patient.photo, - }; - }, [excludePatientIdentifierCodeTypes?.uuids, patient]); - - const handleBeforeGetContent = useCallback( - () => - new Promise<void>((resolve) => { - if (patient && headerTitle) { - onBeforeGetContentResolve.current = resolve; - setIsPrinting(true); - } - }), - [headerTitle, patient], - ); + const handleBeforePrint = useCallback(() => setIsPrinting(true), []); const handleAfterPrint = useCallback(() => { - onBeforeGetContentResolve.current = null; setIsPrinting(false); closeModal(); }, [closeModal]); - const handlePrintError = useCallback((errorLocation, error) => { - onBeforeGetContentResolve.current = null; - + const handlePrintError = useCallback<UseReactToPrintOptions['onPrintError']>((errorLocation, error) => { showSnackbar({ isLowContrast: false, kind: 'error', title: getCoreTranslation('printError', 'Print error'), subtitle: - getCoreTranslation('printErrorExplainer', 'An error occurred in "{{errorLocation}}": ', { errorLocation }) + + getCoreTranslation('printErrorExplainer', 'An error occurred during "{{errorLocation}}": ', { errorLocation }) + error, }); @@ -110,10 +49,9 @@ const PrintIdentifierSticker: React.FC<PrintIdentifierStickerProps> = ({ closeMo }, []); const handlePrint = useReactToPrint({ - content: () => contentToPrintRef.current, - documentTitle: `${patientDetails.name} - ${headerTitle}`, + contentRef: contentToPrintRef, + documentTitle: `${patient ? getPatientName(patient) : ''} - ${headerTitle}`, onAfterPrint: handleAfterPrint, - onBeforeGetContent: handleBeforeGetContent, onPrintError: handlePrintError, }); @@ -123,21 +61,62 @@ const PrintIdentifierSticker: React.FC<PrintIdentifierStickerProps> = ({ closeMo closeModal={closeModal} title={getCoreTranslation('printIdentifierSticker', 'Print identifier sticker')} /> - <ModalBody> - <div ref={contentToPrintRef}> - <style type="text/css" media="print"> - {` - @page { - size: ${printIdentifierStickerSize}; - } - `} - </style> - <PrintComponent - patientDetails={patientDetails} - printIdentifierStickerFields={printIdentifierStickerFields} - t={t} + <ModalBody aria-label="Print identifier sticker modal" hasScrollingContent> + <Stack gap={5}> + <NumberInput + hideSteppers + id="numberOfColumnsInput" + label={t('numberOfLabelColumns', 'No. of patient ID sticker columns')} + min={1} + onChange={(event: React.ChangeEvent<HTMLInputElement>) => + setNumberOfLabelColumns(parseInt(event.target.value || '1')) + } + value={numberOfLabelColumns} + /> + <NumberInput + hideSteppers + id="numberOfRowsPerPageInput" + label={t('numberOfLabelRowsPerPage', 'No. of patient ID sticker rows per page')} + min={1} + onChange={(event: React.ChangeEvent<HTMLInputElement>) => + setNumberOfLabelRowsPerPage(parseInt(event.target.value || '1')) + } + value={numberOfLabelRowsPerPage} /> - </div> + <NumberInput + hideSteppers + id="numberOfLabels" + label={t('numberOfLabels', 'No. of patient ID stickers')} + min={1} + onChange={(event: React.ChangeEvent<HTMLInputElement>) => + setNumberOfLabels(parseInt(event.target.value || '1')) + } + value={numberOfLabels} + /> + <div className={styles.stickerContent}> + <IdentifierSticker patient={patient} /> + <span> + <Button kind="ghost" onClick={() => setIsPreviewVisible(!isPreviewVisible)}> + {!isPreviewVisible ? t('showPreview', 'Show preview') : t('hidePreview', 'Hide preview')} + </Button> + </span> + </div> + <div + className={classNames(styles.previewContainer, { + [styles.hideResultsPreview]: !isPreviewVisible, + })} + > + <div ref={contentToPrintRef}> + <PrintIdentifierStickerContent + labels={labels} + numberOfLabelColumns={numberOfLabelColumns} + numberOfLabelRowsPerPage={numberOfLabelRowsPerPage} + patient={patient} + ref={contentToPrintRef} + /> + </div> + </div> + </Stack> </ModalBody> <ModalFooter> <Button kind="secondary" onClick={closeModal}> @@ -155,30 +134,4 @@ const PrintIdentifierSticker: React.FC<PrintIdentifierStickerProps> = ({ closeMo ); }; -const PrintComponent = ({ patientDetails, printIdentifierStickerFields, t }: PrintComponentProps) => { - return ( - <div className={styles.stickerContainer}> - {printIdentifierStickerFields.includes('name') && <div className={styles.patientName}>{patientDetails.name}</div>} - <div className={styles.detailsGrid}> - {patientDetails.identifiers.map((identifier) => { - return ( - <p key={identifier?.id}> - {identifier?.type?.text}: <strong>{identifier?.value}</strong> - </p> - ); - })} - <p> - {getCoreTranslation('sex', 'Sex')}: <strong>{patientDetails.gender}</strong> - </p> - <p> - {t('dob', 'DOB')}: <strong>{patientDetails.dateOfBirth}</strong> - </p> - <p> - {getCoreTranslation('age', 'Age')}: <strong>{patientDetails.age}</strong> - </p> - </div> - </div> - ); -}; - -export default PrintIdentifierSticker; +export default PrintIdentifierStickerModal; diff --git a/packages/esm-patient-banner-app/src/banner-tags/print-identifier-sticker.scss b/packages/esm-patient-banner-app/src/banner-tags/print-identifier-sticker.scss index 08e0962fdf..a8cc055584 100644 --- a/packages/esm-patient-banner-app/src/banner-tags/print-identifier-sticker.scss +++ b/packages/esm-patient-banner-app/src/banner-tags/print-identifier-sticker.scss @@ -1,5 +1,6 @@ @use '@carbon/layout'; @use '@carbon/type'; +@import '@openmrs/esm-styleguide/src/vars'; .stickerContainer { padding: layout.$spacing-03 layout.$spacing-05; @@ -49,6 +50,31 @@ } } +.previewContainer { + max-height: 10rem; + overflow-y: scroll; + background-color: white; + width: 100%; + border: 1px solid $ui-03; + margin-left: auto; + margin-right: auto; + padding: 2rem; +} + +.labelNumberInput { + padding: 1rem; +} + +.stickerContent { + display: inline-flex; + width: -webkit-fill-available; + justify-content: space-between; +} + +.hideResultsPreview { + display: none; +} + @media print { html, body { diff --git a/packages/esm-patient-banner-app/src/config-schema.ts b/packages/esm-patient-banner-app/src/config-schema.ts index 93c5d5e005..d1489ce351 100644 --- a/packages/esm-patient-banner-app/src/config-schema.ts +++ b/packages/esm-patient-banner-app/src/config-schema.ts @@ -22,6 +22,21 @@ export const configSchema = { }, }, }, + numberOfPatientIdStickers: { + _type: Type.Number, + _description: 'The number of patient ID stickers to print', + _default: '30', + }, + numberOfPatientIdStickerColumns: { + _type: Type.Number, + _description: 'The number of columns of patient ID stickers to print per page', + _default: '3', + }, + numberOfPatientIdStickerRowsPerPage: { + _type: Type.Number, + _description: 'The number of rows for patient ID stickers to print per page', + _default: '5', + }, printIdentifierStickerFields: { _type: Type.Array, _description: 'Patient demographics to include in the identifier sticker printout', @@ -30,11 +45,22 @@ export const configSchema = { _type: Type.String, }, }, - printIdentifierStickerSize: { + printIdentifierStickerPaperSize: { + _type: Type.String, + _description: + 'Specifies the paper size for printing, using units like "mm" or "in", or standard sizes such as "148mm 210mm", "A1", "A2", "A4", or "A5".', + _default: 'A4', + }, + printIdentifierStickerHeight: { _type: Type.String, _description: - 'Specifies the paper size for printing the sticker. You can define the size using units (e.g., mm, in) or named sizes (e.g., "148mm 210mm", "A1", "A2", "A4", "A5").', - _default: '4in 6in', + 'Specifies the height of each patient ID sticker in the printout in units such as px or rem e.g. "15px", "5rem"', + _default: '10rem', + }, + printIdentifierStickerWidth: { + _type: Type.String, + _description: 'The width of each patient ID sticker in the printout in units such as px or rem', + _default: '13rem', }, useRelationshipNameLink: { _type: Type.Boolean, @@ -47,7 +73,12 @@ export interface ConfigObject { excludePatientIdentifierCodeTypes: { uuids: Array<string>; }; + numberOfPatientIdStickerColumns: number; + numberOfPatientIdStickerRowsPerPage: number; + numberOfPatientIdStickers: number; printIdentifierStickerFields: Array<string>; - printIdentifierStickerSize: string; + printIdentifierStickerHeight: string; + printIdentifierStickerPaperSize: string; + printIdentifierStickerWidth: string; useRelationshipNameLink: boolean; } diff --git a/packages/esm-patient-banner-app/translations/en.json b/packages/esm-patient-banner-app/translations/en.json index c96dacfc21..f177cb1283 100644 --- a/packages/esm-patient-banner-app/translations/en.json +++ b/packages/esm-patient-banner-app/translations/en.json @@ -7,8 +7,13 @@ "countyDistrict": "District", "district": "District", "dob": "DOB", + "hidePreview": "Hide preview", + "numberOfLabelColumns": "No. of patient ID sticker columns", + "numberOfLabelRowsPerPage": "No. of patient ID sticker rows per page", + "numberOfLabels": "No. of patient ID stickers", "patientIdentifierSticker": "Patient identifier sticker", "postalCode": "Postal code", + "showPreview": "Show preview", "state": "State", "stateProvince": "State" } diff --git a/packages/esm-patient-labs-app/src/test-results/print-modal/print-modal.extension.tsx b/packages/esm-patient-labs-app/src/test-results/print-modal/print-modal.extension.tsx index 093b01e685..357cf939c2 100644 --- a/packages/esm-patient-labs-app/src/test-results/print-modal/print-modal.extension.tsx +++ b/packages/esm-patient-labs-app/src/test-results/print-modal/print-modal.extension.tsx @@ -21,6 +21,7 @@ import { useReactToPrint } from 'react-to-print'; import dayjs from 'dayjs'; import isBetween from 'dayjs/plugin/isBetween'; dayjs.extend(isBetween); + import { age, getPatientName, @@ -32,6 +33,7 @@ import { ResponsiveWrapper, } from '@openmrs/esm-framework'; import usePanelData from '../panel-view/usePanelData'; +import { type ObsRecord } from '../../types'; import styles from './print-modal.scss'; function PrintModal({ patientUuid, closeDialog }) { @@ -61,7 +63,7 @@ function PrintModal({ patientUuid, closeDialog }) { ]; const handlePrint = useReactToPrint({ - content: () => printContainerRef.current, + contentRef: printContainerRef, }); const patient = usePatient(patientUuid); @@ -246,7 +248,7 @@ function PrintModal({ patientUuid, closeDialog }) { ); } -const formatPanelForDisplay = (panel) => { +const formatPanelForDisplay = (panel: ObsRecord) => { return { id: panel.id, testType: panel.name, diff --git a/packages/esm-patient-medications-app/src/components/medications-details-table.component.tsx b/packages/esm-patient-medications-app/src/components/medications-details-table.component.tsx index 01cbca2268..52f9d37f91 100644 --- a/packages/esm-patient-medications-app/src/components/medications-details-table.component.tsx +++ b/packages/esm-patient-medications-app/src/components/medications-details-table.component.tsx @@ -197,26 +197,14 @@ const MedicationsDetailsTable: React.FC<ActiveMedicationsProps> = ({ }; }, [patient, t, excludePatientIdentifierCodeTypes?.uuids]); - const onBeforeGetContentResolve = useRef(null); - - useEffect(() => { - if (isPrinting && onBeforeGetContentResolve.current) { - onBeforeGetContentResolve.current(); - } - }, [isPrinting]); - const handlePrint = useReactToPrint({ - content: () => contentToPrintRef.current, + contentRef: contentToPrintRef, documentTitle: `OpenMRS - ${patientDetails.name} - ${title}`, - onBeforeGetContent: () => - new Promise((resolve) => { - if (patient && patient.patient && title) { - onBeforeGetContentResolve.current = resolve; - setIsPrinting(true); - } - }), + onBeforePrint: () => { + setIsPrinting(true); + return Promise.resolve(); + }, onAfterPrint: () => { - onBeforeGetContentResolve.current = null; setIsPrinting(false); }, }); diff --git a/packages/esm-patient-orders-app/src/components/orders-details-table.component.tsx b/packages/esm-patient-orders-app/src/components/orders-details-table.component.tsx index 3493800e92..9dfbbc2a9e 100644 --- a/packages/esm-patient-orders-app/src/components/orders-details-table.component.tsx +++ b/packages/esm-patient-orders-app/src/components/orders-details-table.component.tsx @@ -247,28 +247,14 @@ const OrderDetailsTable: React.FC<OrderDetailsProps> = ({ patientUuid, showAddBu }; }, [patient, excludePatientIdentifierCodeTypes?.uuids]); - const onBeforeGetContentResolve = useRef(null); - - useEffect(() => { - if (isPrinting && onBeforeGetContentResolve.current) { - onBeforeGetContentResolve.current(); - } - }, [isPrinting, onBeforeGetContentResolve]); - const handlePrint = useReactToPrint({ - content: () => contentToPrintRef.current, + contentRef: contentToPrintRef, documentTitle: `OpenMRS - ${patientDetails.name} - ${title}`, - onBeforeGetContent: () => - new Promise((resolve) => { - if (patient && patient?.patient && title) { - onBeforeGetContentResolve.current = resolve; - setIsPrinting(true); - } - }), - onAfterPrint: () => { - onBeforeGetContentResolve.current = null; - setIsPrinting(false); + onBeforePrint: () => { + setIsPrinting(true); + return Promise.resolve(); }, + onAfterPrint: () => setIsPrinting(false), }); const orderTypesToDisplay = useMemo( diff --git a/packages/esm-patient-vitals-app/src/vitals/vitals-overview.component.tsx b/packages/esm-patient-vitals-app/src/vitals/vitals-overview.component.tsx index d3e2d946ba..0eda740591 100644 --- a/packages/esm-patient-vitals-app/src/vitals/vitals-overview.component.tsx +++ b/packages/esm-patient-vitals-app/src/vitals/vitals-overview.component.tsx @@ -153,26 +153,14 @@ const VitalsOverview: React.FC<VitalsOverviewProps> = ({ patientUuid, pageSize, [vitals], ); - const onBeforeGetContentResolve = useRef(null); - - useEffect(() => { - if (isPrinting && onBeforeGetContentResolve.current) { - onBeforeGetContentResolve.current(); - } - }, [isPrinting]); - const handlePrint = useReactToPrint({ - content: () => contentToPrintRef.current, + contentRef: contentToPrintRef, documentTitle: `OpenMRS - ${patientDetails.name} - ${headerTitle}`, - onBeforeGetContent: () => - new Promise((resolve) => { - if (patient && patient.patient && headerTitle) { - onBeforeGetContentResolve.current = resolve; - setIsPrinting(true); - } - }), + onBeforePrint: () => { + setIsPrinting(true); + return Promise.resolve(); + }, onAfterPrint: () => { - onBeforeGetContentResolve.current = null; setIsPrinting(false); }, }); diff --git a/yarn.lock b/yarn.lock index 301eb0a3ef..af000b139b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5652,7 +5652,7 @@ __metadata: react-hook-form: "npm:^7.46.2" react-i18next: "npm:^11.18.6" react-router-dom: "npm:^6.16.0" - react-to-print: "npm:^2.14.13" + react-to-print: "npm:^3.0.0-beta-1" rxjs: "npm:^7.8.0" sass: "npm:^1.54.3" swc-loader: "npm:^0.2.3" @@ -21812,13 +21812,12 @@ __metadata: languageName: node linkType: hard -"react-to-print@npm:^2.14.13": - version: 2.14.13 - resolution: "react-to-print@npm:2.14.13" +"react-to-print@npm:^3.0.0-beta-1": + version: 3.0.0-beta-1 + resolution: "react-to-print@npm:3.0.0-beta-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/cc4e438b5b7e5c67a73ca3d87ff0f1b5a550a7e6c1a1911cded14c9591b286efd82029bd0447e016b0b404f274d546a108e760fd7867e6b73342dba450282a29 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ~19 + checksum: 10/74211e0887e257b700d1237b6285e69dc9bfd6e0afec3c0c7c0b13e2791948c63e42ccb9da5767a168de82962231f705b458c702fbe881c5b215e86ae4974b32 languageName: node linkType: hard