diff --git a/__mocks__/patient.mock.ts b/__mocks__/patient.mock.ts new file mode 100644 index 0000000000..14535779f3 --- /dev/null +++ b/__mocks__/patient.mock.ts @@ -0,0 +1,323 @@ +export const mockFhirPatient: fhir.Patient = { + resourceType: 'Patient', + id: 'bfa09dac-ec9e-47c1-9ad3-e3ebdd5d722d', + meta: { + versionId: '1719312976000', + lastUpdated: '2024-06-25T10:56:16.000+00:00', + }, + text: { + status: 'generated', + div: '
Id:bfa09dac-ec9e-47c1-9ad3-e3ebdd5d722d
Identifier:
100008E
Active:true
Name: Joshua JOHNSON
Telecom: +255777053243
Gender:MALE
Birth Date:25/09/2019
Deceased:false
Address:Wakiso Kayunga Uganda
', + }, + identifier: [ + { + id: 'fc6b122a-05bd-4128-8577-7efd8c87cda5', + extension: [ + { + url: 'http://fhir.openmrs.org/ext/patient/identifier#location', + valueReference: { + reference: 'Location/736b08f9-94d6-4b50-ad58-6bc69b9cbfb8', + // type: 'Location', + display: 'Ward 50', + }, + }, + ], + use: 'official', + type: { + coding: [ + { + code: '05a29f94-c0ed-11e2-94be-8c13b969e334', + }, + ], + text: 'OpenMRS ID', + }, + value: '100008E', + }, + ], + active: true, + name: [ + { + id: '67ac67de-aac4-43b3-a0d4-677578a01047', + text: 'Joshua Johnson', + family: 'Johnson', + given: ['Joshua'], + }, + ], + telecom: [ + { + id: 'f3f3c756-d8f1-42ce-8d32-eefe2a86c306', + value: '+255777053243', + }, + ], + gender: 'male', + birthDate: '2019-09-25', + deceasedBoolean: false, + address: [ + { + id: '1e9df4ab-0c73-4f99-b0bd-c2ddc239619b', + extension: [ + { + url: 'http://fhir.openmrs.org/ext/address', + extension: [ + { + url: 'http://fhir.openmrs.org/ext/address#address1', + valueString: 'Nansana', + }, + ], + }, + ], + use: 'home', + city: 'Wakiso', + state: 'Kayunga', + postalCode: '00000', + country: 'Uganda', + }, + { + id: '93167c61-81da-48e1-8a6d-91640a55ed73', + extension: [ + { + url: 'http://fhir.openmrs.org/ext/address', + extension: [ + { + url: 'http://fhir.openmrs.org/ext/address#address1', + valueString: 'Address16442', + }, + ], + }, + ], + use: 'old', + city: 'City6442', + state: 'State6442', + postalCode: '20839', + country: 'Country6442', + }, + { + id: '7befd893-a7ed-4080-986a-db5f5d38fda4', + extension: [ + { + url: 'http://fhir.openmrs.org/ext/address', + extension: [ + { + url: 'http://fhir.openmrs.org/ext/address#address1', + valueString: 'Address16442', + }, + ], + }, + ], + use: 'old', + city: 'City6442', + state: 'State6442', + postalCode: '20839', + country: 'Country6442', + }, + { + id: 'c0a87353-ab2c-4c39-ae7b-13e992206916', + extension: [ + { + url: 'http://fhir.openmrs.org/ext/address', + extension: [ + { + url: 'http://fhir.openmrs.org/ext/address#address1', + valueString: 'Address16442', + }, + ], + }, + ], + use: 'old', + city: 'City6442', + state: 'State6442', + postalCode: '20839', + country: 'Country6442', + }, + { + id: 'b1c92fab-8002-48ed-a1e1-72ab942d12da', + extension: [ + { + url: 'http://fhir.openmrs.org/ext/address', + extension: [ + { + url: 'http://fhir.openmrs.org/ext/address#address1', + valueString: 'Address16442', + }, + ], + }, + ], + use: 'old', + city: 'City6442', + state: 'State6442', + postalCode: '20839', + country: 'Country6442', + }, + { + id: 'c85af135-28a4-483d-864d-d97751c21ebd', + extension: [ + { + url: 'http://fhir.openmrs.org/ext/address', + extension: [ + { + url: 'http://fhir.openmrs.org/ext/address#address1', + valueString: 'Address16442', + }, + ], + }, + ], + use: 'old', + city: 'City6442', + state: 'State6442', + postalCode: '20839', + country: 'Country6442', + }, + { + id: 'a6a15f40-5cc8-47c0-89ee-8cc39d2c73f5', + extension: [ + { + url: 'http://fhir.openmrs.org/ext/address', + extension: [ + { + url: 'http://fhir.openmrs.org/ext/address#address1', + valueString: 'Address16442', + }, + ], + }, + ], + use: 'old', + city: 'City6442', + state: 'State6442', + postalCode: '20839', + country: 'Country6442', + }, + { + id: '72f199fa-f569-42fb-8f5a-bc1c7bed7bb8', + extension: [ + { + url: 'http://fhir.openmrs.org/ext/address', + extension: [ + { + url: 'http://fhir.openmrs.org/ext/address#address1', + valueString: 'Address16442', + }, + ], + }, + ], + use: 'old', + city: 'City6442', + state: 'State6442', + postalCode: '20839', + country: 'Country6442', + }, + { + id: '4e0251d4-c00e-4167-ad3e-077c485aa7ec', + extension: [ + { + url: 'http://fhir.openmrs.org/ext/address', + extension: [ + { + url: 'http://fhir.openmrs.org/ext/address#address1', + valueString: 'Address16442', + }, + ], + }, + ], + use: 'old', + city: 'City6442', + state: 'State6442', + postalCode: '20839', + country: 'Country6442', + }, + { + id: '1b932ad6-d50a-4c57-a3cf-0fee5b3226fa', + extension: [ + { + url: 'http://fhir.openmrs.org/ext/address', + extension: [ + { + url: 'http://fhir.openmrs.org/ext/address#address1', + valueString: 'Address16442', + }, + ], + }, + ], + use: 'old', + city: 'City6442', + state: 'State6442', + postalCode: '20839', + country: 'Country6442', + }, + { + id: '90c59f5a-2cb4-405f-8c2b-0b5d2746d5ae', + extension: [ + { + url: 'http://fhir.openmrs.org/ext/address', + extension: [ + { + url: 'http://fhir.openmrs.org/ext/address#address1', + valueString: 'Address16442', + }, + ], + }, + ], + use: 'old', + city: 'City6442', + state: 'State6442', + postalCode: '20839', + country: 'Country6442', + }, + { + id: '810f3756-cb30-4fd8-a729-f6c1a5847312', + extension: [ + { + url: 'http://fhir.openmrs.org/ext/address', + extension: [ + { + url: 'http://fhir.openmrs.org/ext/address#address1', + valueString: 'Nansana', + }, + ], + }, + ], + use: 'old', + city: 'Wakiso', + state: 'Kayunga', + postalCode: '00000', + country: 'Uganda', + }, + { + id: '72d21a40-c872-4b99-ac2a-f7b179c4aff2', + extension: [ + { + url: 'http://fhir.openmrs.org/ext/address', + extension: [ + { + url: 'http://fhir.openmrs.org/ext/address#address1', + valueString: 'Nansana', + }, + ], + }, + ], + use: 'old', + city: 'Wakiso', + state: 'Kayunga', + postalCode: '00000', + country: 'Uganda', + }, + { + id: '6f7781b2-4b4d-4c69-bde0-98127712aa76', + extension: [ + { + url: 'http://fhir.openmrs.org/ext/address', + extension: [ + { + url: 'http://fhir.openmrs.org/ext/address#address1', + valueString: 'Nansana', + }, + ], + }, + ], + use: 'old', + city: 'Wakiso', + state: 'Kayunga', + postalCode: '00000', + country: 'Uganda', + }, + ], +}; diff --git a/package.json b/package.json index 6d9ba91dd9..34b20bffe0 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "dependencies": { "@hookform/resolvers": "^3.3.1", "classnames": "^2.3.2", + "react-barcode": "^1.5.3", "react-hook-form": "^7.46.2", "react-to-print": "^2.14.13", "zod": "^3.22.2" diff --git a/packages/esm-form-entry-app/package.json b/packages/esm-form-entry-app/package.json index 1ce81394ed..6712e130cf 100644 --- a/packages/esm-form-entry-app/package.json +++ b/packages/esm-form-entry-app/package.json @@ -94,7 +94,7 @@ "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "^2.1.0", "ngx-build-plus": "^17.0.0", - "openmrs": "next", + "openmrs": "^5.8.2-pre.2324", "protractor": "~7.0.0", "rxjs": "^7.8.0", "style-loader": "2.x", 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 deleted file mode 100644 index 9092801ae8..0000000000 --- a/packages/esm-patient-banner-app/src/banner-tags/print-identifier-sticker-modal.test.tsx +++ /dev/null @@ -1,64 +0,0 @@ -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'; - -const mockCloseModal = jest.fn(); -const mockUseReactToPrint = jest.mocked(useReactToPrint); -const mockUseConfig = jest.mocked(useConfig); - -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(); - - 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 () => { - const user = userEvent.setup(); - - render(); - - 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(); - - const printButton = screen.getByRole('button', { name: /Print/i }); - expect(printButton).toBeInTheDocument(); - - await user.click(printButton); - expect(handlePrint).toHaveBeenCalled(); - }); -}); 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 deleted file mode 100644 index 47dac29857..0000000000 --- a/packages/esm-patient-banner-app/src/banner-tags/print-identifier-sticker.modal.tsx +++ /dev/null @@ -1,184 +0,0 @@ -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 { type ConfigObject } from '../config-schema'; -import styles from './print-identifier-sticker.scss'; - -interface PrintIdentifierStickerProps { - closeModal: () => void; - patient: fhir.Patient; -} - -interface PrintComponentProps extends Partial { - 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 = ({ closeModal, patient }) => { - const { t } = useTranslation(); - const { printIdentifierStickerFields, printIdentifierStickerSize, excludePatientIdentifierCodeTypes } = - useConfig(); - const contentToPrintRef = useRef(null); - const onBeforeGetContentResolve = useRef<() => void | null>(null); - const [isPrinting, setIsPrinting] = useState(false); - const headerTitle = t('patientIdentifierSticker', 'Patient identifier sticker'); - - 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 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((resolve) => { - if (patient && headerTitle) { - onBeforeGetContentResolve.current = resolve; - setIsPrinting(true); - } - }), - [headerTitle, patient], - ); - - const handleAfterPrint = useCallback(() => { - onBeforeGetContentResolve.current = null; - setIsPrinting(false); - closeModal(); - }, [closeModal]); - - const handlePrintError = useCallback((errorLocation, error) => { - onBeforeGetContentResolve.current = null; - - showSnackbar({ - isLowContrast: false, - kind: 'error', - title: getCoreTranslation('printError', 'Print error'), - subtitle: - getCoreTranslation('printErrorExplainer', 'An error occurred in "{{errorLocation}}": ', { errorLocation }) + - error, - }); - - setIsPrinting(false); - }, []); - - const handlePrint = useReactToPrint({ - content: () => contentToPrintRef.current, - documentTitle: `${patientDetails.name} - ${headerTitle}`, - onAfterPrint: handleAfterPrint, - onBeforeGetContent: handleBeforeGetContent, - onPrintError: handlePrintError, - }); - - return ( - <> - - -
- - -
-
- - - - - - ); -}; - -const PrintComponent = ({ patientDetails, printIdentifierStickerFields, t }: PrintComponentProps) => { - return ( -
- {printIdentifierStickerFields.includes('name') &&
{patientDetails.name}
} -
- {patientDetails.identifiers.map((identifier) => { - return ( -

- {identifier?.type?.text}: {identifier?.value} -

- ); - })} -

- {getCoreTranslation('sex', 'Sex')}: {patientDetails.gender} -

-

- {t('dob', 'DOB')}: {patientDetails.dateOfBirth} -

-

- {getCoreTranslation('age', 'Age')}: {patientDetails.age} -

-
-
- ); -}; - -export default PrintIdentifierSticker; diff --git a/packages/esm-patient-banner-app/src/config-schema.ts b/packages/esm-patient-banner-app/src/config-schema.ts index 93c5d5e005..c2c2824518 100644 --- a/packages/esm-patient-banner-app/src/config-schema.ts +++ b/packages/esm-patient-banner-app/src/config-schema.ts @@ -12,42 +12,76 @@ export const configSchema = { _type: Type.UUID, }, }, - excludePatientIdentifierCodeTypes: { - uuids: { + printPatientSticker: { + enabled: { + _type: Type.Boolean, + _description: 'Whether to enable the print patient sticker feature', + _default: true, + }, + header: { + _type: Type.Object, + _description: + 'Configuration properties for the patient sticker headerConfiguration properties for the patient sticker header', + showBarcode: { + _type: Type.Boolean, + _description: 'Whether to display a barcode on the patient sticker', + }, + showLogo: { + _type: Type.Boolean, + _description: 'Whether to display a logo on the patient sticker', + }, + logo: { + _type: Type.String, + _description: 'The URL of the logo to display in the patient sticker', + }, + _default: { + showBarcode: true, + showLogo: true, + logo: '', + }, + }, + fields: { + _type: Type.Array, + _description: 'Patient demographics to include in the patient sticker printout', + _default: ['name', 'dob', 'gender', 'identifier', 'age', 'contact', 'address'], + }, + pageSize: { + _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: 'A4', + }, + identifiersToDisplay: { _type: Type.Array, - _description: 'List of UUIDs of patient identifier types to exclude from rendering in the patient banner', + _description: + 'List of UUIDs of patient identifier types to exclude from rendering in the patient banner. If empty, all identifiers will be displayed.', _default: [], _elements: { _type: Type.UUID, }, }, }, - printIdentifierStickerFields: { - _type: Type.Array, - _description: 'Patient demographics to include in the identifier sticker printout', - _default: ['age', 'dateOfBirth', 'gender', 'identifier', 'name'], - _elements: { - _type: Type.String, - }, - }, - printIdentifierStickerSize: { - _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', - }, useRelationshipNameLink: { _type: Type.Boolean, _description: "Whether to use the relationship name as a link to the associated person's patient chart.", _default: false, }, }; + +export type AllowedPatientFields = 'name' | 'dob' | 'gender' | 'identifier' | 'age' | 'contact' | 'address'; + export interface ConfigObject { contactAttributeTypes: Array; - excludePatientIdentifierCodeTypes: { - uuids: Array; + printPatientSticker: { + enabled: boolean; + header: { + showBarcode: boolean; + showLogo: boolean; + logo: string; + }; + fields: Array; + pageSize: string; + identifiersToDisplay: Array; }; - printIdentifierStickerFields: Array; - printIdentifierStickerSize: string; useRelationshipNameLink: boolean; } diff --git a/packages/esm-patient-banner-app/src/index.ts b/packages/esm-patient-banner-app/src/index.ts index 8a538bfbd1..9da2558d6d 100644 --- a/packages/esm-patient-banner-app/src/index.ts +++ b/packages/esm-patient-banner-app/src/index.ts @@ -35,10 +35,18 @@ export const deceasedPatientTag = getSyncLifecycle(deceasedPatientTagComponent, export const patientBanner = getSyncLifecycle(patientBannerComponent, options); export const printIdentifierStickerModal = getAsyncLifecycle( - () => import('./banner-tags/print-identifier-sticker.modal'), + () => import('./print-identifier-sticker/print-identifier-sticker.modal'), options, ); +export const printIdentifierStickerActionButton = getAsyncLifecycle( + () => import('./print-identifier-sticker/print-identifier-sticker-action-button.component'), + { + featureName: 'patient-actions-slot-print-identifier-sticker-button', + moduleName, + }, +); + /* The translations for built-in address fields are kept here in patient-banner. This comment ensures that they are included in the translations files. diff --git a/packages/esm-patient-banner-app/src/print-identifier-sticker/action-button.scss b/packages/esm-patient-banner-app/src/print-identifier-sticker/action-button.scss new file mode 100644 index 0000000000..2a7fdf7d6c --- /dev/null +++ b/packages/esm-patient-banner-app/src/print-identifier-sticker/action-button.scss @@ -0,0 +1,3 @@ +.menuitem { + max-width: none; +} 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 new file mode 100644 index 0000000000..48193d1129 --- /dev/null +++ b/packages/esm-patient-banner-app/src/print-identifier-sticker/patient-detail.component.tsx @@ -0,0 +1,147 @@ +import React from 'react'; +import { age, type CoreTranslationKey, getCoreTranslation, getPatientName, useConfig } from '@openmrs/esm-framework'; +import dayjs from 'dayjs'; +import { type ConfigObject } from '../config-schema'; +import { useTranslation } from 'react-i18next'; +import styles from './print-identifier-sticker.scss'; + +export interface PatientDetailProps { + patient: fhir.Patient; +} + +export const PatientName: React.FC = ({ patient }) => { + const { t } = useTranslation(); + return ( +
+ + {t('patientNameWithSeparator', 'Patient name:')} + + {getPatientName(patient)} +
+ ); +}; + +export const PatientAge: React.FC = ({ patient }) => { + const { t } = useTranslation(); + return ( +
+ + {t('patientAge', 'Age:')} + + {age(patient.birthDate)} +
+ ); +}; + +export const PatientDob: React.FC = ({ patient }) => { + const { t } = useTranslation(); + return ( +
+ + {t('patientDateOfBirthWithSeparator', 'Date of birth:')} + + {dayjs(patient.birthDate).format('DD-MM-YYYY')} +
+ ); +}; + +export const PatientGender: React.FC = ({ patient }) => { + const { t } = useTranslation(); + 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; + } + }; + return ( +
+ + {t('patientGenderWithSeparator', 'Gender:')} + + {getGender(patient.gender)} +
+ ); +}; + +export const PatientIdentifier: React.FC = ({ patient }) => { + const { printPatientSticker } = useConfig(); + const { identifiersToDisplay } = printPatientSticker ?? {}; + const patientIdentifiers = + (identifiersToDisplay ?? []).length === 0 + ? patient.identifier + : patient.identifier?.filter((identifier) => identifiersToDisplay.includes(identifier.type.coding[0].code)); + return ( +
+ {patientIdentifiers?.map((identifier) => ( +
+ + {identifier.type.text}: + + {identifier.value} +
+ ))} +
+ ); +}; + +export const PatientContact: React.FC = ({ patient }) => { + const { t } = useTranslation(); + + if (!patient?.telecom?.length) { + return null; + } + + return ( +
+ + {t('telephoneNumberWithSeparator', 'Telephone number:')} + + {patient.telecom?.[0]?.value} +
+ ); +}; + +export const PatientAddress: React.FC = ({ patient }) => { + const address = patient?.address?.find((a) => a.use === 'home'); + const getAddressKey = (url: string) => url.split('#')[1]; + + return ( + <> + {address ? ( + Object.entries(address) + .filter(([key]) => key !== 'id' && key !== 'use') + .map(([key, value]) => + key === 'extension' ? ( + address.extension?.[0]?.extension?.map((add, i) => ( +
+ + {getCoreTranslation( + getAddressKey(add.url) as CoreTranslationKey, + getAddressKey(add.url) as CoreTranslationKey, + )} + : + + {add.valueString} +
+ )) + ) : ( +
+ {getCoreTranslation(key as CoreTranslationKey, key)}: + {value} +
+ ), + ) + ) : ( +
  • --
  • + )} + + ); +}; diff --git a/packages/esm-patient-chart-app/src/actions-buttons/print-identifier-sticker.component.tsx b/packages/esm-patient-banner-app/src/print-identifier-sticker/print-identifier-sticker-action-button.component.tsx similarity index 67% rename from packages/esm-patient-chart-app/src/actions-buttons/print-identifier-sticker.component.tsx rename to packages/esm-patient-banner-app/src/print-identifier-sticker/print-identifier-sticker-action-button.component.tsx index 8ccaaf6f06..0e68234203 100644 --- a/packages/esm-patient-chart-app/src/actions-buttons/print-identifier-sticker.component.tsx +++ b/packages/esm-patient-banner-app/src/print-identifier-sticker/print-identifier-sticker-action-button.component.tsx @@ -1,16 +1,20 @@ import React, { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { OverflowMenuItem } from '@carbon/react'; -import { showModal, useFeatureFlag } from '@openmrs/esm-framework'; +import { showModal, useConfig, useFeatureFlag, usePatient } from '@openmrs/esm-framework'; import styles from './action-button.scss'; +import type { ConfigObject } from '../config-schema'; interface PrintIdentifierStickerOverflowMenuItemProps { - patient: fhir.Patient; + patientUuid: string; } -const PrintIdentifierStickerOverflowMenuItem: React.FC = ({ patient }) => { +const PrintIdentifierStickerOverflowMenuItem: React.FC = ({ + patientUuid, +}) => { const { t } = useTranslation(); - const canPrintPatientIdentifierSticker = useFeatureFlag('print-patient-identifier-sticker'); + const { printPatientSticker } = useConfig(); + const { patient } = usePatient(patientUuid); const handleLaunchModal = useCallback(() => { const dispose = showModal('print-identifier-sticker-modal', { @@ -19,7 +23,7 @@ const PrintIdentifierStickerOverflowMenuItem: React.FC) + .mockResolvedValue(getDefaultsFromConfigSchema(configSchema)); +jest.mock('react-to-print', () => ({ + ...jest.requireActual('react-to-print'), + useReactToPrint: jest.fn(), +})); +const mockedUseReactToPrint = jest.mocked(useReactToPrint); + +jest.mock('react-barcode', () => jest.fn().mockReturnValue(
    )); + +describe('Testing PrintIdentifierStickerModal', () => { + beforeEach(() => { + jest.clearAllMocks(); + mockedUseConfig.mockReturnValue(getDefaultsFromConfigSchema(configSchema)); + }); + it('should render PrintIdentifierStickerModal', async () => { + const user = userEvent.setup(); + const mockHandlePrint = jest.fn(); + mockedUseReactToPrint.mockReturnValue(mockHandlePrint); + renderPrintIdentifierStickerModal(); + expect(screen.getByText(/print identifier sticker/i)).toBeInTheDocument(); + const printButton = screen.getByRole('button', { name: /print/i }); + const cancelButton = screen.getByRole('button', { name: /cancel/i }); + await user.click(cancelButton); + expect(mockedCloseModal).toHaveBeenCalled(); + await user.click(printButton); + expect(mockHandlePrint).toHaveBeenCalled(); + }); + it('should render barcode if enabled via config', async () => { + const defaultConfig = getDefaultsFromConfigSchema(configSchema) as ConfigObject; + mockedUseConfig.mockReturnValue({ + ...defaultConfig, + printPatientSticker: { + ...defaultConfig.printPatientSticker, + header: { + showBarcode: true, + showLogo: true, + logo: '', + }, + }, + }); + renderPrintIdentifierStickerModal(); + expect(screen.getByTestId('barcode')).toBeInTheDocument(); + expect(Barcode).toHaveBeenCalledWith( + { + value: '100008E', + width: 2, + background: '#f4f4f4', + displayValue: true, + renderer: 'img', + font: 'IBM Plex Sans', + textAlign: 'center', + textPosition: 'bottom', + fontSize: 16, + }, + {}, + ); + expect(screen.getByTestId('openmrs-logo')).toBeInTheDocument(); + }); + it("should not render barcode if it's disabled via config", async () => { + const defaultConfig = getDefaultsFromConfigSchema(configSchema) as ConfigObject; + mockedUseConfig.mockReturnValue({ + ...defaultConfig, + printPatientSticker: { + ...defaultConfig.printPatientSticker, + header: { + showBarcode: false, + showLogo: false, + logo: '', + }, + }, + }); + renderPrintIdentifierStickerModal(); + expect(screen.queryByTestId('barcode')).not.toBeInTheDocument(); + expect(screen.queryByTestId('openmrs-logo')).not.toBeInTheDocument(); + }); + it('should render implementation logo if passed via config', () => { + const defaultConfig = getDefaultsFromConfigSchema(configSchema) as ConfigObject; + mockedUseConfig.mockReturnValue({ + ...defaultConfig, + printPatientSticker: { + ...defaultConfig.printPatientSticker, + header: { + showBarcode: true, + showLogo: true, + logo: '/openmrs/spa/logo.png', + }, + }, + }); + renderPrintIdentifierStickerModal(); + expect(screen.getByRole('img')).toHaveAttribute('src', '/openmrs/spa/logo.png'); + }); + + it('should render patient details', () => { + renderPrintIdentifierStickerModal(); + expect(getByTextWithMarkup(/Joshua Johnson/i)).toBeInTheDocument(); + expect(getByTextWithMarkup(/\+255777053243/i)).toBeInTheDocument(); + expect(getByTextWithMarkup(/100008E/i)).toBeInTheDocument(); + expect(getByTextWithMarkup(age(mockFhirPatient.birthDate))).toBeInTheDocument(); + }); +}); + +function renderPrintIdentifierStickerModal() { + return render(); +} 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 new file mode 100644 index 0000000000..317b167cc1 --- /dev/null +++ b/packages/esm-patient-banner-app/src/print-identifier-sticker/print-identifier-sticker.modal.tsx @@ -0,0 +1,151 @@ +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, usePatient } from '@openmrs/esm-framework'; +import { type ConfigObject } from '../config-schema'; +import styles from './print-identifier-sticker.scss'; +import Barcode from 'react-barcode'; +import { defaultBarcodeParams, getPatientField } from './print-identifier-sticker.resource'; + +interface PrintIdentifierStickerProps { + closeModal: () => void; + patient: fhir.Patient; +} + +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 [isPrinting, setIsPrinting] = useState(false); + const headerTitle = t('patientIdentifierSticker', 'Patient identifier sticker'); + + useEffect(() => { + if (isPrinting && onBeforeGetContentResolve.current) { + onBeforeGetContentResolve.current(); + } + }, [isPrinting]); + + const handleBeforeGetContent = useCallback( + () => + new Promise((resolve) => { + if (patient && headerTitle) { + onBeforeGetContentResolve.current = resolve; + setIsPrinting(true); + } + }), + [headerTitle, patient], + ); + + const handleAfterPrint = useCallback(() => { + onBeforeGetContentResolve.current = null; + setIsPrinting(false); + closeModal(); + }, [closeModal]); + + const handlePrintError = useCallback((errorLocation, error) => { + onBeforeGetContentResolve.current = null; + + showSnackbar({ + isLowContrast: false, + kind: 'error', + title: getCoreTranslation('printError', 'Print error'), + subtitle: + getCoreTranslation('printErrorExplainer', 'An error occurred in "{{errorLocation}}": ', { errorLocation }) + + error, + }); + + setIsPrinting(false); + }, []); + + const handlePrint = useReactToPrint({ + content: () => contentToPrintRef.current, + documentTitle: `${getPatientName(patient)} - ${headerTitle}`, + onAfterPrint: handleAfterPrint, + onBeforeGetContent: handleBeforeGetContent, + onPrintError: handlePrintError, + copyStyles: true, + }); + + return ( + <> + + +
    + + +
    +
    + + + + + + ); +}; + +interface PrintComponentProps extends Partial { + patient: fhir.Patient; +} + +const PrintComponent = ({ patient }: PrintComponentProps) => { + const { t } = useTranslation(); + const { printPatientSticker } = useConfig(); + const primaryIdentifierValue = patient?.identifier?.find((identifier) => identifier.use === 'official')?.value; + return ( +
    +
    + {printPatientSticker?.header?.showBarcode && ( + + )} + {printPatientSticker?.header?.showLogo && ( +
    + +
    + )} +
    + {printPatientSticker.fields.map((field) => { + const Component = getPatientField(field); + return ; + })} +
    + ); +}; + +const ImplementationLogo: React.FC = () => { + const { t } = useTranslation(); + const { printPatientSticker } = useConfig(); + + return printPatientSticker?.header?.logo ? ( + implementation-logo + ) : ( + + + + ); +}; + +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 new file mode 100644 index 0000000000..ef32c1272e --- /dev/null +++ b/packages/esm-patient-banner-app/src/print-identifier-sticker/print-identifier-sticker.resource.ts @@ -0,0 +1,46 @@ +import { type Options } from 'react-barcode'; +import { type AllowedPatientFields } from '../config-schema'; +import { + PatientAddress, + PatientAge, + PatientContact, + type PatientDetailProps, + PatientDob, + PatientGender, + PatientIdentifier, + PatientName, +} from './patient-detail.component'; + +export const defaultBarcodeParams: Options = { + width: 2, + format: 'CODE39', + background: '#f4f4f4', + displayValue: true, + renderer: 'img', + font: 'IBM Plex Sans', + textAlign: 'center', + textPosition: 'bottom', + fontSize: 16, +}; + +export function getPatientField(field: AllowedPatientFields): React.FC { + switch (field) { + case 'name': + return PatientName; + case 'age': + return PatientAge; + case 'dob': + return PatientDob; + case 'gender': + return PatientGender; + case 'identifier': + return PatientIdentifier; + case 'contact': + return PatientContact; + case 'address': + return PatientAddress; + default: + console.error(`Invalid patient field: ${field}`); + return null; + } +} diff --git a/packages/esm-patient-banner-app/src/banner-tags/print-identifier-sticker.scss b/packages/esm-patient-banner-app/src/print-identifier-sticker/print-identifier-sticker.scss similarity index 68% rename from packages/esm-patient-banner-app/src/banner-tags/print-identifier-sticker.scss rename to packages/esm-patient-banner-app/src/print-identifier-sticker/print-identifier-sticker.scss index 08e0962fdf..21862e5916 100644 --- a/packages/esm-patient-banner-app/src/banner-tags/print-identifier-sticker.scss +++ b/packages/esm-patient-banner-app/src/print-identifier-sticker/print-identifier-sticker.scss @@ -1,10 +1,19 @@ @use '@carbon/layout'; @use '@carbon/type'; +@use '~@openmrs/esm-styleguide/src/_vars' as *; .stickerContainer { padding: layout.$spacing-03 layout.$spacing-05; } +.documentHeader { + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid $brand-01; + margin-bottom: layout.$spacing-05; +} + .row { margin: layout.$spacing-03 0 0; display: flex; @@ -49,6 +58,20 @@ } } +.strong { + @include type.type-style('heading-04'); + font-weight: 700; + margin-inline-end: layout.$spacing-03; +} + +.patientDetail { + @include type.type-style('heading-03'); +} + +.implementationLogo { + width: 40%; +} + @media print { html, body { diff --git a/packages/esm-patient-banner-app/src/routes.json b/packages/esm-patient-banner-app/src/routes.json index 2de83d9011..06829547f0 100644 --- a/packages/esm-patient-banner-app/src/routes.json +++ b/packages/esm-patient-banner-app/src/routes.json @@ -24,6 +24,13 @@ "component": "patientBanner", "online": true, "offline": true + }, + { + "name": "print-identifier-sticker-button", + "slot": "patient-actions-slot", + "component": "printIdentifierStickerActionButton", + "online": true, + "offline": true } ], "modals": [ diff --git a/packages/esm-patient-banner-app/translations/en.json b/packages/esm-patient-banner-app/translations/en.json index c96dacfc21..d69ea42ead 100644 --- a/packages/esm-patient-banner-app/translations/en.json +++ b/packages/esm-patient-banner-app/translations/en.json @@ -6,9 +6,14 @@ "country": "Country", "countyDistrict": "District", "district": "District", - "dob": "DOB", + "patientAge": "Age:", + "patientDateOfBirthWithSeparator": "Date of birth:", + "patientGenderWithSeparator": "Gender:", "patientIdentifierSticker": "Patient identifier sticker", + "patientNameWithSeparator": "Patient name:", "postalCode": "Postal code", + "printIdentifierSticker": "Print identifier sticker", "state": "State", - "stateProvince": "State" + "stateProvince": "State", + "telephoneNumberWithSeparator": "Telephone number:" } diff --git a/packages/esm-patient-chart-app/src/index.ts b/packages/esm-patient-chart-app/src/index.ts index 2b4d78a186..5820f8daf2 100644 --- a/packages/esm-patient-chart-app/src/index.ts +++ b/packages/esm-patient-chart-app/src/index.ts @@ -24,7 +24,6 @@ import pastVisitsDetailOverviewComponent from './visit/past-visit-overview.compo import pastVisitsOverviewComponent from './visit/visits-widget/visit-detail-overview.component'; import patientChartPageComponent from './root.component'; import patientDetailsTileComponent from './patient-details-tile/patient-details-tile.component'; -import printIdentifierStickerActionButtonComponent from './actions-buttons/print-identifier-sticker.component'; import startVisitActionButtonComponent from './actions-buttons/start-visit.component'; import startVisitActionButtonOnPatientSearch from './visit/start-visit-button.component'; import startVisitFormComponent from './visit/visit-form/visit-form.component'; @@ -50,11 +49,6 @@ export function startupApp() { 'Retrospective Data Entry', "Features to enter data for past visits. Includes the 'Edit Past Visit' button in the start visit dialog, and the 'Add Past Visit' button in the patient header.", ); - registerFeatureFlag( - 'print-patient-identifier-sticker', - 'Print patient identifier sticker', - 'Features to support printing a patient identifier sticker', - ); } export const root = getSyncLifecycle(patientChartPageComponent, { featureName: 'patient-chart', moduleName }); @@ -229,11 +223,6 @@ export const deleteVisitActionButton = getAsyncLifecycle( { featureName: 'delete-visit', moduleName }, ); -export const printIdentifierStickerActionButton = getSyncLifecycle(printIdentifierStickerActionButtonComponent, { - featureName: 'patient-actions-slot-print-identifier-sticker-button', - moduleName, -}); - export const activeVisitActionsComponent = getAsyncLifecycle( () => import('./visit/visits-widget/active-visit-buttons/active-visit-buttons'), { featureName: 'active-visit-actions', moduleName }, diff --git a/packages/esm-patient-chart-app/src/routes.json b/packages/esm-patient-chart-app/src/routes.json index b006461f43..71fde6684e 100644 --- a/packages/esm-patient-chart-app/src/routes.json +++ b/packages/esm-patient-chart-app/src/routes.json @@ -70,13 +70,6 @@ "online": true, "offline": true }, - { - "name": "print-identifier-sticker-button", - "slot": "patient-actions-slot", - "component": "printIdentifierStickerActionButton", - "online": true, - "offline": true - }, { "name": "add-past-visit-button", "slot": "patient-search-actions-slot", diff --git a/packages/esm-patient-chart-app/translations/en.json b/packages/esm-patient-chart-app/translations/en.json index e9fc6e9069..43f57a82d1 100644 --- a/packages/esm-patient-chart-app/translations/en.json +++ b/packages/esm-patient-chart-app/translations/en.json @@ -118,7 +118,6 @@ "pastVisitErrorText": "Past visit error", "pastVisits": "Past visits", "Patient Summary": "Patient summary", - "printIdentifierSticker": "Print identifier sticker", "program": "Program", "provider": "Provider", "quantity": "Quantity", diff --git a/yarn.lock b/yarn.lock index 0a476de993..12479e9a6c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5516,9 +5516,9 @@ __metadata: languageName: node linkType: hard -"@openmrs/esm-api@npm:5.8.2-pre.2361": - version: 5.8.2-pre.2361 - resolution: "@openmrs/esm-api@npm:5.8.2-pre.2361" +"@openmrs/esm-api@npm:5.8.2-pre.2369": + version: 5.8.2-pre.2369 + resolution: "@openmrs/esm-api@npm:5.8.2-pre.2369" dependencies: "@types/fhir": "npm:0.0.31" lodash-es: "npm:^4.17.21" @@ -5527,17 +5527,17 @@ __metadata: "@openmrs/esm-error-handling": 5.x "@openmrs/esm-navigation": 5.x "@openmrs/esm-offline": 5.x - checksum: 10/b4b531ca8efe645fc33215b4b0f36394a18524e986df4dc31514bfacf9faa8e25e0a2311a2ae41f17fd9c5507f16baee2c2cb328774237173f3b57ab358603a1 + checksum: 10/cf66b24879c3a4c754c59fea05de33628cba06a44ecb57b58da8dc673ff298fbf7953af4e36f0088cdad62b00d9fbf7e95cf2ee493c5422c17eb272d28f4a4d8 languageName: node linkType: hard -"@openmrs/esm-app-shell@npm:5.8.2-pre.2361": - version: 5.8.2-pre.2361 - resolution: "@openmrs/esm-app-shell@npm:5.8.2-pre.2361" +"@openmrs/esm-app-shell@npm:5.8.2-pre.2369": + version: 5.8.2-pre.2369 + resolution: "@openmrs/esm-app-shell@npm:5.8.2-pre.2369" dependencies: "@carbon/react": "npm:~1.37.0" - "@openmrs/esm-framework": "npm:5.8.2-pre.2361" - "@openmrs/esm-styleguide": "npm:5.8.2-pre.2361" + "@openmrs/esm-framework": "npm:5.8.2-pre.2369" + "@openmrs/esm-styleguide": "npm:5.8.2-pre.2369" dayjs: "npm:^1.10.4" dexie: "npm:^3.0.3" html-webpack-plugin: "npm:^5.5.0" @@ -5562,13 +5562,13 @@ __metadata: workbox-strategies: "npm:^6.1.5" workbox-webpack-plugin: "npm:^6.1.5" workbox-window: "npm:^6.1.5" - checksum: 10/a71536c158c46b8e166bb4f77b7bf24c89d261c4a6611d96fcf181c9e040e6892125603684a1aa7f8b5deff19953a2514d1d62f5ea28890ca9455592fbd083ad + checksum: 10/994a0de0e78c2b6dcfe328afb4359f9e7f20434809c1ae0af4d5fe85ef3918403d2e9ca787f7c7ef546c09cb26b35fd341ed0175f22d81bf2e52dc3d74eb8e16 languageName: node linkType: hard -"@openmrs/esm-config@npm:5.8.2-pre.2361": - version: 5.8.2-pre.2361 - resolution: "@openmrs/esm-config@npm:5.8.2-pre.2361" +"@openmrs/esm-config@npm:5.8.2-pre.2369": + version: 5.8.2-pre.2369 + resolution: "@openmrs/esm-config@npm:5.8.2-pre.2369" dependencies: ramda: "npm:^0.26.1" peerDependencies: @@ -5576,44 +5576,44 @@ __metadata: "@openmrs/esm-state": 5.x "@openmrs/esm-utils": 5.x single-spa: 5.x - checksum: 10/0e3184227ab66a8ddd93c0c24e5822f0ca205b8421129f8d635b24e0befa213f95750d8346411aca74a17ef9fee75006e414b570c282d59fc13f95081f942720 + checksum: 10/93e73515ff872b26506d24176f9aa5b97a1c5d38860c41dcc1572975d5e2fb36e19750a3ecbc0149601cb926be886b0d9a714f9aee3529ace5545e0421182155 languageName: node linkType: hard -"@openmrs/esm-context@npm:5.8.2-pre.2361": - version: 5.8.2-pre.2361 - resolution: "@openmrs/esm-context@npm:5.8.2-pre.2361" +"@openmrs/esm-context@npm:5.8.2-pre.2369": + version: 5.8.2-pre.2369 + resolution: "@openmrs/esm-context@npm:5.8.2-pre.2369" dependencies: immer: "npm:^10.0.4" peerDependencies: "@openmrs/esm-globals": 5.x "@openmrs/esm-state": 5.x - checksum: 10/61f1439da8658f41274d391cf41906adbaa5ab55e8955febbf919c840544ec4a60c60e967151fe7ebcc903466a259fd7d2a54b355af703280cb62698e3f48b52 + checksum: 10/3e751c7d458efb6b19f2bda340b5b399af21e6d387d871a4f32922f30790e56183f63d455e44b22c0e99951d66c22da26d518fdfff2f81e703fc669a931b588a languageName: node linkType: hard -"@openmrs/esm-dynamic-loading@npm:5.8.2-pre.2361": - version: 5.8.2-pre.2361 - resolution: "@openmrs/esm-dynamic-loading@npm:5.8.2-pre.2361" +"@openmrs/esm-dynamic-loading@npm:5.8.2-pre.2369": + version: 5.8.2-pre.2369 + resolution: "@openmrs/esm-dynamic-loading@npm:5.8.2-pre.2369" peerDependencies: "@openmrs/esm-globals": 5.x "@openmrs/esm-translations": 5.x - checksum: 10/bbb5d1de5b5872ea727957b7754bf171c3d6372736f9b5371adb368860d71ef2c537d7a0e10acf8a3caaa11fb1d1f77004cd7dd07b73e17f101dc450af8c20cd + checksum: 10/762d33106bb2bca285fa239ed97a644e67fb6b68ef03621f6813bb429498a30400e616d440a2186957355b3142236d47128c42dc0fd247ca3e8b7a73d94d2051 languageName: node linkType: hard -"@openmrs/esm-error-handling@npm:5.8.2-pre.2361": - version: 5.8.2-pre.2361 - resolution: "@openmrs/esm-error-handling@npm:5.8.2-pre.2361" +"@openmrs/esm-error-handling@npm:5.8.2-pre.2369": + version: 5.8.2-pre.2369 + resolution: "@openmrs/esm-error-handling@npm:5.8.2-pre.2369" peerDependencies: "@openmrs/esm-globals": 5.x - checksum: 10/4a039b47f1c776e3e60d4ecf55154666bb9e077f6122a543c5a2b12c6fd065704fa3ec107e0e0b5e3723dceff56096986a1ac6e3d15183b4a8d436cb1302fc5f + checksum: 10/eaf94898c7860b2218868ec694128027ad40e27975f93088d5e1d7a658d05ddcdc12fd7a93d4641d8c0c265dc584cb359111e1fcf2c016c5fcd70c0fc016269a languageName: node linkType: hard -"@openmrs/esm-expression-evaluator@npm:5.8.2-pre.2361": - version: 5.8.2-pre.2361 - resolution: "@openmrs/esm-expression-evaluator@npm:5.8.2-pre.2361" +"@openmrs/esm-expression-evaluator@npm:5.8.2-pre.2369": + version: 5.8.2-pre.2369 + resolution: "@openmrs/esm-expression-evaluator@npm:5.8.2-pre.2369" dependencies: "@jsep-plugin/arrow": "npm:^1.0.5" "@jsep-plugin/new": "npm:^1.0.3" @@ -5622,13 +5622,13 @@ __metadata: "@jsep-plugin/template": "npm:^1.0.4" "@jsep-plugin/ternary": "npm:^1.1.3" jsep: "npm:^1.3.9" - checksum: 10/5f1d014dee476efbb46c3852bc947a5741282bb1c0fa2a94092c2442ae87ab2bf915532d70a8f0e3ffacec15a10d913d052ef0daf5cbf9c4c229dca2a4270445 + checksum: 10/ee9b7b213afbb9db1358a8cc50aca3d4d52dd147bb9170f88da08a46345f5256977065a91c2e37844304c76d368bbcf2ab3e1f678f8dbe909fc7d0bab3656fbb languageName: node linkType: hard -"@openmrs/esm-extensions@npm:5.8.2-pre.2361": - version: 5.8.2-pre.2361 - resolution: "@openmrs/esm-extensions@npm:5.8.2-pre.2361" +"@openmrs/esm-extensions@npm:5.8.2-pre.2369": + version: 5.8.2-pre.2369 + resolution: "@openmrs/esm-extensions@npm:5.8.2-pre.2369" dependencies: lodash-es: "npm:^4.17.21" peerDependencies: @@ -5639,20 +5639,20 @@ __metadata: "@openmrs/esm-state": 5.x "@openmrs/esm-utils": 5.x single-spa: 5.x - checksum: 10/dc132b030b7ea22ea1fd01d75bb5d414d015198a6b0683c091c108f665af75d9e7f62674e5885ec2f1fd6eeafcb4a4ab705a8184eca96d6520e40af25a401b2e + checksum: 10/c5c1822f735bd4527405a2c076f5a84c7db7e4b9fb249aeeaaa8f9ac4575cdba7162da062e26a5dd09d8843b926eed201d8c3d4e45ddcf6dc2ba505aaceb99fd languageName: node linkType: hard -"@openmrs/esm-feature-flags@npm:5.8.2-pre.2361": - version: 5.8.2-pre.2361 - resolution: "@openmrs/esm-feature-flags@npm:5.8.2-pre.2361" +"@openmrs/esm-feature-flags@npm:5.8.2-pre.2369": + version: 5.8.2-pre.2369 + resolution: "@openmrs/esm-feature-flags@npm:5.8.2-pre.2369" dependencies: ramda: "npm:^0.26.1" peerDependencies: "@openmrs/esm-globals": 5.x "@openmrs/esm-state": 5.x single-spa: 5.x - checksum: 10/201a388ec97f05e19136b37e3a91b69bc1db1e68ca2d11fe52b886d6946c48eba38f1702bbe50b65cff200b94ac6df4be2da95b71698ac81fb8ed2889067bdb6 + checksum: 10/e06e5181e2c22c88c724aeb0dd01d3aacec7d144d185eaaa426a93f495f1c496efe77dd4326b10dc48a422dda6cfbad59e835e1297c679b74b80e830ac6bbf45 languageName: node linkType: hard @@ -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.2324" protractor: "npm:~7.0.0" reflect-metadata: "npm:^0.1.13" rxjs: "npm:^7.8.0" @@ -5769,27 +5769,27 @@ __metadata: languageName: unknown linkType: soft -"@openmrs/esm-framework@npm:5.8.2-pre.2361, @openmrs/esm-framework@npm:next": - version: 5.8.2-pre.2361 - resolution: "@openmrs/esm-framework@npm:5.8.2-pre.2361" - dependencies: - "@openmrs/esm-api": "npm:5.8.2-pre.2361" - "@openmrs/esm-config": "npm:5.8.2-pre.2361" - "@openmrs/esm-context": "npm:5.8.2-pre.2361" - "@openmrs/esm-dynamic-loading": "npm:5.8.2-pre.2361" - "@openmrs/esm-error-handling": "npm:5.8.2-pre.2361" - "@openmrs/esm-expression-evaluator": "npm:5.8.2-pre.2361" - "@openmrs/esm-extensions": "npm:5.8.2-pre.2361" - "@openmrs/esm-feature-flags": "npm:5.8.2-pre.2361" - "@openmrs/esm-globals": "npm:5.8.2-pre.2361" - "@openmrs/esm-navigation": "npm:5.8.2-pre.2361" - "@openmrs/esm-offline": "npm:5.8.2-pre.2361" - "@openmrs/esm-react-utils": "npm:5.8.2-pre.2361" - "@openmrs/esm-routes": "npm:5.8.2-pre.2361" - "@openmrs/esm-state": "npm:5.8.2-pre.2361" - "@openmrs/esm-styleguide": "npm:5.8.2-pre.2361" - "@openmrs/esm-translations": "npm:5.8.2-pre.2361" - "@openmrs/esm-utils": "npm:5.8.2-pre.2361" +"@openmrs/esm-framework@npm:5.8.2-pre.2369, @openmrs/esm-framework@npm:next": + version: 5.8.2-pre.2369 + resolution: "@openmrs/esm-framework@npm:5.8.2-pre.2369" + dependencies: + "@openmrs/esm-api": "npm:5.8.2-pre.2369" + "@openmrs/esm-config": "npm:5.8.2-pre.2369" + "@openmrs/esm-context": "npm:5.8.2-pre.2369" + "@openmrs/esm-dynamic-loading": "npm:5.8.2-pre.2369" + "@openmrs/esm-error-handling": "npm:5.8.2-pre.2369" + "@openmrs/esm-expression-evaluator": "npm:5.8.2-pre.2369" + "@openmrs/esm-extensions": "npm:5.8.2-pre.2369" + "@openmrs/esm-feature-flags": "npm:5.8.2-pre.2369" + "@openmrs/esm-globals": "npm:5.8.2-pre.2369" + "@openmrs/esm-navigation": "npm:5.8.2-pre.2369" + "@openmrs/esm-offline": "npm:5.8.2-pre.2369" + "@openmrs/esm-react-utils": "npm:5.8.2-pre.2369" + "@openmrs/esm-routes": "npm:5.8.2-pre.2369" + "@openmrs/esm-state": "npm:5.8.2-pre.2369" + "@openmrs/esm-styleguide": "npm:5.8.2-pre.2369" + "@openmrs/esm-translations": "npm:5.8.2-pre.2369" + "@openmrs/esm-utils": "npm:5.8.2-pre.2369" dayjs: "npm:^1.10.7" peerDependencies: dayjs: 1.x @@ -5800,7 +5800,7 @@ __metadata: rxjs: 6.x single-spa: 5.x swr: 2.x - checksum: 10/6a5d1942087ea1b9658764c8886abad42dfc0dfb2e8ef79479b5ed093915a1f56531d2ccfd05c82961e329e80be2c1e177c8b7c2aecc6370ac2ebc776bf11e23 + checksum: 10/c90af1d79d535fde43e46ee8cd126004366f264f02cc8377cc7569012345f55926dc6a9b930b2dfb83ba09f9ce189267d0f4ad07fa6b081ce439a1b48ae0d986 languageName: node linkType: hard @@ -5826,31 +5826,31 @@ __metadata: languageName: unknown linkType: soft -"@openmrs/esm-globals@npm:5.8.2-pre.2361": - version: 5.8.2-pre.2361 - resolution: "@openmrs/esm-globals@npm:5.8.2-pre.2361" +"@openmrs/esm-globals@npm:5.8.2-pre.2369": + version: 5.8.2-pre.2369 + resolution: "@openmrs/esm-globals@npm:5.8.2-pre.2369" dependencies: "@types/fhir": "npm:0.0.31" peerDependencies: single-spa: 5.x - checksum: 10/02a884d87c054f23ce4f77c9d4f2b2234016e64eedfa1dc24be97ed4201442f2b6f0f08b3a96d5976f65147b89db34dd48be03dd3bd69d3b2dab17a41e96141c + checksum: 10/bd5e3ff2144a961617b97f0c43801d58f90cc675d8e5bb091da78a18119cbcfb03e328013489f4833f7e5378e44dc8130786a7734a0db0576ded0d8246c5d1cd languageName: node linkType: hard -"@openmrs/esm-navigation@npm:5.8.2-pre.2361": - version: 5.8.2-pre.2361 - resolution: "@openmrs/esm-navigation@npm:5.8.2-pre.2361" +"@openmrs/esm-navigation@npm:5.8.2-pre.2369": + version: 5.8.2-pre.2369 + resolution: "@openmrs/esm-navigation@npm:5.8.2-pre.2369" dependencies: path-to-regexp: "npm:6.1.0" peerDependencies: "@openmrs/esm-state": 5.x - checksum: 10/c564aa323cdb97d7009ba10c57c7aefc9d4af2e29a82494c80fdc361f46b26ed901c10127322bc5ba5d26efed81d7a9e6c52a5a716e50f53a1a26b9ecfe9d2ea + checksum: 10/2cefa7bf31cc0377b0b8bd489962fc31c7bd429b08831bc1bd45e0050e445b944fdada6076fc95f98d15c03ad23c5f9d6b24d4a45582dbe382825c2422b90949 languageName: node linkType: hard -"@openmrs/esm-offline@npm:5.8.2-pre.2361": - version: 5.8.2-pre.2361 - resolution: "@openmrs/esm-offline@npm:5.8.2-pre.2361" +"@openmrs/esm-offline@npm:5.8.2-pre.2369": + version: 5.8.2-pre.2369 + resolution: "@openmrs/esm-offline@npm:5.8.2-pre.2369" dependencies: dexie: "npm:^3.0.3" lodash-es: "npm:^4.17.21" @@ -5861,7 +5861,7 @@ __metadata: "@openmrs/esm-globals": 5.x "@openmrs/esm-state": 5.x rxjs: 6.x - checksum: 10/2aa6c34ca56e5e1a3a01a41cc4587e65596edeae89e397aed24b06e1bc43b01175152c55574a64d7473f341659768348744452b165222054c9548e6e4c8f8ff8 + checksum: 10/c1d67e084ea12c553fdf1ce0df845e329126b9aa84ac76353593e8b865c4f9e892972c5dd563f520ea4a2645326a64b901070db14cff161173166d27fe8853c8 languageName: node linkType: hard @@ -5997,6 +5997,7 @@ __metadata: openmrs: "npm:next" prettier: "npm:^3.0.3" react: "npm:^18.3.1" + react-barcode: "npm:^1.5.3" react-dom: "npm:^18.3.1" react-hook-form: "npm:^7.46.2" react-i18next: "npm:^11.18.6" @@ -6254,9 +6255,9 @@ __metadata: languageName: unknown linkType: soft -"@openmrs/esm-react-utils@npm:5.8.2-pre.2361": - version: 5.8.2-pre.2361 - resolution: "@openmrs/esm-react-utils@npm:5.8.2-pre.2361" +"@openmrs/esm-react-utils@npm:5.8.2-pre.2369": + version: 5.8.2-pre.2369 + resolution: "@openmrs/esm-react-utils@npm:5.8.2-pre.2369" dependencies: lodash-es: "npm:^4.17.21" single-spa-react: "npm:^6.0.0" @@ -6277,13 +6278,13 @@ __metadata: react-i18next: 11.x rxjs: 6.x swr: 2.x - checksum: 10/38115ba811c3d84893ee4c7c5f4c1995186955605df09371e15638018787fa2c7f4ce105a5dc84a7f1893067f1db85de9dd2e65071d067245b96d4cb6d242924 + checksum: 10/0f353a5659b9d94f0a03e7763fbf662c1bb01bb9f3b4ea49349d03b7e297035e4aeef1d92118cc4077eb9e8b63f2fe68f7ab122d2745aae1f789969d490269cb languageName: node linkType: hard -"@openmrs/esm-routes@npm:5.8.2-pre.2361": - version: 5.8.2-pre.2361 - resolution: "@openmrs/esm-routes@npm:5.8.2-pre.2361" +"@openmrs/esm-routes@npm:5.8.2-pre.2369": + version: 5.8.2-pre.2369 + resolution: "@openmrs/esm-routes@npm:5.8.2-pre.2369" peerDependencies: "@openmrs/esm-config": 5.x "@openmrs/esm-dynamic-loading": 5.x @@ -6292,24 +6293,24 @@ __metadata: "@openmrs/esm-globals": 5.x "@openmrs/esm-utils": 5.x single-spa: 6.x - checksum: 10/6dfbb3d04e452069151be9f697fbe8009b3c8abb1781c41395a353ad586d663351cd86ab81488798bfa82873862cd75ec5a4b0ec80cef5c1c8c4e2ea663b5482 + checksum: 10/38614f62f3910da5e103057972692d41e5fb8d2d4b694c9d93fd3d9699d2fd03ae1bb7b65bf5f7a6c5980f0c9b724a0306be51ed0481ce26e13a6de13fcf56f6 languageName: node linkType: hard -"@openmrs/esm-state@npm:5.8.2-pre.2361": - version: 5.8.2-pre.2361 - resolution: "@openmrs/esm-state@npm:5.8.2-pre.2361" +"@openmrs/esm-state@npm:5.8.2-pre.2369": + version: 5.8.2-pre.2369 + resolution: "@openmrs/esm-state@npm:5.8.2-pre.2369" dependencies: zustand: "npm:^4.5.5" peerDependencies: "@openmrs/esm-globals": 5.x - checksum: 10/63dfca465e114a9b564f2c7ff2c3537ae43cec0bada501f95e87873893b9e47757b349e6b70783e2dc200c47e85d7bc603dffe7a051b8ebf81c608f4eef1cf31 + checksum: 10/4329348f7b35fd6a6bf9580d8cfb45663b810af0e7043eee04aaa925248068579f469af3fde339657882fabd187b5a499ad077319c17e080ea6b06d3f0ffd6ba languageName: node linkType: hard -"@openmrs/esm-styleguide@npm:5.8.2-pre.2361": - version: 5.8.2-pre.2361 - resolution: "@openmrs/esm-styleguide@npm:5.8.2-pre.2361" +"@openmrs/esm-styleguide@npm:5.8.2-pre.2369": + version: 5.8.2-pre.2369 + resolution: "@openmrs/esm-styleguide@npm:5.8.2-pre.2369" dependencies: "@carbon/charts": "npm:^1.12.0" "@carbon/react": "npm:~1.37.0" @@ -6333,24 +6334,24 @@ __metadata: react-dom: 18.x react-i18next: 11.x rxjs: 6.x - checksum: 10/392807bda113efaa23d0c32fde4eb9cf37ef2320cc35290dd918de826002d739539b60b811ef72d5551c0940f3c6c8d61807c2f8bfc5b65b61561768d43908af + checksum: 10/6279a3098d1b677379400012bc838c4d2313081f6e57c06ca114bdc25030532a750d9bd368913b258a85ab269ec871c46f275f1a9fd36d4f0b1ba366d19b29d9 languageName: node linkType: hard -"@openmrs/esm-translations@npm:5.8.2-pre.2361": - version: 5.8.2-pre.2361 - resolution: "@openmrs/esm-translations@npm:5.8.2-pre.2361" +"@openmrs/esm-translations@npm:5.8.2-pre.2369": + version: 5.8.2-pre.2369 + resolution: "@openmrs/esm-translations@npm:5.8.2-pre.2369" dependencies: i18next: "npm:21.10.0" peerDependencies: i18next: 21.x - checksum: 10/c4be2be5d5828a8a36a917e5c69879647dd514417ace8a46ab8c67ae2ad60113ab8cd9daf85883b17cd6be7eb6dabca45e3a96118f8b2ba81d538bf04619c00e + checksum: 10/ce3babb0f655d4f2b407595b0816489a959d0d16bd863daf51c49fdd40dd7347164c82423dbd01617b2027a31faf1f95555e2c394da2b3d37dcbaf2fddb34e23 languageName: node linkType: hard -"@openmrs/esm-utils@npm:5.8.2-pre.2361": - version: 5.8.2-pre.2361 - resolution: "@openmrs/esm-utils@npm:5.8.2-pre.2361" +"@openmrs/esm-utils@npm:5.8.2-pre.2369": + version: 5.8.2-pre.2369 + resolution: "@openmrs/esm-utils@npm:5.8.2-pre.2369" dependencies: "@formatjs/intl-durationformat": "npm:^0.2.4" "@internationalized/date": "npm:^3.5.5" @@ -6360,7 +6361,7 @@ __metadata: dayjs: 1.x i18next: 21.x rxjs: 6.x - checksum: 10/fba0b545bf4e3e9ebafbc92987b83a92bea43adc4594c98e3c02aa3dbb93c12169286fa4a332762374ca919a98d76947ac638d1020d31878b35efd32a3f5fea2 + checksum: 10/22d3e463a911848547f88b3934285421e7fddbdbc6a8869437fee3911561c57d6e6e239641cd8f03db6bee5047382b967b8ab3bae2514d59273e5d01fc01acb1 languageName: node linkType: hard @@ -6411,9 +6412,9 @@ __metadata: languageName: node linkType: hard -"@openmrs/webpack-config@npm:5.8.2-pre.2361": - version: 5.8.2-pre.2361 - resolution: "@openmrs/webpack-config@npm:5.8.2-pre.2361" +"@openmrs/webpack-config@npm:5.8.2-pre.2369": + version: 5.8.2-pre.2369 + resolution: "@openmrs/webpack-config@npm:5.8.2-pre.2369" dependencies: "@swc/core": "npm:^1.3.58" clean-webpack-plugin: "npm:^4.0.0" @@ -6431,7 +6432,7 @@ __metadata: webpack-stats-plugin: "npm:^1.0.3" peerDependencies: webpack: 5.x - checksum: 10/d0b443ca6a22a1a4391e77c1e19da5c4ac7c443275d75c69fede25d99e16fab0e910bd70fd032636d73da922fe9bd2217fbdfea288a339b2459bf6e0e9273608 + checksum: 10/cbc1fbb09c627bb5330c26822c5a0cc859e78f12ebf4c527d6367b3cd89b82c5cd1b98db3a9ced7dcd52af6d678c0ba79a10aed42fd43df321f4ae3b4fbce739 languageName: node linkType: hard @@ -18184,6 +18185,13 @@ __metadata: languageName: node linkType: hard +"jsbarcode@npm:^3.8.0": + version: 3.11.6 + resolution: "jsbarcode@npm:3.11.6" + checksum: 10/4dcf9f675e799741c89fa4941a97147a1c01575d792a186d8f1f8ad6b949d9d78fa658b0c9321ec1fa2c9ec0aa9784ac1181632357585114ecd74e9a3a08c979 + languageName: node + linkType: hard + "jsbn@npm:1.1.0": version: 1.1.0 resolution: "jsbn@npm:1.1.0" @@ -20664,12 +20672,12 @@ __metadata: languageName: node linkType: hard -"openmrs@npm:next": - version: 5.8.2-pre.2361 - resolution: "openmrs@npm:5.8.2-pre.2361" +"openmrs@npm:^5.8.2-pre.2324, openmrs@npm:next": + version: 5.8.2-pre.2369 + resolution: "openmrs@npm:5.8.2-pre.2369" dependencies: - "@openmrs/esm-app-shell": "npm:5.8.2-pre.2361" - "@openmrs/webpack-config": "npm:5.8.2-pre.2361" + "@openmrs/esm-app-shell": "npm:5.8.2-pre.2369" + "@openmrs/webpack-config": "npm:5.8.2-pre.2369" "@pnpm/npm-conf": "npm:^2.1.0" "@swc/core": "npm:^1.3.58" autoprefixer: "npm:^10.4.20" @@ -20708,7 +20716,7 @@ __metadata: yargs: "npm:^17.6.2" bin: openmrs: ./dist/cli.js - checksum: 10/4a2f4940d486dd59ad9d447eaa78d9a901c6b2cc778130870cbd982563bdcb7aa8d876ab140450de11abb57a55b5c7f01272d593281cf890f9c82862da20da7d + checksum: 10/f2ac023ed7f6353454164385a74dae5c07950758986fa46b10453d820edb0e1e840516f1481e94d69a5bc959f46efbf7fd60791ef2a4cf6789fd14b74b64f9b9 languageName: node linkType: hard @@ -22352,6 +22360,18 @@ __metadata: languageName: node linkType: hard +"react-barcode@npm:^1.5.3": + version: 1.5.3 + resolution: "react-barcode@npm:1.5.3" + dependencies: + jsbarcode: "npm:^3.8.0" + prop-types: "npm:^15.6.2" + peerDependencies: + react: ^16.0.0 || ^17.0.0 || ^18.0.0 + checksum: 10/1c1953c6e64fad12cf60f6be82a138849c5d8f0267b7aac1077662b86e58637192282d4a22d745f947c1ae168f058fe1a53f3111a00ba34d4d53657ebb1118c1 + languageName: node + linkType: hard + "react-dom@npm:^18.1.0, react-dom@npm:^18.2.0, react-dom@npm:^18.3.1": version: 18.3.1 resolution: "react-dom@npm:18.3.1"