diff --git a/package.json b/package.json index 218909ae41..f0f897904f 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "dependencies": { "@hookform/resolvers": "^3.3.1", "classnames": "^2.3.2", + "html-to-image": "^1.11.11", "react-barcode": "^1.5.3", "react-hook-form": "^7.46.2", "react-to-print": "^2.14.13", @@ -62,7 +63,7 @@ "jest-environment-jsdom": "^29.7.0", "lint-staged": "^14.0.1", "lodash": "^4.17.21", - "openmrs": "next", + "openmrs": "^5.8.2-pre.2483", "prettier": "^3.0.3", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/packages/esm-form-entry-app/translations/es.json b/packages/esm-form-entry-app/translations/es.json index a85c88ae1c..e274b71b8a 100644 --- a/packages/esm-form-entry-app/translations/es.json +++ b/packages/esm-form-entry-app/translations/es.json @@ -29,7 +29,7 @@ "from": "Desde", "futureDateRestriction": "¡No se permite una fecha futura!", "hoursAgo": " horas atrás", - "invalidDate": "¡La fecha proporcionada es inválida!", + "invalidDate": "¡La fecha proporcionada no válida!", "loading": "Cargando...", "loadingComponent": "Cargando Componente...", "max": "El valor máximo debe ser {max}", diff --git a/packages/esm-patient-attachments-app/translations/es.json b/packages/esm-patient-attachments-app/translations/es.json index 0e1e36203b..1bb8c5db08 100644 --- a/packages/esm-patient-attachments-app/translations/es.json +++ b/packages/esm-patient-attachments-app/translations/es.json @@ -35,15 +35,15 @@ "gridView": "Vista de cuadrícula", "image": "Imagen", "imageDescription": "Descripción de la imagen", - "imagePlaceholder": "Marcador de posición de la imagen", + "imagePlaceholder": "Marcador de posición de imagen", "imagePreview": "Vista previa de la imagen", "name": "nombre", - "nameIsRequired": "El nombre es requerido", + "nameIsRequired": "El nombre es obligatorio", "noAttachmentsToDisplay": "No hay archivos adjuntos para mostrar para este paciente", "noImageToDisplay": "No hay imagen para mostrar", "options": "Opciones", "successfullyDeleted": "eliminado exitosamente", - "supportedFiletypes": "Los archivos compatibles son {{supportedFiles}}", + "supportedFiletypes": "Los archivos soportados son {{supportedFiles}}", "tableView": "Vista de tabla", "type": "Tipo", "unsupportedFileType": "Tipo de archivo no compatible", diff --git a/packages/esm-patient-banner-app/src/config-schema.ts b/packages/esm-patient-banner-app/src/config-schema.ts index f5cb1b81d5..1f89c37244 100644 --- a/packages/esm-patient-banner-app/src/config-schema.ts +++ b/packages/esm-patient-banner-app/src/config-schema.ts @@ -34,10 +34,32 @@ export const configSchema = { logo: '', }, }, - fields: { - _type: Type.Array, - _description: 'Patient demographics to include in the patient sticker printout', - _default: ['name', 'dob', 'gender', 'identifier', 'age', 'contact', 'address'], + printStickerFields: { + _type: Type.Object, + _description: 'Configuration of the patient sticker fields for the patient identifier stickers', + fields: { + _type: Type.Array, + _description: 'Patient demographics to include in the patient sticker printout', + }, + fieldSeparator: { + _type: Type.Boolean, + _description: 'Whether to display a colon symbol alongside each field label', + }, + fieldsTableGroups: { + _type: Type.Array, + _description: + 'Groups of patient demographic fields to be displayed as distinct tables in the patient sticker. Each group contains a set of related fields that will appear together in one table ie a single line.', + }, + fieldsContainerStyleOverrides: { + _type: Type.Object, + _description: 'CSS style elements override how fields appear in the field container.', + }, + _default: { + fields: ['name', 'dob', 'gender', 'identifier', 'age', 'contact', 'address'], + fieldSeparator: false, + fieldsTableGroups: [], + fieldsContainerStyleOverrides: {}, + }, }, pageSize: { _type: Type.String, @@ -45,6 +67,45 @@ export const configSchema = { '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: 'A4', }, + printMultipleStickers: { + _type: Type.Object, + _description: 'Configuration of how many stickers to print, together with the columns and rows to print per page', + numberOfStickers: { + _type: Type.Number, + _description: 'The number of patient ID stickers to print', + }, + stickerColumnsPerPage: { + _type: Type.Number, + _description: 'The number of columns of patient ID stickers to print per page', + }, + stickerRowsPerPage: { + _type: Type.Number, + _description: 'The number of rows of patient ID stickers to print per page', + }, + _default: { + enabled: false, + numberOfStickers: 1, + stickerColumnsPerPage: 1, + stickerRowsPerPage: 1, + }, + }, + stickerSize: { + _type: Type.Object, + _description: 'Configuration of the patient sticker height and width for the patient identifier stickers', + height: { + _type: Type.String, + _description: + 'Specifies the height of each patient ID sticker in the printout in units such as inches or centimetres.', + }, + width: { + _type: Type.String, + _description: 'The width of each patient ID sticker in the printout in units such as inches or centimetres.', + }, + _default: { + height: 'auto', + width: 'auto', + }, + }, identifiersToDisplay: { _type: Type.Array, _description: @@ -54,6 +115,11 @@ export const configSchema = { _type: Type.UUID, }, }, + autoPrint: { + _type: Type.Boolean, + _description: 'Whether to print the patient sticker by default', + _default: false, + }, }, useRelationshipNameLink: { _type: Type.Boolean, @@ -72,9 +138,24 @@ export interface ConfigObject { showLogo: boolean; logo: string; }; - fields: Array; + printStickerFields: { + fields: Array; + fieldSeparator: boolean; + fieldsTableGroups: Array>; + fieldsContainerStyleOverrides: Record; + }; pageSize: string; + printMultipleStickers: { + numberOfStickers: number; + stickerColumnsPerPage: number; + stickerRowsPerPage: number; + }; + stickerSize: { + height: string; + width: string; + }; identifiersToDisplay: Array; + autoPrint: boolean; }; useRelationshipNameLink: boolean; } diff --git a/packages/esm-patient-banner-app/src/print-identifier-sticker/patient-detail.component.tsx b/packages/esm-patient-banner-app/src/print-identifier-sticker/patient-detail.component.tsx index ffa4086033..6aa42376e8 100644 --- a/packages/esm-patient-banner-app/src/print-identifier-sticker/patient-detail.component.tsx +++ b/packages/esm-patient-banner-app/src/print-identifier-sticker/patient-detail.component.tsx @@ -7,45 +7,48 @@ import styles from './print-identifier-sticker.scss'; export interface PatientDetailProps { patient: fhir.Patient; + showFieldSeparator: boolean; } -export const PatientName: React.FC = ({ patient }) => { +export const PatientName: React.FC = ({ patient, showFieldSeparator }) => { const { t } = useTranslation(); return ( -
- - {t('patientNameWithSeparator', 'Patient name:')} +
+ + {showFieldSeparator ? t('patientNameWithSeparator', 'Patient name:') : t('patientName', 'Patient name')} {getPatientName(patient)}
); }; -export const PatientAge: React.FC = ({ patient }) => { +export const PatientAge: React.FC = ({ patient, showFieldSeparator }) => { const { t } = useTranslation(); return ( -
- - {t('patientAge', 'Age:')} +
+ + {showFieldSeparator ? t('patientAgeWithSeparator', 'Age:') : t('patientAge', 'Age')} {age(patient.birthDate)}
); }; -export const PatientDob: React.FC = ({ patient }) => { +export const PatientDob: React.FC = ({ patient, showFieldSeparator }) => { const { t } = useTranslation(); return ( -
- - {t('patientDateOfBirthWithSeparator', 'Date of birth:')} +
+ + {showFieldSeparator + ? t('patientDateOfBirthWithSeparator', 'Date of birth:') + : t('patientDateOfBirth', 'Date of birth')} {dayjs(patient.birthDate).format('DD-MM-YYYY')}
); }; -export const PatientGender: React.FC = ({ patient }) => { +export const PatientGender: React.FC = ({ patient, showFieldSeparator }) => { const { t } = useTranslation(); const getGender = (gender: string): string => { switch (gender) { @@ -62,16 +65,16 @@ export const PatientGender: React.FC = ({ patient }) => { } }; return ( -
- - {t('patientGenderWithSeparator', 'Gender:')} +
+ + {showFieldSeparator ? t('patientGenderWithSeparator', 'Gender:') : t('patientGender', 'Gender')} {getGender(patient.gender)}
); }; -export const PatientIdentifier: React.FC = ({ patient }) => { +export const PatientIdentifier: React.FC = ({ patient, showFieldSeparator }) => { const { printPatientSticker } = useConfig(); const { identifiersToDisplay } = printPatientSticker ?? {}; const patientIdentifiers = @@ -79,11 +82,12 @@ export const PatientIdentifier: React.FC = ({ patient }) => ? patient.identifier : patient.identifier?.filter((identifier) => identifiersToDisplay.includes(identifier.type.coding[0].code)); return ( -
+
{patientIdentifiers?.map((identifier) => ( -
- - {identifier.type.text}: +
+ + {identifier.type.text} + {showFieldSeparator ? ':' : ''} {identifier.value}
@@ -92,7 +96,7 @@ export const PatientIdentifier: React.FC = ({ patient }) => ); }; -export const PatientContact: React.FC = ({ patient }) => { +export const PatientContact: React.FC = ({ patient, showFieldSeparator }) => { const { t } = useTranslation(); if (!patient?.telecom?.length) { @@ -100,16 +104,18 @@ export const PatientContact: React.FC = ({ patient }) => { } return ( -
- - {t('telephoneNumberWithSeparator', 'Telephone number:')} +
+ + {showFieldSeparator + ? t('telephoneNumberWithSeparator', 'Telephone number:') + : t('telephoneNumber', 'Telephone number')} {patient.telecom?.[0]?.value}
); }; -export const PatientAddress: React.FC = ({ patient }) => { +export const PatientAddress: React.FC = ({ patient, showFieldSeparator }) => { const address = patient?.address?.find((a) => a.use === 'home'); const getAddressKey = (url: string) => url.split('#')[1]; @@ -121,20 +127,23 @@ export const PatientAddress: React.FC = ({ patient }) => { .map(([key, value]) => key === 'extension' ? ( address.extension?.[0]?.extension?.map((add, i) => ( -
- +
+ {getCoreTranslation( getAddressKey(add.url) as CoreTranslationKey, getAddressKey(add.url) as CoreTranslationKey, )} - : + {showFieldSeparator ? ':' : ''} {add.valueString}
)) ) : ( -
- {getCoreTranslation(key as CoreTranslationKey, key)}: +
+ + {getCoreTranslation(key as CoreTranslationKey, key)} + {showFieldSeparator ? ':' : ''} + {value}
), diff --git a/packages/esm-patient-banner-app/src/print-identifier-sticker/print-identifier-sticker-modal.test.tsx b/packages/esm-patient-banner-app/src/print-identifier-sticker/print-identifier-sticker-modal.test.tsx index c9d3d189a9..4e1a39f28e 100644 --- a/packages/esm-patient-banner-app/src/print-identifier-sticker/print-identifier-sticker-modal.test.tsx +++ b/packages/esm-patient-banner-app/src/print-identifier-sticker/print-identifier-sticker-modal.test.tsx @@ -6,7 +6,6 @@ import { useReactToPrint } from 'react-to-print'; import { age, getDefaultsFromConfigSchema, useConfig } from '@openmrs/esm-framework'; import { mockFhirPatient } from '../../../../__mocks__/patient.mock'; import { type ConfigObject, configSchema } from '../config-schema'; -import { getByTextWithMarkup } from 'tools'; import PrintIdentifierSticker from './print-identifier-sticker.modal'; const mockedCloseModal = jest.fn(); @@ -32,7 +31,7 @@ describe('PrintIdentifierStickerModal', () => { const mockHandlePrint = jest.fn(); mockedUseReactToPrint.mockReturnValue(mockHandlePrint); - renderPrintIdentifierStickerModal(); + render(); expect(screen.getByText(/print identifier sticker/i)).toBeInTheDocument(); const printButton = screen.getByRole('button', { name: /print/i }); @@ -58,13 +57,13 @@ describe('PrintIdentifierStickerModal', () => { }, }); - renderPrintIdentifierStickerModal(); + render(); - expect(screen.getByTestId('barcode')).toBeInTheDocument(); + expect(screen.getAllByTestId('barcode')[0]).toBeInTheDocument(); expect(Barcode).toHaveBeenCalledWith( { value: '100008E', - width: 2, + width: 3, background: '#f4f4f4', displayValue: true, renderer: 'img', @@ -76,7 +75,7 @@ describe('PrintIdentifierStickerModal', () => { }, {}, ); - expect(screen.getByTestId('openmrs-logo')).toBeInTheDocument(); + expect(screen.getAllByLabelText('Identifier sticker implementation logo')[0]).toBeInTheDocument(); }); it("should not render a barcode if it's disabled via config", async () => { @@ -92,7 +91,7 @@ describe('PrintIdentifierStickerModal', () => { }, }); - renderPrintIdentifierStickerModal(); + render(); expect(screen.queryByTestId('barcode')).not.toBeInTheDocument(); expect(screen.queryByTestId('openmrs-logo')).not.toBeInTheDocument(); @@ -111,21 +110,17 @@ describe('PrintIdentifierStickerModal', () => { }, }); - renderPrintIdentifierStickerModal(); + render(); - expect(screen.getByRole('img')).toHaveAttribute('src', '/openmrs/spa/logo.png'); + expect(screen.getAllByRole('img')[0]).toHaveAttribute('src', '/openmrs/spa/logo.png'); }); it("renders the patient's details in the print modal", () => { - renderPrintIdentifierStickerModal(); + render(); - expect(getByTextWithMarkup(/Joshua Johnson/i)).toBeInTheDocument(); - expect(getByTextWithMarkup(/\+255777053243/i)).toBeInTheDocument(); - expect(getByTextWithMarkup(/100008E/i)).toBeInTheDocument(); - expect(getByTextWithMarkup(age(mockFhirPatient.birthDate))).toBeInTheDocument(); + expect(screen.getAllByText(/Joshua Johnson/i)[0]).toBeInTheDocument(); + expect(screen.getAllByText(/\+255777053243/i)[0]).toBeInTheDocument(); + expect(screen.getAllByText(/100008E/i)[0]).toBeInTheDocument(); + expect(screen.getAllByText(age(mockFhirPatient.birthDate))[0]).toBeInTheDocument(); }); }); - -function renderPrintIdentifierStickerModal() { - return render(); -} diff --git a/packages/esm-patient-banner-app/src/print-identifier-sticker/print-identifier-sticker.component.tsx b/packages/esm-patient-banner-app/src/print-identifier-sticker/print-identifier-sticker.component.tsx new file mode 100644 index 0000000000..666c4919fb --- /dev/null +++ b/packages/esm-patient-banner-app/src/print-identifier-sticker/print-identifier-sticker.component.tsx @@ -0,0 +1,99 @@ +import React from 'react'; +import Barcode from 'react-barcode'; +import { useTranslation } from 'react-i18next'; +import { interpolateUrl, useConfig } from '@openmrs/esm-framework'; +import { defaultBarcodeParams, getPatientField } from './print-identifier-sticker.resource'; +import { type AllowedPatientFields, type ConfigObject } from '../config-schema'; +import styles from './print-identifier-sticker.scss'; +import { Column, Grid } from '@carbon/react'; + +interface PrintComponentProps { + patient: fhir.Patient; +} + +const PrintComponent: React.FC = ({ patient }) => { + const { printPatientSticker } = useConfig(); + const fieldsTableGroups: Array> = + printPatientSticker?.printStickerFields?.fieldsTableGroups; + const individualFields = + printPatientSticker?.printStickerFields.fields?.filter((field) => !fieldsTableGroups.flat().includes(field)) || []; + + const showFieldSeparator = printPatientSticker?.printStickerFields?.fieldSeparator; + const primaryIdentifierValue = patient?.identifier?.find((identifier) => identifier.use === 'official')?.value; + return ( +
+ + {printPatientSticker?.header?.showBarcode && ( +
+
+ +
+
+ )} + {printPatientSticker?.header?.showLogo && ( +
+
+ +
+
+ )} +
+
+ {individualFields.map((field) => { + const Component = getPatientField(field); + return ( +
+ +
+ ); + })} +
+ {fieldsTableGroups.length > 0 + ? fieldsTableGroups.map((group, index) => ( + + + + {group.map((field) => { + const Component = getPatientField(field); + return ( + + ); + })} + + +
+ +
+ )) + : null} +
+ ); +}; + +const ImplementationLogo: React.FC = () => { + const { t } = useTranslation(); + const { printPatientSticker } = useConfig(); + + return printPatientSticker?.header?.logo ? ( + {t('implementationLogo', + ) : ( + // TODO: Figure out why #omrs-logo-full-mono sprite is not working + + + + ); +}; + +export default PrintComponent; diff --git a/packages/esm-patient-banner-app/src/print-identifier-sticker/print-identifier-sticker.modal.tsx b/packages/esm-patient-banner-app/src/print-identifier-sticker/print-identifier-sticker.modal.tsx index 2d8d2efbee..e031cc61f8 100644 --- a/packages/esm-patient-banner-app/src/print-identifier-sticker/print-identifier-sticker.modal.tsx +++ b/packages/esm-patient-banner-app/src/print-identifier-sticker/print-identifier-sticker.modal.tsx @@ -1,30 +1,57 @@ -import React, { useCallback, useEffect, useRef, useState } from 'react'; -import Barcode from 'react-barcode'; +import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react'; +import classNames from 'classnames'; import { useTranslation } from 'react-i18next'; import { useReactToPrint } from 'react-to-print'; -import { Button, InlineLoading, ModalBody, ModalFooter, ModalHeader } from '@carbon/react'; -import { getPatientName, showSnackbar, useConfig, getCoreTranslation } from '@openmrs/esm-framework'; +import { + Button, + Column, + Grid, + InlineLoading, + ModalBody, + ModalFooter, + ModalHeader, + NumberInput, + Stack, + Toggle, +} from '@carbon/react'; +import { getPatientName, getCoreTranslation, showSnackbar, useConfig } from '@openmrs/esm-framework'; import { type ConfigObject } from '../config-schema'; -import { defaultBarcodeParams, getPatientField } from './print-identifier-sticker.resource'; +import PrintComponent from './print-identifier-sticker.component'; import styles from './print-identifier-sticker.scss'; +import { toSvg } from 'html-to-image'; interface PrintIdentifierStickerProps { closeModal: () => void; patient: fhir.Patient; } -interface PrintComponentProps extends Partial { +interface PrintMultipleStickersComponentProps + extends Pick { + pageSize: string; patient: fhir.Patient; + printMultipleStickers: { + numberOfStickers: number; + stickerColumnsPerPage: number; + stickerRowsPerPage: number; + }; + stickerSize: { + height: string; + width: string; + }; + stickerSVGDataSource: string; + setStickerSVGDataSource: React.Dispatch>; } const PrintIdentifierSticker: React.FC = ({ closeModal, patient }) => { const { t } = useTranslation(); const { printPatientSticker } = useConfig(); - const { pageSize } = printPatientSticker ?? {}; - const contentToPrintRef = useRef(null); - const onBeforeGetContentResolve = useRef<() => void | null>(null); + const { pageSize, autoPrint, printMultipleStickers, stickerSize } = printPatientSticker ?? {}; const [isPrinting, setIsPrinting] = useState(false); + const [stickerSVGDataSource, setStickerSVGDataSource] = useState(''); + const headerTitle = t('patientIdentifierSticker', 'Patient identifier sticker'); + const contentToPrintRef = useRef(null); + const onBeforeGetContentResolve = useRef<() => void | null>(null); useEffect(() => { if (isPrinting && onBeforeGetContentResolve.current) { @@ -49,6 +76,60 @@ const PrintIdentifierSticker: React.FC = ({ closeMo closeModal(); }, [closeModal]); + const handlePrintWindow = useCallback( + (printWindow: HTMLIFrameElement | null): Promise => { + return new Promise((resolve) => { + if (printWindow) { + const printContent = printWindow.contentDocument || printWindow.contentWindow?.document; + if (printContent) { + printContent.body.style.overflow = 'initial !important'; + + const style = printContent.createElement('style'); + style.textContent = ` + @page { + size: ${pageSize}; + margin: 15px 20px 0 20px; + } + + @media print { + #print-wrapper { + width: 100vw !important; + height: 100vh !important; + transform-origin: top left; + transform: scale(var(--print-scale)); + } + + #print-content { + position: absolute; + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + } + } + `; + printContent.head.appendChild(style); + + const content = printContent.getElementById('print-wrapper'); + const contentWidth = content.scrollWidth; + const contentHeight = content.scrollHeight; + const pageWidth = window.innerWidth; + const pageHeight = window.innerHeight; + const scaleX = pageWidth / contentWidth; + const scaleY = pageHeight / contentHeight; + const scale = Math.min(scaleX, scaleY) * 0.95; + + printContent.documentElement.style.setProperty('--print-scale', scale.toString()); + + printWindow.contentWindow?.print(); + } + } + resolve(); + }); + }, + [pageSize], + ); + const handlePrintError = useCallback((errorLocation, error) => { onBeforeGetContentResolve.current = null; @@ -70,32 +151,43 @@ const PrintIdentifierSticker: React.FC = ({ closeMo onAfterPrint: handleAfterPrint, onBeforeGetContent: handleBeforeGetContent, onPrintError: handlePrintError, + print: handlePrintWindow, copyStyles: true, }); + useEffect(() => { + if (!isPrinting && autoPrint && stickerSVGDataSource && contentToPrintRef.current) { + handlePrint(); + } + }, [autoPrint, handlePrint, isPrinting, stickerSVGDataSource]); + return ( <> - -
- - -
+ + - + + ) : null} + +
+
+ {pages.map((pageLabels, pageIndex) => ( + + ))} +
+
+ + ); + }, +); export default PrintIdentifierSticker; diff --git a/packages/esm-patient-banner-app/src/print-identifier-sticker/print-identifier-sticker.resource.ts b/packages/esm-patient-banner-app/src/print-identifier-sticker/print-identifier-sticker.resource.ts index ef32c1272e..bb6a78ef78 100644 --- a/packages/esm-patient-banner-app/src/print-identifier-sticker/print-identifier-sticker.resource.ts +++ b/packages/esm-patient-banner-app/src/print-identifier-sticker/print-identifier-sticker.resource.ts @@ -12,11 +12,11 @@ import { } from './patient-detail.component'; export const defaultBarcodeParams: Options = { - width: 2, + width: 3, format: 'CODE39', - background: '#f4f4f4', + background: '#ffffff', displayValue: true, - renderer: 'img', + renderer: 'svg', font: 'IBM Plex Sans', textAlign: 'center', textPosition: 'bottom', diff --git a/packages/esm-patient-banner-app/src/print-identifier-sticker/print-identifier-sticker.scss b/packages/esm-patient-banner-app/src/print-identifier-sticker/print-identifier-sticker.scss index 1062c6d2d0..ecf09b5b3a 100644 --- a/packages/esm-patient-banner-app/src/print-identifier-sticker/print-identifier-sticker.scss +++ b/packages/esm-patient-banner-app/src/print-identifier-sticker/print-identifier-sticker.scss @@ -1,3 +1,4 @@ +@use '@carbon/colors'; @use '@carbon/layout'; @use '@carbon/type'; @use '@openmrs/esm-styleguide/src/_vars' as *; @@ -6,12 +7,29 @@ padding: layout.$spacing-03 layout.$spacing-05; } -.documentHeader { +.svgContainer { + background-color: white; +} + +.gridContainer { + padding-left: 0; +} + +.gridHeaderContainer { display: flex; - justify-content: space-between; - align-items: center; + gap: 1rem; + padding: 0; border-bottom: 1px solid $brand-01; margin-bottom: layout.$spacing-05; + + :global(.cds--css-grid-column) { + margin-left: 0; + margin-right: 0; + } + + img { + width: 100%; + } } .row { @@ -22,13 +40,13 @@ } .gridRow { - padding: 0px; + padding: 0; display: flex; flex-direction: row; justify-content: space-between; div { - margin-left: 0px; + margin-left: 0; margin-bottom: 5px; } } @@ -58,26 +76,168 @@ } } -.strong { - @include type.type-style('heading-04'); - font-weight: 700; - margin-inline-end: layout.$spacing-03; +.patientDetailLabel { + @include type.type-style('heading-03'); +} + +.multipleStickerToggle { + :global(.cds--toggle__label-text) { + @include type.type-style('heading-03'); + font-weight: 700; + } + + :global(.cds--toggle__switch--checked) { + background-color: colors.$green-50; + } } .patientDetail { - @include type.type-style('heading-03'); + @include type.type-style('heading-06'); + font-weight: 700; + margin-inline-end: layout.$spacing-03; + padding-bottom: 0.5rem; } .implementationLogo { - width: 40%; + display: flex; + align-items: center; + height: 100%; +} + +.stickerBarcode svg { + max-width: 100%; +} + +.previewContainer { + max-height: layout.$spacing-13; + overflow: scroll; + background-color: white; + width: 100%; + border: 1px solid $ui-03; + margin-left: auto; + margin-right: auto; + padding: layout.$spacing-07; +} + +.labelNumberInput { + padding: layout.$spacing-05; +} + +.hidePreviewContainer { + display: none; +} + +.stickerBarcodeContainer { + flex: 7; } -@media print { - html, - body { - height: 100vh; - margin: 0 !important; - padding: 0 !important; - overflow: hidden; +.logoContainer { + flex: 3; +} + +.printRoot { + @media print { + @page { + size: var(--omrs-print-label-paper-size, auto); + } + + html, + body { + height: initial !important; + background-color: white; + } + + .pageBreak { + break-after: page; + } + + .stickerContainer { + width: 100%; + height: 100%; + position: absolute; + box-sizing: border-box; + overflow: hidden; + } + } + + .labelsContainer { + grid-template-columns: repeat(var(--omrs-print-label-columns, 1), 1fr); + grid-template-rows: repeat(var(--omrs-print-label-rows, 1), auto); + } + + .implementationLogo { + display: flex; + align-items: center; + height: 100%; + } + + .stickerBarcodeContainer { + flex: 7; + } + + .logoContainer { + flex: 3; } + + .stickerBarcode svg { + max-width: 100%; + } + + .gridHeaderContainer { + display: flex; + gap: 1rem; + padding: 0; + border-bottom: 1px solid $brand-01; + margin-bottom: layout.$spacing-05; + + :global(.cds--css-grid-column) { + margin-left: 0; + margin-right: 0; + } + + img { + width: 100%; + } + } +} + +.printContainer { + height: auto; + width: auto; +} + +.labelsContainer { + display: grid; + column-gap: 1.25rem; + row-gap: layout.$spacing-05; + place-items: center; + background-color: white; +} + +.multipleStickerInputs { + display: flex; + gap: layout.$spacing-05; +} + +.previewButton { + margin-top: layout.$spacing-05; +} + +.fieldsContainer { + display: grid; + grid-template-columns: 1fr 1fr; + align-items: baseline; +} + +.fieldRow { + display: contents; +} + +.fieldsTable { + table-layout: fixed; + width: 100%; +} + +.fieldsTableCell div { + display: grid; } diff --git a/packages/esm-patient-banner-app/translations/am.json b/packages/esm-patient-banner-app/translations/am.json index 61c50e2670..7cd1ecb824 100644 --- a/packages/esm-patient-banner-app/translations/am.json +++ b/packages/esm-patient-banner-app/translations/am.json @@ -7,7 +7,7 @@ "countyDistrict": "District", "district": "District", "implementationLogo": "Implementation logo", - "patientAge": "Age:", + "patientAgeWithSeparator": "Age:", "patientDateOfBirthWithSeparator": "Date of birth:", "patientGenderWithSeparator": "Gender:", "patientIdentifierSticker": "Patient identifier sticker", diff --git a/packages/esm-patient-banner-app/translations/de.json b/packages/esm-patient-banner-app/translations/de.json index 61c50e2670..7cd1ecb824 100644 --- a/packages/esm-patient-banner-app/translations/de.json +++ b/packages/esm-patient-banner-app/translations/de.json @@ -7,7 +7,7 @@ "countyDistrict": "District", "district": "District", "implementationLogo": "Implementation logo", - "patientAge": "Age:", + "patientAgeWithSeparator": "Age:", "patientDateOfBirthWithSeparator": "Date of birth:", "patientGenderWithSeparator": "Gender:", "patientIdentifierSticker": "Patient identifier sticker", diff --git a/packages/esm-patient-banner-app/translations/en.json b/packages/esm-patient-banner-app/translations/en.json index 61c50e2670..336b132ac5 100644 --- a/packages/esm-patient-banner-app/translations/en.json +++ b/packages/esm-patient-banner-app/translations/en.json @@ -3,18 +3,31 @@ "address2": "Address line 2", "city": "City", "cityVillage": "city", + "columnsPerPage": "Columns per page", "country": "Country", "countyDistrict": "District", "district": "District", + "hidePreview": "Hide preview", + "identifierStickerImplementationLogo": "Identifier sticker implementation logo", "implementationLogo": "Implementation logo", - "patientAge": "Age:", + "patientAge": "Age", + "patientAgeWithSeparator": "Age:", + "patientDateOfBirth": "Date of birth", "patientDateOfBirthWithSeparator": "Date of birth:", + "patientGender": "Gender", "patientGenderWithSeparator": "Gender:", "patientIdentifierSticker": "Patient identifier sticker", + "patientName": "Patient name", "patientNameWithSeparator": "Patient name:", "postalCode": "Postal code", "printIdentifierSticker": "Print identifier sticker", + "printIdentifierStickerModal": "Print identifier sticker modal", + "printMultipleStickers": "Print multiple stickers", + "rowsPerPage": "Rows per page", + "showPreview": "Show preview", "state": "State", "stateProvince": "State", - "telephoneNumberWithSeparator": "Telephone number:" + "telephoneNumber": "Telephone number", + "telephoneNumberWithSeparator": "Telephone number:", + "totalNumber": "Total number" } diff --git a/packages/esm-patient-banner-app/translations/fr.json b/packages/esm-patient-banner-app/translations/fr.json index 5f367042a5..ec39e4e120 100644 --- a/packages/esm-patient-banner-app/translations/fr.json +++ b/packages/esm-patient-banner-app/translations/fr.json @@ -7,7 +7,7 @@ "countyDistrict": "District", "district": "District", "implementationLogo": "Logo de mise en œuvre", - "patientAge": "Age:", + "patientAgeWithSeparator": "Age:", "patientDateOfBirthWithSeparator": "Date de Naissance:", "patientGenderWithSeparator": "Genre:", "patientIdentifierSticker": "Autocollant d'identification du patient", diff --git a/packages/esm-patient-banner-app/translations/hi.json b/packages/esm-patient-banner-app/translations/hi.json index 61c50e2670..7cd1ecb824 100644 --- a/packages/esm-patient-banner-app/translations/hi.json +++ b/packages/esm-patient-banner-app/translations/hi.json @@ -7,7 +7,7 @@ "countyDistrict": "District", "district": "District", "implementationLogo": "Implementation logo", - "patientAge": "Age:", + "patientAgeWithSeparator": "Age:", "patientDateOfBirthWithSeparator": "Date of birth:", "patientGenderWithSeparator": "Gender:", "patientIdentifierSticker": "Patient identifier sticker", diff --git a/packages/esm-patient-banner-app/translations/hi_IN.json b/packages/esm-patient-banner-app/translations/hi_IN.json index 61c50e2670..7cd1ecb824 100644 --- a/packages/esm-patient-banner-app/translations/hi_IN.json +++ b/packages/esm-patient-banner-app/translations/hi_IN.json @@ -7,7 +7,7 @@ "countyDistrict": "District", "district": "District", "implementationLogo": "Implementation logo", - "patientAge": "Age:", + "patientAgeWithSeparator": "Age:", "patientDateOfBirthWithSeparator": "Date of birth:", "patientGenderWithSeparator": "Gender:", "patientIdentifierSticker": "Patient identifier sticker", diff --git a/packages/esm-patient-banner-app/translations/id.json b/packages/esm-patient-banner-app/translations/id.json index 61c50e2670..7cd1ecb824 100644 --- a/packages/esm-patient-banner-app/translations/id.json +++ b/packages/esm-patient-banner-app/translations/id.json @@ -7,7 +7,7 @@ "countyDistrict": "District", "district": "District", "implementationLogo": "Implementation logo", - "patientAge": "Age:", + "patientAgeWithSeparator": "Age:", "patientDateOfBirthWithSeparator": "Date of birth:", "patientGenderWithSeparator": "Gender:", "patientIdentifierSticker": "Patient identifier sticker", diff --git a/packages/esm-patient-banner-app/translations/it.json b/packages/esm-patient-banner-app/translations/it.json index 61c50e2670..7cd1ecb824 100644 --- a/packages/esm-patient-banner-app/translations/it.json +++ b/packages/esm-patient-banner-app/translations/it.json @@ -7,7 +7,7 @@ "countyDistrict": "District", "district": "District", "implementationLogo": "Implementation logo", - "patientAge": "Age:", + "patientAgeWithSeparator": "Age:", "patientDateOfBirthWithSeparator": "Date of birth:", "patientGenderWithSeparator": "Gender:", "patientIdentifierSticker": "Patient identifier sticker", diff --git a/packages/esm-patient-banner-app/translations/km.json b/packages/esm-patient-banner-app/translations/km.json index 233d56abb5..3448c0489b 100644 --- a/packages/esm-patient-banner-app/translations/km.json +++ b/packages/esm-patient-banner-app/translations/km.json @@ -7,7 +7,7 @@ "countyDistrict": "ស្រុក/ខ័ណ្ឌ", "district": "ស្រុក/ខ័ណ្ឌ", "implementationLogo": "Implementation logo", - "patientAge": "Age:", + "patientAgeWithSeparator": "Age:", "patientDateOfBirthWithSeparator": "Date of birth:", "patientGenderWithSeparator": "Gender:", "patientIdentifierSticker": "Patient identifier sticker", diff --git a/packages/esm-patient-banner-app/translations/ne.json b/packages/esm-patient-banner-app/translations/ne.json index 61c50e2670..7cd1ecb824 100644 --- a/packages/esm-patient-banner-app/translations/ne.json +++ b/packages/esm-patient-banner-app/translations/ne.json @@ -7,7 +7,7 @@ "countyDistrict": "District", "district": "District", "implementationLogo": "Implementation logo", - "patientAge": "Age:", + "patientAgeWithSeparator": "Age:", "patientDateOfBirthWithSeparator": "Date of birth:", "patientGenderWithSeparator": "Gender:", "patientIdentifierSticker": "Patient identifier sticker", diff --git a/packages/esm-patient-banner-app/translations/pt.json b/packages/esm-patient-banner-app/translations/pt.json index 47d6730993..29cd18878c 100644 --- a/packages/esm-patient-banner-app/translations/pt.json +++ b/packages/esm-patient-banner-app/translations/pt.json @@ -7,7 +7,7 @@ "countyDistrict": "Distrito", "district": "Distrito", "implementationLogo": "Logotipo de implementação", - "patientAge": "Idade:", + "patientAgeWithSeparator": "Idade:", "patientDateOfBirthWithSeparator": "Data de nascimento:", "patientGenderWithSeparator": "Sexo:", "patientIdentifierSticker": "Etiqueta de Identificador do Utente", diff --git a/packages/esm-patient-banner-app/translations/qu.json b/packages/esm-patient-banner-app/translations/qu.json index 61c50e2670..7cd1ecb824 100644 --- a/packages/esm-patient-banner-app/translations/qu.json +++ b/packages/esm-patient-banner-app/translations/qu.json @@ -7,7 +7,7 @@ "countyDistrict": "District", "district": "District", "implementationLogo": "Implementation logo", - "patientAge": "Age:", + "patientAgeWithSeparator": "Age:", "patientDateOfBirthWithSeparator": "Date of birth:", "patientGenderWithSeparator": "Gender:", "patientIdentifierSticker": "Patient identifier sticker", diff --git a/packages/esm-patient-banner-app/translations/si.json b/packages/esm-patient-banner-app/translations/si.json index 61c50e2670..7cd1ecb824 100644 --- a/packages/esm-patient-banner-app/translations/si.json +++ b/packages/esm-patient-banner-app/translations/si.json @@ -7,7 +7,7 @@ "countyDistrict": "District", "district": "District", "implementationLogo": "Implementation logo", - "patientAge": "Age:", + "patientAgeWithSeparator": "Age:", "patientDateOfBirthWithSeparator": "Date of birth:", "patientGenderWithSeparator": "Gender:", "patientIdentifierSticker": "Patient identifier sticker", diff --git a/packages/esm-patient-banner-app/translations/sw.json b/packages/esm-patient-banner-app/translations/sw.json index 61c50e2670..7cd1ecb824 100644 --- a/packages/esm-patient-banner-app/translations/sw.json +++ b/packages/esm-patient-banner-app/translations/sw.json @@ -7,7 +7,7 @@ "countyDistrict": "District", "district": "District", "implementationLogo": "Implementation logo", - "patientAge": "Age:", + "patientAgeWithSeparator": "Age:", "patientDateOfBirthWithSeparator": "Date of birth:", "patientGenderWithSeparator": "Gender:", "patientIdentifierSticker": "Patient identifier sticker", diff --git a/packages/esm-patient-banner-app/translations/sw_KE.json b/packages/esm-patient-banner-app/translations/sw_KE.json index 61c50e2670..7cd1ecb824 100644 --- a/packages/esm-patient-banner-app/translations/sw_KE.json +++ b/packages/esm-patient-banner-app/translations/sw_KE.json @@ -7,7 +7,7 @@ "countyDistrict": "District", "district": "District", "implementationLogo": "Implementation logo", - "patientAge": "Age:", + "patientAgeWithSeparator": "Age:", "patientDateOfBirthWithSeparator": "Date of birth:", "patientGenderWithSeparator": "Gender:", "patientIdentifierSticker": "Patient identifier sticker", diff --git a/packages/esm-patient-banner-app/translations/tr.json b/packages/esm-patient-banner-app/translations/tr.json index 61c50e2670..7cd1ecb824 100644 --- a/packages/esm-patient-banner-app/translations/tr.json +++ b/packages/esm-patient-banner-app/translations/tr.json @@ -7,7 +7,7 @@ "countyDistrict": "District", "district": "District", "implementationLogo": "Implementation logo", - "patientAge": "Age:", + "patientAgeWithSeparator": "Age:", "patientDateOfBirthWithSeparator": "Date of birth:", "patientGenderWithSeparator": "Gender:", "patientIdentifierSticker": "Patient identifier sticker", diff --git a/packages/esm-patient-banner-app/translations/tr_TR.json b/packages/esm-patient-banner-app/translations/tr_TR.json index 61c50e2670..7cd1ecb824 100644 --- a/packages/esm-patient-banner-app/translations/tr_TR.json +++ b/packages/esm-patient-banner-app/translations/tr_TR.json @@ -7,7 +7,7 @@ "countyDistrict": "District", "district": "District", "implementationLogo": "Implementation logo", - "patientAge": "Age:", + "patientAgeWithSeparator": "Age:", "patientDateOfBirthWithSeparator": "Date of birth:", "patientGenderWithSeparator": "Gender:", "patientIdentifierSticker": "Patient identifier sticker", diff --git a/packages/esm-patient-banner-app/translations/uk.json b/packages/esm-patient-banner-app/translations/uk.json index 61c50e2670..7cd1ecb824 100644 --- a/packages/esm-patient-banner-app/translations/uk.json +++ b/packages/esm-patient-banner-app/translations/uk.json @@ -7,7 +7,7 @@ "countyDistrict": "District", "district": "District", "implementationLogo": "Implementation logo", - "patientAge": "Age:", + "patientAgeWithSeparator": "Age:", "patientDateOfBirthWithSeparator": "Date of birth:", "patientGenderWithSeparator": "Gender:", "patientIdentifierSticker": "Patient identifier sticker", diff --git a/packages/esm-patient-banner-app/translations/vi.json b/packages/esm-patient-banner-app/translations/vi.json index c1abe8095a..d476470271 100644 --- a/packages/esm-patient-banner-app/translations/vi.json +++ b/packages/esm-patient-banner-app/translations/vi.json @@ -7,7 +7,7 @@ "countyDistrict": "Huyện", "district": "Huyện", "implementationLogo": "Logo triển khai", - "patientAge": "Tuổi\"", + "patientAgeWithSeparator": "Tuổi\"", "patientDateOfBirthWithSeparator": "Ngày sinh:", "patientGenderWithSeparator": "Giới tính:", "patientIdentifierSticker": "Nhãn dán nhận dạng bệnh nhân", diff --git a/packages/esm-patient-banner-app/translations/zh.json b/packages/esm-patient-banner-app/translations/zh.json index 2a7db4c417..7d29be7076 100644 --- a/packages/esm-patient-banner-app/translations/zh.json +++ b/packages/esm-patient-banner-app/translations/zh.json @@ -7,7 +7,7 @@ "countyDistrict": "区县", "district": "区县", "implementationLogo": "Implementation logo", - "patientAge": "Age:", + "patientAgeWithSeparator": "Age:", "patientDateOfBirthWithSeparator": "Date of birth:", "patientGenderWithSeparator": "Gender:", "patientIdentifierSticker": "患者标识符贴纸", diff --git a/packages/esm-patient-banner-app/translations/zh_CN.json b/packages/esm-patient-banner-app/translations/zh_CN.json index bd02eec04c..baaacae736 100644 --- a/packages/esm-patient-banner-app/translations/zh_CN.json +++ b/packages/esm-patient-banner-app/translations/zh_CN.json @@ -7,7 +7,7 @@ "countyDistrict": "区县", "district": "区县", "implementationLogo": "Implementation logo", - "patientAge": "Age:", + "patientAgeWithSeparator": "Age:", "patientDateOfBirthWithSeparator": "Date of birth:", "patientGenderWithSeparator": "Gender:", "patientIdentifierSticker": "Patient identifier sticker", diff --git a/packages/esm-patient-tests-app/translations/es.json b/packages/esm-patient-tests-app/translations/es.json index 91e89c3f82..70e85bc671 100644 --- a/packages/esm-patient-tests-app/translations/es.json +++ b/packages/esm-patient-tests-app/translations/es.json @@ -1,10 +1,10 @@ { "add": "Agregar", "additionalInstructions": "Instrucciones adicionales", - "addLabOrderWorkspaceTitle": "Agregar orden de laboratorio", + "addLabOrderWorkspaceTitle": "Añadir orden de laboratorio", "back": "Atrás", - "backToOrderBasket": "Regresar a la canasta de órdenes", - "backToTimeline": "Regresar al calendario", + "backToOrderBasket": "Volver a la cesta de órdenes", + "backToTimeline": "Volver al calendario", "cancel": "Cancelar", "checkFilters": "Revisar los filtros anteriores", "clear": "Borrar", @@ -66,7 +66,7 @@ "results": "Resultados", "Results Viewer": "Visualizador de Resultados", "resultsText": "resultados", - "returnToOrderBasket": "Regresar a la canasta de órdenes", + "returnToOrderBasket": "Regresar a la cesta de órdenes", "returnToTimeline": "Volver al calendario", "saveOrder": "Guardar orden", "search": "Buscar", diff --git a/yarn.lock b/yarn.lock index e4d114d2b9..dced989f0a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5750,7 +5750,7 @@ __metadata: ngx-bootstrap: "npm:^12.0.0" ngx-build-plus: "npm:^17.0.0" ngx-webcam: "npm:^0.4.1" - openmrs: "npm:next" + openmrs: "npm:^5.8.2-pre.2483" protractor: "npm:~7.0.0" reflect-metadata: "npm:^0.1.13" rxjs: "npm:^7.8.0" @@ -5986,6 +5986,7 @@ __metadata: eslint-plugin-playwright: "npm:^0.16.0" eslint-plugin-react-hooks: "npm:^4.6.0" eslint-plugin-testing-library: "npm:^6.2.2" + html-to-image: "npm:^1.11.11" husky: "npm:^8.0.3" i18next: "npm:^21.10.0" i18next-parser: "npm:^6.6.0" @@ -5995,7 +5996,7 @@ __metadata: jest-environment-jsdom: "npm:^29.7.0" lint-staged: "npm:^14.0.1" lodash: "npm:^4.17.21" - openmrs: "npm:next" + openmrs: "npm:^5.8.2-pre.2483" prettier: "npm:^3.0.3" react: "npm:^18.3.1" react-barcode: "npm:^1.5.3"