Skip to content

Commit

Permalink
(refactor) O3-2831 Move patient banner into styleguide (openmrs#1645)
Browse files Browse the repository at this point in the history
  • Loading branch information
brandones authored Mar 15, 2024
1 parent 50422a1 commit 4f5807b
Show file tree
Hide file tree
Showing 16 changed files with 883 additions and 1,769 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import { render, screen } from '@testing-library/react';
import { mockPatient } from 'tools';
import { mockDeceasedPatient } from '__mocks__';
import DeceasedPatientBannerTag from './deceased-patient-tag.component';
import DeceasedPatientBannerTag from './deceased-patient-tag.extension';

describe('DeceasedPatientTag', () => {
it('does not render Deceased tag for patients who are still alive', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { formatDatetime } from '@openmrs/esm-framework';
import { useVisitOrOfflineVisit } from '@openmrs/esm-patient-common-lib';
import { mockCurrentVisit } from '__mocks__';
import { mockPatient } from 'tools';
import VisitTag from './visit-tag.component';
import VisitTag from './visit-tag.extension';

const mockUseVisitOrOfflineVisit = useVisitOrOfflineVisit as jest.Mock;

Expand Down
182 changes: 34 additions & 148 deletions packages/esm-patient-banner-app/src/banner/patient-banner.component.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,23 @@
import React, { type MouseEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import classNames from 'classnames';
import { useTranslation } from 'react-i18next';
import { Button, Tag } from '@carbon/react';
import { ChevronDown, ChevronUp, OverflowMenuVertical } from '@carbon/react/icons';
import { ExtensionSlot, age, formatDate, parseDate, useConfig, useConnectedExtensions } from '@openmrs/esm-framework';
import ContactDetails from '../contact-details/contact-details.component';
import CustomOverflowMenuComponent from '../ui-components/overflow-menu.component';
import {
PatientBannerActionsMenu,
PatientBannerContactDetails,
PatientBannerPatientInfo,
PatientBannerToggleContactDetailsButton,
PatientPhoto,
} from '@openmrs/esm-framework';
import styles from './patient-banner.scss';

interface PatientBannerProps {
patient: fhir.Patient;
patientUuid: string;
onClick?: (patientUuid: string) => void;
onTransition?: () => void;
hideActionsOverflow?: boolean;
}

const PatientBanner: React.FC<PatientBannerProps> = ({
patient,
patientUuid,
onClick,
onTransition,
hideActionsOverflow,
}) => {
const { t } = useTranslation();
const overflowMenuRef = useRef(null);
const PatientBanner: React.FC<PatientBannerProps> = ({ patient, patientUuid, hideActionsOverflow }) => {
const patientBannerRef = useRef(null);
const [isTabletViewport, setIsTabletViewport] = useState(false);
const { excludePatientIdentifierCodeTypes } = useConfig();
const patientActions = useConnectedExtensions('patient-actions-slot');

useEffect(() => {
const currentRef = patientBannerRef.current;
Expand All @@ -45,13 +34,7 @@ const PatientBanner: React.FC<PatientBannerProps> = ({
};
}, [patientBannerRef, setIsTabletViewport]);

const patientActionsSlotState = useMemo(
() => ({ patientUuid, onClick, onTransition }),
[patientUuid, onClick, onTransition],
);

const patientName = `${patient?.name?.[0]?.given?.join(' ')} ${patient?.name?.[0].family}`;
const patientPhotoSlotState = useMemo(() => ({ patientUuid, patientName }), [patientUuid, patientName]);

const [showContactDetails, setShowContactDetails] = useState(false);
const toggleContactDetails = useCallback(() => {
Expand All @@ -60,51 +43,6 @@ const PatientBanner: React.FC<PatientBannerProps> = ({

const isDeceased = Boolean(patient?.deceasedDateTime);

const patientAvatar = (
<div className={styles.patientAvatar} role="img">
<ExtensionSlot name="patient-photo-slot" state={patientPhotoSlotState} />
</div>
);

const handleNavigateToPatientChart = useCallback(
(event: MouseEvent) => {
if (onClick) {
!(overflowMenuRef?.current && overflowMenuRef?.current.contains(event.target)) && onClick(patientUuid);
}
},
[onClick, patientUuid],
);

const [showDropdown, setShowDropdown] = useState(false);
const closeDropdownMenu = useCallback(() => {
setShowDropdown((value) => !value);
}, []);

const showActionsMenu = useMemo(
() => !hideActionsOverflow && patientActions.length > 0,
[patientActions.length, hideActionsOverflow],
);

const getGender = (gender: string): string => {
switch (gender) {
case 'male':
return t('male', 'Male');
case 'female':
return t('female', 'Female');
case 'other':
return t('other', 'Other');
case 'unknown':
return t('unknown', 'Unknown');
default:
return gender;
}
};

const identifiers =
patient?.identifier?.filter(
(identifier) => !excludePatientIdentifierCodeTypes?.uuids.includes(identifier.type.coding[0].code),
) ?? [];

return (
<header
className={classNames(
Expand All @@ -113,86 +51,34 @@ const PatientBanner: React.FC<PatientBannerProps> = ({
)}
ref={patientBannerRef}
>
<div
className={classNames(styles.patientBanner, { [styles.patientAvatarButton]: onClick })}
onClick={handleNavigateToPatientChart}
role="button"
tabIndex={0}
>
{patientAvatar}
<div className={styles.patientInfo}>
<div className={classNames(styles.row, styles.patientNameRow)}>
<div className={styles.flexRow}>
<span className={styles.patientName}>{patientName}</span>
<ExtensionSlot
name="patient-banner-tags-slot"
state={{ patientUuid, patient }}
className={styles.flexRow}
/>
</div>
{showActionsMenu && (
<div className={styles.overflowMenuContainer} ref={overflowMenuRef}>
<CustomOverflowMenuComponent
deceased={isDeceased}
menuTitle={
<>
<span className={styles.actionsButtonText}>{t('actions', 'Actions')}</span>{' '}
<OverflowMenuVertical size={16} style={{ marginLeft: '0.5rem', fill: '#78A9FF' }} />
</>
}
dropDownMenu={showDropdown}
>
<ExtensionSlot
onClick={closeDropdownMenu}
name="patient-actions-slot"
key="patient-actions-slot"
className={styles.overflowMenuItemList}
state={patientActionsSlotState}
/>
</CustomOverflowMenuComponent>
</div>
)}
</div>
<div className={styles.demographics}>
<span>{getGender(patient?.gender)}</span> &middot; <span>{age(patient?.birthDate)}</span> &middot;{' '}
<span>{formatDate(parseDate(patient?.birthDate), { mode: 'wide', time: false })}</span>
</div>
<div className={styles.row}>
<div className={styles.identifiers}>
{identifiers?.length
? identifiers.map(({ value, type }) => (
<span key={value} className={styles.identifierTag}>
<Tag className={styles.tag} type="gray" title={type.text}>
{type.text}
</Tag>
{value}
</span>
))
: ''}
</div>
<Button
className={styles.toggleContactDetailsButton}
kind="ghost"
renderIcon={(props) =>
showContactDetails ? <ChevronUp size={16} {...props} /> : <ChevronDown size={16} {...props} />
}
iconDescription="Toggle contact details"
onClick={toggleContactDetails}
style={{ marginTop: '-0.25rem' }}
>
{showContactDetails ? t('hideDetails', 'Hide details') : t('showDetails', 'Show details')}
</Button>
</div>
<div className={styles.patientBanner}>
<div className={styles.patientAvatar} role="img">
<PatientPhoto patientUuid={patientUuid} patientName={patientName} />
</div>
<PatientBannerPatientInfo patient={patient} />
<div className={styles.buttonCol}>
{!hideActionsOverflow ? (
<PatientBannerActionsMenu
patientUuid={patientUuid}
actionsSlotName={'patient-actions-slot'}
isDeceased={patient.deceasedBoolean}
/>
) : null}
<PatientBannerToggleContactDetailsButton
className={styles.toggleContactDetailsButton}
toggleContactDetails={toggleContactDetails}
showContactDetails={showContactDetails}
/>
</div>
</div>
{showContactDetails && (
<ContactDetails
isTabletViewport={isTabletViewport}
address={patient?.address ?? []}
telecom={patient?.telecom ?? []}
patientId={patient?.id}
deceased={isDeceased}
/>
<div
className={`${styles.contactDetails} ${styles[patient.deceasedBoolean && 'deceasedContactDetails']} ${
styles[isTabletViewport && 'tabletContactDetails']
}`}
>
<PatientBannerContactDetails patientId={patient?.id} deceased={isDeceased} />
</div>
)}
</header>
);
Expand Down
114 changes: 30 additions & 84 deletions packages/esm-patient-banner-app/src/banner/patient-banner.scss
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@
background-color: $ui-01;
}

.buttonCol {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: flex-end;
}

.deceasedPatientContainer {
background-color: colors.$gray-80;

.patientName {
color: $ui-01;
}

.demographics, .row, .identifierTag, .identifier, .contactDetails .heading {
color: $ui-02;
}
Expand All @@ -32,19 +35,10 @@
}
}

.overflowMenuContainer {
margin: -0.75rem 0;
}

.patientBanner {
display: flex;
}

.patientName {
@include type.type-style("heading-03");
margin-right: 0.25rem;
}

.patientAvatar {
width: 5rem;
height: 5rem;
Expand All @@ -59,88 +53,40 @@
background: none;
}

.patientInfo {
width: 100%;
}

.demographics {
@include type.type-style("body-compact-02");
.contactDetails {
color: $text-02;
margin-top: 0.375rem;
}

.row {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: baseline;
}

.patientNameRow {
margin-top: 0.875rem;
}

.flexRow {
display: flex;
flex-flow: row wrap;
align-items: center;
}

.identifiers {
@include type.type-style("body-compact-02");
color: $ui-04;
display: flex;
flex-wrap: wrap;
}

.identifierTag {
width: 100%;
border-top: 1px solid $ui-03;
display: flex;
align-items: center;
margin-right: 0.75rem;

a {
@include type.type-style("body-compact-01");
text-decoration: none;
}
}

.tag {
margin: 0.25rem 0.25rem 0.25rem 0rem;
}

.tooltipPadding {
padding: 0.25rem;
}
.deceasedContactDetails {
.contactDetails, .heading, .row, .row > .col {
color: $ui-02;
}

.tooltipSmallText {
font-size: 80%;
a {
color: $inverse-link
}
}

.actionsButtonText {
@include type.type-style("body-compact-01");
color: $interactive-01;
}
.tabletContactDetails {
display: block;

// Overriding styles for RTL support
html[dir='rtl'] {
.overflowMenuContainer {
& > div {
margin-left: unset;
margin-right: -1.5rem;
& > div {
left: unset;
right: -6.025rem;
}
.row {
&:first-child {
border-bottom: 1px solid $ui-03;
}
}

.demographics {
display: flex;
gap: 0.25rem;
}

.tag {
margin: 0.25rem 0rem 0.25rem 0.25rem;
}

.patientName {
margin-right: unset;
margin-left: 0.25rem;
.row > .col {
&:nth-of-type(2n + 1) {
border-right: 1px solid $ui-03;
}
}
}
Loading

0 comments on commit 4f5807b

Please sign in to comment.