Skip to content

Commit 1168846

Browse files
authored
Merge pull request #3 from FelixKiprotich350/main
(feat) intial UI design and population of prescribed medical supplies which are non drugs
2 parents 24545c0 + eded378 commit 1168846

File tree

53 files changed

+8191
-40
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+8191
-40
lines changed
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { render } from '@testing-library/react';
2+
import React from 'react';
3+
import ActionButtons from './action-buttons.component';
4+
import { type MedicationRequest, MedicationRequestStatus, NonDrugMedicationDispense } from '../types';
5+
import { toDateObjectStrict, useConfig } from '@openmrs/esm-framework';
6+
import { date } from 'zod';
7+
8+
const mockedUseConfig = useConfig as jest.Mock;
9+
const mockPatientUuid = '558494fe-5850-4b34-a3bf-06550334ba4a';
10+
const mockEncounterUuid = '7aee7123-9e50-4f72-a636-895d77a63e98';
11+
12+
describe('Action Buttons Component tests', () => {
13+
beforeEach(() => {
14+
mockedUseConfig.mockReturnValue({
15+
medicationRequestExpirationPeriodInDays: 90,
16+
actionButtons: {
17+
pauseButton: {
18+
enabled: true,
19+
},
20+
closeButton: {
21+
enabled: true,
22+
},
23+
},
24+
dispenseBehavior: {
25+
allowModifyingPrescription: false,
26+
restrictTotalQuantityDispensed: false,
27+
},
28+
});
29+
});
30+
31+
test('component should render dispense button if active medication', () => {
32+
// status = active, and validity period start set to current datetime
33+
const medicationRequest: NonDrugMedicationDispense = {
34+
uuid: 'd4f69a68-1171-4e47-8693-478df18daf40',
35+
patient: 'd4f69a68-1171-4e47-8693-478df18daf40',
36+
encounter: 'd4f69a68-1171-4e47-8693-478df18daf40',
37+
dispensingUnit: { uuid: 'd4f69a68-1171-4e47-8693-478df18daf40', display: 'Test' },
38+
quantity: 2,
39+
display: 'test ',
40+
instrucions: 'test',
41+
status: '',
42+
medicalSupplyOrder: 'd4f69a68-1171-4e47-8693-478df18daf40',
43+
concept: '',
44+
dateDispensed: new Date(),
45+
statusReason: '',
46+
location: '',
47+
encounters: '',
48+
dispenser: '',
49+
};
50+
51+
const { getByText, container } = render(
52+
<ActionButtons
53+
patientUuid={mockPatientUuid}
54+
encounterUuid={mockEncounterUuid}
55+
medicationDispense={medicationRequest}
56+
/>,
57+
);
58+
expect(getByText('Dispense')).toBeInTheDocument();
59+
});
60+
61+
// status = active, but validity period start time years in the past
62+
test('component should not render dispense button if expired medication', () => {
63+
// status = active, and validity period start set to current datetime
64+
const medicationRequest: NonDrugMedicationDispense = {
65+
uuid: 'd4f69a68-1171-4e47-8693-478df18daf40',
66+
patient: 'd4f69a68-1171-4e47-8693-478df18daf40',
67+
encounter: 'd4f69a68-1171-4e47-8693-478df18daf40',
68+
dispensingUnit: { uuid: 'd4f69a68-1171-4e47-8693-478df18daf40', display: 'Test' },
69+
quantity: 2,
70+
display: 'test ',
71+
instrucions: 'test',
72+
status: '',
73+
medicalSupplyOrder: 'd4f69a68-1171-4e47-8693-478df18daf40',
74+
concept: '',
75+
dateDispensed: new Date(),
76+
statusReason: '',
77+
location: '',
78+
encounters: '',
79+
dispenser: '',
80+
};
81+
82+
const { queryByText, container } = render(
83+
<ActionButtons
84+
patientUuid={mockPatientUuid}
85+
encounterUuid={mockEncounterUuid}
86+
medicationDispense={medicationRequest}
87+
/>,
88+
);
89+
// expect(queryByText('Dispense')).not.toBeInTheDocument();
90+
});
91+
});
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import React from 'react';
2+
import { Button } from '@carbon/react';
3+
import { useTranslation } from 'react-i18next';
4+
import { useConfig, useSession } from '@openmrs/esm-framework';
5+
import {
6+
MedicationDispenseStatus,
7+
type MedicationRequestBundle,
8+
MedicationRequestStatus,
9+
NonDrugMedicationDispense,
10+
} from '../types';
11+
import { launchOverlay } from '../hooks/useOverlay';
12+
import {
13+
computeMedicationRequestStatus,
14+
computeQuantityRemaining,
15+
getMostRecentMedicationDispenseStatus,
16+
} from '../utils';
17+
import { type PharmacyConfig } from '../config-schema';
18+
import { initiateMedicationDispenseBody, useProviders } from '../medication-dispense/medication-dispense.resource';
19+
import DispenseForm from '../forms/dispense-form.component';
20+
import PauseDispenseForm from '../forms/pause-dispense-form.component';
21+
import CloseDispenseForm from '../forms/close-dispense-form.component';
22+
import styles from './action-buttons.scss';
23+
24+
interface ActionButtonsProps {
25+
patientUuid: string;
26+
encounterUuid: string;
27+
medicationDispense: NonDrugMedicationDispense;
28+
}
29+
30+
const ActionButtons: React.FC<ActionButtonsProps> = ({ patientUuid, encounterUuid, medicationDispense }) => {
31+
const { t } = useTranslation();
32+
const config = useConfig<PharmacyConfig>();
33+
const session = useSession();
34+
const providers = useProviders(config.dispenserProviderRoles);
35+
36+
return (
37+
<div className={styles.actionBtns}>
38+
{medicationDispense.uuid ? (
39+
<Button
40+
kind="primary"
41+
onClick={() =>
42+
launchOverlay(
43+
t('dispensePrescription', 'Dispense prescription'),
44+
<DispenseForm
45+
patientUuid={patientUuid}
46+
encounterUuid={encounterUuid}
47+
medicationDispense={medicationDispense}
48+
mode="enter"
49+
/>,
50+
)
51+
}>
52+
{t('dispense', 'Dispense')}
53+
</Button>
54+
) : null}
55+
{medicationDispense.uuid ? (
56+
<Button
57+
kind="secondary"
58+
onClick={() =>
59+
launchOverlay(
60+
t('pausePrescription', 'Pause prescription'),
61+
<PauseDispenseForm patientUuid={patientUuid} encounterUuid={encounterUuid} mode="enter" />,
62+
)
63+
}>
64+
{t('pause', 'Pause')}
65+
</Button>
66+
) : null}
67+
{medicationDispense.uuid ? (
68+
<Button
69+
kind="danger"
70+
onClick={() =>
71+
launchOverlay(
72+
t('closePrescription', 'Close prescription'),
73+
<CloseDispenseForm patientUuid={patientUuid} encounterUuid={encounterUuid} mode="enter" />,
74+
)
75+
}>
76+
{t('close', 'Close')}
77+
</Button>
78+
) : null}
79+
</div>
80+
);
81+
};
82+
83+
export default ActionButtons;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.actionBtns {
2+
float: right;
3+
margin-left: 1rem;
4+
margin-top: 2rem;
5+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import React from 'react';
2+
import { render } from '@testing-library/react';
3+
import { type MedicationReferenceOrCodeableConcept } from '../types';
4+
import MedicationCard from './medication-card.component';
5+
6+
describe('Medication Card Component tests', () => {
7+
test('component should render medication card without edit action button', () => {
8+
const medication: MedicationReferenceOrCodeableConcept = {
9+
medicationReference: {
10+
display: 'Some Medication',
11+
reference: '',
12+
type: '',
13+
},
14+
};
15+
16+
const { getByText, container } = render(<MedicationCard medication={medication} />);
17+
expect(getByText('Some Medication')).toBeInTheDocument();
18+
expect(container.querySelector('svg')).not.toBeInTheDocument();
19+
});
20+
21+
test('component should render medication card with edit action button', () => {
22+
const medication: MedicationReferenceOrCodeableConcept = {
23+
medicationReference: {
24+
display: 'Some Medication',
25+
reference: '',
26+
type: '',
27+
},
28+
};
29+
30+
const action = () => 0;
31+
32+
const { getByText, container } = render(<MedicationCard medication={medication} editAction={action} />);
33+
expect(getByText('Some Medication')).toBeInTheDocument();
34+
expect(container.querySelector('svg')).toBeInTheDocument();
35+
});
36+
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import React, { ReactSVGElement } from 'react';
2+
import { Tile } from '@carbon/react';
3+
import { Edit } from '@carbon/react/icons';
4+
import { type MedicationReferenceOrCodeableConcept } from '../types/index';
5+
import styles from './medication-card.scss';
6+
import { getMedicationDisplay } from '../utils';
7+
8+
const MedicationCard: React.FC<{
9+
medication: MedicationReferenceOrCodeableConcept;
10+
editAction?: Function;
11+
}> = ({ medication, editAction }) => {
12+
return (
13+
<Tile className={styles.medicationTile}>
14+
<p className={styles.medicationName}>
15+
<strong>{getMedicationDisplay(medication)}</strong>
16+
</p>
17+
{editAction && <Edit onClick={editAction as React.MouseEventHandler<ReactSVGElement>} />}
18+
</Tile>
19+
);
20+
};
21+
22+
export default MedicationCard;
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
@use '@carbon/styles/scss/spacing';
2+
@use '@carbon/styles/scss/type';
3+
@import '~@openmrs/esm-styleguide/src/vars';
4+
5+
.medicationTile {
6+
display: flex;
7+
flex-direction: row;
8+
justify-content: space-between;
9+
width: 100%;
10+
margin: 2px 0 8px;
11+
padding: 0 8px 0 8px;
12+
background-color: #fff;
13+
border-left: 4px solid var(--brand-03);
14+
color: $text-02;
15+
margin-bottom: 1rem !important;
16+
}
17+
18+
.medicationName {
19+
font-size: 15px !important;
20+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
@use '@carbon/styles/scss/spacing';
2+
@use '@carbon/styles/scss/type';
3+
@import '~@openmrs/esm-styleguide/src/vars';
4+
5+
.medicationDispenseReviewContainer {
6+
display: flex;
7+
flex-direction: column;
8+
padding-bottom: 10px;
9+
10+
:global(.cds--css-grid) {
11+
padding-left: 0 !important;
12+
padding-right: 0 !important;
13+
}
14+
}
15+
16+
.dispenseDetailsContainer {
17+
display: flex;
18+
flex-direction: row;
19+
flex: 1;
20+
gap: spacing.$spacing-05;
21+
}
22+
23+
.substitutionReason {
24+
width: 100%;
25+
}
26+
27+
.substitutionType {
28+
width: 100%;
29+
}
30+
31+
.productiveHeading02 {
32+
color: $color-gray-70;
33+
@include type.type-style('productive-heading-02');
34+
}
35+
36+
:global(.omrs-breakpoint-lt-desktop) .formWrapper {
37+
background-color: $openmrs-background-grey;
38+
}
39+
40+
:global(.omrs-breakpoint-gt-tablet) .formWrapper {
41+
background-color: $ui-02;
42+
}
43+
44+
.formGroup {
45+
display: flex;
46+
margin-bottom: spacing.$spacing-02;
47+
padding: spacing.$spacing-05;
48+
}
49+
50+
:global(.omrs-breakpoint-lt-desktop) .formGroup > span {
51+
flex: 1;
52+
}
53+
54+
:global(.omrs-breakpoint-lt-desktop) .formGroup > div {
55+
flex: 3;
56+
}
57+
58+
.formGroup span {
59+
@extend .productiveHeading02;
60+
}
61+
62+
.patientInfo {
63+
position: sticky;
64+
z-index: 1000;
65+
background-color: $ui-02;
66+
top: 3rem;
67+
overflow-y: auto;
68+
}
69+
70+
:global(.omrs-breakpoint-lt-desktop) .formGroup {
71+
flex-direction: row;
72+
}
73+
74+
:global(.omrs-breakpoint-gt-tablet) .formGroup {
75+
flex-direction: column;
76+
}
77+
78+
.buttonGroup {
79+
display: flex;
80+
position: sticky;
81+
bottom: 0;
82+
width: 100%;
83+
}
84+
85+
:global(.omrs-breakpoint-lt-desktop) .buttonGroup {
86+
padding: spacing.$spacing-05 spacing.$spacing-06;
87+
background-color: $ui-02;
88+
}
89+
90+
.buttonGroup button {
91+
max-width: none;
92+
width: 50%;
93+
height: spacing.$spacing-10;
94+
align-items: flex-start;
95+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import React from 'react';
2+
import {
3+
NonDrugDispensingUnit,
4+
NonDrugMedicationDispense,
5+
type DosageInstruction,
6+
type MedicationDispense,
7+
type MedicationRequest,
8+
type Quantity,
9+
} from '../types';
10+
import styles from './medication-event.scss';
11+
12+
import { useTranslation } from 'react-i18next';
13+
14+
// can render MedicationRequest or MedicationDispense
15+
const MedicationEvent: React.FC<{ medicationDispense: NonDrugMedicationDispense }> = ({ medicationDispense }) => {
16+
const { t } = useTranslation();
17+
18+
return (
19+
<div>
20+
<p className={styles.medicationName}>
21+
<strong>{medicationDispense.display}</strong>
22+
</p>
23+
24+
<p className={styles.bodyLong01}>
25+
<span className={styles.label01}>{t('quantity', 'Quantity').toUpperCase()}</span>{' '}
26+
<span className={styles.quantity}> : {String(medicationDispense.quantity)} </span>
27+
</p>
28+
<p className={styles.bodyLong01}>
29+
<span className={styles.label01}>{t('dispenseUnit', 'Dispense Unit').toUpperCase()}</span>{' '}
30+
<span className={styles.quantity}> : {String(medicationDispense.dispensingUnit?.display)}</span>
31+
</p>
32+
</div>
33+
);
34+
};
35+
36+
export default MedicationEvent;

0 commit comments

Comments
 (0)