Skip to content

Commit

Permalink
(feat) Add configurable ability to print multiple stickers on the sam…
Browse files Browse the repository at this point in the history
…e page
  • Loading branch information
jnsereko authored and denniskigen committed Sep 3, 2024
1 parent 5e7a458 commit 462a6cd
Show file tree
Hide file tree
Showing 14 changed files with 380 additions and 236 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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();
});
});
Original file line number Diff line number Diff line change
@@ -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;
Loading

0 comments on commit 462a6cd

Please sign in to comment.