diff --git a/package.json b/package.json index 3e5b697..6c7848e 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "url": "https://github.com/openmrs/openmrs-esm-dispensing/issues" }, "dependencies": { - "@carbon/react": "1.12.0", + "@carbon/react": "1.71.0", "classnames": "^2.5.1", "lodash-es": "^4.17.21" }, diff --git a/src/components/action-buttons.scss b/src/components/action-buttons.scss index c133916..c29f1dc 100644 --- a/src/components/action-buttons.scss +++ b/src/components/action-buttons.scss @@ -1,5 +1,7 @@ +@use '@carbon/layout'; + .actionBtns { float: right; - margin-left: 1rem; - margin-top: 2rem; + margin-left: layout.$spacing-05; + margin-top: layout.$spacing-07; } diff --git a/src/components/action-buttons.component.test.tsx b/src/components/action-buttons.test.tsx similarity index 87% rename from src/components/action-buttons.component.test.tsx rename to src/components/action-buttons.test.tsx index 1371d1a..063797e 100644 --- a/src/components/action-buttons.component.test.tsx +++ b/src/components/action-buttons.test.tsx @@ -1,16 +1,22 @@ -import { render } from '@testing-library/react'; import React from 'react'; -import ActionButtons from './action-buttons.component'; +import { render, screen } from '@testing-library/react'; +import { getDefaultsFromConfigSchema, useConfig } from '@openmrs/esm-framework'; +import { configSchema, type PharmacyConfig } from '../config-schema'; import { type MedicationRequest, MedicationRequestStatus } from '../types'; -import { useConfig } from '@openmrs/esm-framework'; +import ActionButtons from './action-buttons.component'; -const mockedUseConfig = useConfig as jest.Mock; +const mockedUseConfig = jest.mocked(useConfig); const mockPatientUuid = '558494fe-5850-4b34-a3bf-06550334ba4a'; const mockEncounterUuid = '7aee7123-9e50-4f72-a636-895d77a63e98'; -describe('Action Buttons Component tests', () => { +const defaultPharmacyConfig: PharmacyConfig = { + ...getDefaultsFromConfigSchema(configSchema), +}; + +describe('ActionButtons', () => { beforeEach(() => { mockedUseConfig.mockReturnValue({ + ...defaultPharmacyConfig, medicationRequestExpirationPeriodInDays: 90, actionButtons: { pauseButton: { @@ -27,7 +33,7 @@ describe('Action Buttons Component tests', () => { }); }); - test('component should render dispense button if active medication', () => { + test('renders all the action buttons for an active medication request', () => { // status = active, and validity period start set to current datetime const medicationRequest: MedicationRequest = { resourceType: 'MedicationRequest', @@ -124,18 +130,21 @@ describe('Action Buttons Component tests', () => { }, }; - const { getByText, container } = render( + render( , ); - expect(getByText('Dispense')).toBeInTheDocument(); + + expect(screen.getByRole('button', { name: /dispense/i })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /pause/i })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /close/i })).toBeInTheDocument(); }); // status = active, but validity period start time years in the past - test('component should not render dispense button if expired medication', () => { + test('should not render the dispense button if the medication request is expired', () => { // status = active, and validity period start set to current datetime const medicationRequest: MedicationRequest = { resourceType: 'MedicationRequest', @@ -232,13 +241,14 @@ describe('Action Buttons Component tests', () => { }, }; - const { queryByText, container } = render( + render( , ); - expect(queryByText('Dispense')).not.toBeInTheDocument(); + + expect(screen.queryByRole('button', { name: /dispense/i })).not.toBeInTheDocument(); }); }); diff --git a/src/components/medication-card.component.tsx b/src/components/medication-card.component.tsx index efd7a71..db54727 100644 --- a/src/components/medication-card.component.tsx +++ b/src/components/medication-card.component.tsx @@ -1,20 +1,29 @@ import React from 'react'; -import { Tile } from '@carbon/react'; +import { IconButton, Tile } from '@carbon/react'; import { Edit } from '@carbon/react/icons'; +import { useTranslation } from 'react-i18next'; import { type MedicationReferenceOrCodeableConcept } from '../types'; -import styles from './medication-card.scss'; import { getMedicationDisplay } from '../utils'; +import styles from './medication-card.scss'; const MedicationCard: React.FC<{ medication: MedicationReferenceOrCodeableConcept; - editAction?: Function; + editAction?: () => void; }> = ({ medication, editAction }) => { + const { t } = useTranslation(); + return (

{getMedicationDisplay(medication)}

- {editAction && } + {editAction && ( + + + + + + )}
); }; diff --git a/src/components/medication-card.scss b/src/components/medication-card.scss index 632af2d..4ffbe36 100644 --- a/src/components/medication-card.scss +++ b/src/components/medication-card.scss @@ -1,20 +1,25 @@ -@use '@carbon/styles/scss/spacing'; -@use '@carbon/styles/scss/type'; -@import '~@openmrs/esm-styleguide/src/vars'; +@use '@carbon/layout'; +@use '@carbon/type'; +@use '@openmrs/esm-styleguide/src/vars' as *; .medicationTile { + background-color: white; + border-left: layout.$spacing-02 solid var(--brand-03); + color: $text-02; display: flex; flex-direction: row; justify-content: space-between; + margin: layout.$spacing-01 0 layout.$spacing-05 layout.$spacing-05; + padding: 0 layout.$spacing-05; width: 100%; - margin: 2px 0 8px; - padding: 0 8px 0 8px; - background-color: #fff; - border-left: 4px solid var(--brand-03); - color: $text-02; - margin-bottom: 1rem !important; } .medicationName { - font-size: 15px !important; + font-size: layout.$spacing-05 !important; +} + +.editButton { + :global(.cds--tooltip-content) { + color: white !important; + } } diff --git a/src/components/medication-card.component.test.tsx b/src/components/medication-card.test.tsx similarity index 50% rename from src/components/medication-card.component.test.tsx rename to src/components/medication-card.test.tsx index dbb3455..9b3804f 100644 --- a/src/components/medication-card.component.test.tsx +++ b/src/components/medication-card.test.tsx @@ -1,10 +1,10 @@ import React from 'react'; -import { render } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import { type MedicationReferenceOrCodeableConcept } from '../types'; import MedicationCard from './medication-card.component'; -describe('Medication Card Component tests', () => { - test('component should render medication card without edit action button', () => { +describe('MedicationCardComponent', () => { + test('renders the medication card without the edit action button', () => { const medication: MedicationReferenceOrCodeableConcept = { medicationReference: { display: 'Some Medication', @@ -13,9 +13,10 @@ describe('Medication Card Component tests', () => { }, }; - const { getByText, container } = render(); - expect(getByText('Some Medication')).toBeInTheDocument(); - expect(container.querySelector('svg')).not.toBeInTheDocument(); + render(); + + expect(screen.getByText('Some Medication')).toBeInTheDocument(); + expect(screen.queryByRole('button')).not.toBeInTheDocument(); }); test('component should render medication card with edit action button', () => { @@ -29,8 +30,8 @@ describe('Medication Card Component tests', () => { const action = () => 0; - const { getByText, container } = render(); - expect(getByText('Some Medication')).toBeInTheDocument(); - expect(container.querySelector('svg')).toBeInTheDocument(); + render(); + expect(screen.getByText('Some Medication')).toBeInTheDocument(); + expect(screen.queryByRole('button')).toBeInTheDocument(); }); }); diff --git a/src/components/medication-dispense-review.scss b/src/components/medication-dispense-review.scss index 83243e8..a39ad53 100644 --- a/src/components/medication-dispense-review.scss +++ b/src/components/medication-dispense-review.scss @@ -1,6 +1,6 @@ -@use '@carbon/styles/scss/spacing'; -@use '@carbon/styles/scss/type'; -@import '~@openmrs/esm-styleguide/src/vars'; +@use '@carbon/layout'; +@use '@carbon/type'; +@use '@openmrs/esm-styleguide/src/vars' as *; .medicationDispenseReviewContainer { display: flex; @@ -17,7 +17,7 @@ display: flex; flex-direction: row; flex: 1; - gap: spacing.$spacing-05; + gap: layout.$spacing-05; } .substitutionReason { @@ -43,8 +43,8 @@ .formGroup { display: flex; - margin-bottom: spacing.$spacing-02; - padding: spacing.$spacing-05; + margin-bottom: layout.$spacing-02; + padding: layout.$spacing-05; } :global(.omrs-breakpoint-lt-desktop) .formGroup > span { @@ -55,15 +55,17 @@ flex: 3; } -.formGroup span { - @extend .productiveHeading02; +.formGroup { + span { + @extend .productiveHeading02; + } } .patientInfo { position: sticky; z-index: 1000; background-color: $ui-02; - top: 3rem; + top: layout.$spacing-09; overflow-y: auto; } @@ -83,13 +85,15 @@ } :global(.omrs-breakpoint-lt-desktop) .buttonGroup { - padding: spacing.$spacing-05 spacing.$spacing-06; + padding: layout.$spacing-05 layout.$spacing-06; background-color: $ui-02; } -.buttonGroup button { - max-width: none; - width: 50%; - height: spacing.$spacing-10; - align-items: flex-start; +.buttonGroup { + button { + max-width: none; + width: 50%; + height: layout.$spacing-10; + align-items: flex-start; + } } diff --git a/src/components/medication-event.component.tsx b/src/components/medication-event.component.tsx index 8ca5eef..3202e53 100644 --- a/src/components/medication-event.component.tsx +++ b/src/components/medication-event.component.tsx @@ -1,6 +1,6 @@ import React from 'react'; +import { useTranslation } from 'react-i18next'; import { type DosageInstruction, type MedicationDispense, type MedicationRequest, type Quantity } from '../types'; -import styles from './medication-event.scss'; import { getDosageInstruction, getQuantity, @@ -8,7 +8,7 @@ import { getMedicationDisplay, getMedicationReferenceOrCodeableConcept, } from '../utils'; -import { useTranslation } from 'react-i18next'; +import styles from './medication-event.scss'; // can render MedicationRequest or MedicationDispense const MedicationEvent: React.FC<{ diff --git a/src/components/medication-event.scss b/src/components/medication-event.scss index 96edb38..6f0d5ba 100644 --- a/src/components/medication-event.scss +++ b/src/components/medication-event.scss @@ -1,18 +1,23 @@ -@use '@carbon/styles/scss/spacing'; -@use '@carbon/styles/scss/type'; -@import '~@openmrs/esm-styleguide/src/vars'; +@use '@carbon/layout'; +@use '@carbon/type'; +@use '@openmrs/esm-styleguide/src/vars' as *; .medicationName { - font-size: 15px !important; -} - -.bodyLong01 { - font-size: 13px !important; + font-size: layout.$spacing-05 !important; } .dosage, .quantity, .refills { - color: #525252; + color: $text-02; font-weight: bold; } + +.bodyLong01 { + @include type.type-style('body-01'); + font-size: 13px !important; +} + +.label01 { + @include type.type-style('label-01'); +} diff --git a/src/components/patient-details.component.tsx b/src/components/patient-details.component.tsx index edc66ef..f0111fc 100644 --- a/src/components/patient-details.component.tsx +++ b/src/components/patient-details.component.tsx @@ -1,19 +1,17 @@ -import React, { useEffect } from 'react'; -import { useTranslation } from 'react-i18next'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { attach, detach, ExtensionSlot, type PatientUuid, usePatient } from '@openmrs/esm-framework'; import styles from './patient-details.scss'; const PatientDetails: React.FC<{ patientUuid: PatientUuid; }> = ({ patientUuid }) => { - const { t } = useTranslation(); const { patient } = usePatient(patientUuid); const patientName = patient; - const patientPhotoSlotState = React.useMemo(() => ({ patientUuid, patientName }), [patientUuid, patientName]); + const patientPhotoSlotState = useMemo(() => ({ patientUuid, patientName }), [patientUuid, patientName]); - const [showContactDetails, setShowContactDetails] = React.useState(false); - const toggleContactDetails = React.useCallback((event: MouseEvent) => { + const [showContactDetails, setShowContactDetails] = useState(false); + const toggleContactDetails = useCallback((event: MouseEvent) => { event.stopPropagation(); setShowContactDetails((value) => !value); }, []); diff --git a/src/components/patient-details.scss b/src/components/patient-details.scss index 7f36846..e60fa2f 100644 --- a/src/components/patient-details.scss +++ b/src/components/patient-details.scss @@ -1,13 +1,13 @@ -@use '@carbon/styles/scss/spacing'; -@use '@carbon/styles/scss/type'; -@import '~@openmrs/esm-styleguide/src/vars'; +@use '@carbon/layout'; +@use '@carbon/type'; +@use '@openmrs/esm-styleguide/src/vars' as *; .patientDetailsContainer > div { - margin-bottom: 1rem; + margin-bottom: layout.$spacing-05; } .container { - border-bottom: 0.063rem solid $ui-03; + border-bottom: 1px solid $ui-03; background-color: $ui-02; display: grid; grid-template-columns: 1fr auto; @@ -21,13 +21,13 @@ .patientName { @include type.type-style('heading-03'); - margin-right: 0.25rem; + margin-right: layout.$spacing-02; } .patientAvatar { - width: 5rem; - height: 5rem; - margin: 1rem; + width: layout.$spacing-11; + height: layout.$spacing-11; + margin: layout.$spacing-05; border-radius: 1px; } @@ -40,7 +40,7 @@ .patientInfo { width: 100%; - padding-right: 1rem; + padding-right: layout.$spacing-05; } .demographics { @@ -57,7 +57,7 @@ } .patientNameRow { - margin-top: spacing.$spacing-05; + margin-top: layout.$spacing-05; } .flexRow { @@ -73,7 +73,7 @@ } .tooltipPadding { - padding: 0.25rem; + padding: layout.$spacing-02; } .tooltipSmallText { diff --git a/src/dashboard/dispensing-dashboard.component.tsx b/src/dashboard/dispensing-dashboard.component.tsx index 7602349..86d31ba 100644 --- a/src/dashboard/dispensing-dashboard.component.tsx +++ b/src/dashboard/dispensing-dashboard.component.tsx @@ -1,15 +1,16 @@ import React from 'react'; +import { useTranslation } from 'react-i18next'; import { InlineNotification } from '@carbon/react'; -import Overlay from '../forms/overlay/overlay.component'; -import { PharmacyHeader } from '../pharmacy-header/pharmacy-header.component'; -import PrescriptionTabLists from '../prescriptions/prescription-tab-lists.component'; import { useConfig } from '@openmrs/esm-framework'; -import { useTranslation } from 'react-i18next'; import { type PharmacyConfig } from '../config-schema'; +import { PharmacyHeader } from '../pharmacy-header/pharmacy-header.component'; +import PrescriptionTabLists from '../prescriptions/prescription-tab-lists.component'; +import Overlay from '../forms/overlay/overlay.component'; export default function DispensingDashboard() { - const config = useConfig(); const { t } = useTranslation(); + const config = useConfig(); + if (config.dispenseBehavior.restrictTotalQuantityDispensed && config.dispenseBehavior.allowModifyingPrescription) { return (
@@ -24,7 +25,7 @@ export default function DispensingDashboard() { ); } else { return ( -
+
{/* */} diff --git a/src/dispensing-tiles/dispensing-tile.component.tsx b/src/dispensing-tiles/dispensing-tile.component.tsx index 892a60d..c141f7b 100644 --- a/src/dispensing-tiles/dispensing-tile.component.tsx +++ b/src/dispensing-tiles/dispensing-tile.component.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import { Tile, Button } from '@carbon/react'; import { ArrowRight } from '@carbon/react/icons'; +import { ResponsiveWrapper } from '@openmrs/esm-framework'; import styles from './dispensing-tile.scss'; interface DispensingTileProps { @@ -15,24 +16,26 @@ const DispensingTile: React.FC = ({ label, value, headerLab const { t } = useTranslation(); return ( - -
-
- - {children} + + +
+
+ + {children} +
+
- -
-
- -

{value}

-
- +
+ +

{value}

+
+ + ); }; diff --git a/src/dispensing-tiles/dispensing-tile.scss b/src/dispensing-tiles/dispensing-tile.scss index f7d762c..ccf7524 100644 --- a/src/dispensing-tiles/dispensing-tile.scss +++ b/src/dispensing-tiles/dispensing-tile.scss @@ -1,20 +1,20 @@ -@use '@carbon/styles/scss/spacing'; -@use '@carbon/styles/scss/type'; -@import '~@openmrs/esm-styleguide/src/vars'; +@use '@carbon/layout'; +@use '@carbon/type'; +@use '@openmrs/esm-styleguide/src/vars' as *; .tileContainer { - border: 0.0625rem solid $ui-03; + border: 1px solid $ui-03; flex-grow: 1; height: 7.875rem; - padding: 0 spacing.$spacing-05; - margin: spacing.$spacing-03 spacing.$spacing-03; + padding: 0 layout.$spacing-05; + margin: layout.$spacing-03 layout.$spacing-03; } .tileHeader { display: flex; justify-content: space-between; align-items: center; - margin-bottom: spacing.$spacing-03; + margin-bottom: layout.$spacing-03; } .headerLabel { @@ -35,7 +35,7 @@ .headerLabelContainer { display: flex; align-items: center; - height: spacing.$spacing-07; + height: layout.$spacing-07; } .arrowIcon { diff --git a/src/dispensing-tiles/dispensing-tiles.component.tsx b/src/dispensing-tiles/dispensing-tiles.component.tsx index d3f6eef..c247094 100644 --- a/src/dispensing-tiles/dispensing-tiles.component.tsx +++ b/src/dispensing-tiles/dispensing-tiles.component.tsx @@ -7,7 +7,7 @@ import styles from './dispensing-tiles.scss'; const DispensingTiles: React.FC = () => { const { t } = useTranslation(); - const { metrics, isError, isLoading } = useMetrics(); + const { metrics, error, isLoading } = useMetrics(); if (isLoading) { return ; diff --git a/src/dispensing-tiles/dispensing-tiles.resource.tsx b/src/dispensing-tiles/dispensing-tiles.resource.tsx index dfcea8f..1a26df3 100644 --- a/src/dispensing-tiles/dispensing-tiles.resource.tsx +++ b/src/dispensing-tiles/dispensing-tiles.resource.tsx @@ -3,23 +3,23 @@ import useSWRImmutable from 'swr/immutable'; import { type FetchResponse, openmrsFetch } from '@openmrs/esm-framework'; // NOT CURRENTLY USED - export function useMetrics() { const metrics = { orders: 43, orders_for_home_delivery: 4, missed_collections: 12, }; - const { data, error } = useSWR<{ data: { results: {} } }, Error>(`/ws/rest/v1/queue?`, openmrsFetch); + const { data, error, isLoading } = useSWR<{ data: { results: {} } }, Error>(`/ws/rest/v1/queue?`, openmrsFetch); return { metrics: metrics, - isError: error, - isLoading: !data && !error, + error, + isLoading, }; } export function useServices() { + // TODO: Move to config file const serviceConceptSetUuid = '330c0ec6-0ac7-4b86-9c70-29d76f0ae20a'; const apiUrl = `/ws/rest/v1/concept/${serviceConceptSetUuid}`; const { data } = useSWRImmutable(apiUrl, openmrsFetch); diff --git a/src/dispensing-tiles/dispensing-tiles.scss b/src/dispensing-tiles/dispensing-tiles.scss index 3d83eb9..16d4985 100644 --- a/src/dispensing-tiles/dispensing-tiles.scss +++ b/src/dispensing-tiles/dispensing-tiles.scss @@ -1,12 +1,12 @@ -@use '@carbon/styles/scss/spacing'; -@use '@carbon/styles/scss/type'; -@import '~@openmrs/esm-styleguide/src/vars'; +@use '@carbon/layout'; +@use '@carbon/type'; +@use '@openmrs/esm-styleguide/src/vars' as *; .cardContainer { background-color: $ui-02; display: flex; justify-content: space-between; - padding: 0 spacing.$spacing-05 spacing.$spacing-10 spacing.$spacing-03; + padding: 0 layout.$spacing-05 layout.$spacing-10 layout.$spacing-03; flex-flow: row wrap; - margin-top: -spacing.$spacing-03; + margin-top: -layout.$spacing-03; } diff --git a/src/dispensing.test.tsx b/src/dispensing.test.tsx index a425b55..153edc9 100644 --- a/src/dispensing.test.tsx +++ b/src/dispensing.test.tsx @@ -1,25 +1,3 @@ -/** - * This is the root test for this page. It simply checks that the page - * renders. If the components of your page are highly interdependent, - * (e.g., if the `Hello` component had state that communicated - * information between `Greeter` and `PatientGetter`) then you might - * want to do most of your testing here. If those components are - * instead quite independent (as is the case in this example), then - * it would make more sense to test those components independently. - * - * The key thing to remember, always, is: write tests that behave like - * users. They should *look* for elements by their visual - * characteristics, *interact* with them, and (mostly) *assert* based - * on things that would be visually apparent to a user. - * - * To learn more about how we do testing, see the following resources: - * https://kentcdodds.com/blog/how-to-know-what-to-test - * https://kentcdodds.com/blog/testing-implementation-details - * https://kentcdodds.com/blog/common-mistakes-with-react-testing-library - * - * Kent C. Dodds is the inventor of `@testing-library`: - * https://testing-library.com/docs/guiding-principles - */ import React from 'react'; import { render } from '@testing-library/react'; import Dispensing from './dispensing.component'; diff --git a/src/forms/close-dispense-form.component.tsx b/src/forms/close-dispense-form.component.tsx index cb8c649..31a79bd 100644 --- a/src/forms/close-dispense-form.component.tsx +++ b/src/forms/close-dispense-form.component.tsx @@ -2,20 +2,20 @@ import React, { useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { ExtensionSlot, + ResponsiveWrapper, showNotification, showToast, useConfig, - useLayoutType, usePatient, } from '@openmrs/esm-framework'; import { Button, ComboBox, InlineLoading } from '@carbon/react'; import { saveMedicationDispense, useReasonForCloseValueSet } from '../medication-dispense/medication-dispense.resource'; import { closeOverlay } from '../hooks/useOverlay'; -import styles from './forms.scss'; import { updateMedicationRequestFulfillerStatus } from '../medication-request/medication-request.resource'; import { type MedicationDispense, MedicationDispenseStatus, MedicationRequestFulfillerStatus } from '../types'; import { type PharmacyConfig } from '../config-schema'; import { getUuidFromReference, revalidate } from '../utils'; +import styles from './forms.scss'; interface CloseDispenseFormProps { medicationDispense: MedicationDispense; @@ -31,9 +31,8 @@ const CloseDispenseForm: React.FC = ({ encounterUuid, }) => { const { t } = useTranslation(); - const config = useConfig(); - const isTablet = useLayoutType() === 'tablet'; const { patient, isLoading } = usePatient(patientUuid); + const config = useConfig(); // Keep track of medication dispense payload const [medicationDispensePayload, setMedicationDispensePayload] = useState(); @@ -152,29 +151,30 @@ const CloseDispenseForm: React.FC = ({ )} {patient && }
- item?.text} - initialSelectedItem={{ - id: medicationDispense.statusReasonCodeableConcept?.coding[0]?.code, - text: medicationDispense.statusReasonCodeableConcept?.text, - }} - onChange={({ selectedItem }) => { - setMedicationDispensePayload({ - ...medicationDispensePayload, - statusReasonCodeableConcept: { - coding: [ - { - code: selectedItem?.id, - }, - ], - }, - }); - }} - /> + + item?.text} + initialSelectedItem={{ + id: medicationDispense.statusReasonCodeableConcept?.coding[0]?.code, + text: medicationDispense.statusReasonCodeableConcept?.text, + }} + onChange={({ selectedItem }) => { + setMedicationDispensePayload({ + ...medicationDispensePayload, + statusReasonCodeableConcept: { + coding: [ + { + code: selectedItem?.id, + }, + ], + }, + }); + }} + /> +