Skip to content

Commit

Permalink
(feat) O3-3246 - Ward App - open patient record in workspace (openmrs…
Browse files Browse the repository at this point in the history
  • Loading branch information
brandones authored Jul 9, 2024
1 parent 199906e commit 20499f9
Show file tree
Hide file tree
Showing 11 changed files with 278 additions and 117 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import dayjs from 'dayjs';
import AppointmentTabs from './appointments/appointment-tabs.component';
import AppointmentsHeader from './header/appointments-header.component';
import AppointmentMetrics from './metrics/appointments-metrics.component';
import { WorkspaceOverlay } from '@openmrs/esm-framework';
import { useParams } from 'react-router-dom';
import SelectedDateContext from './hooks/selectedDateContext';
import { omrsDateFormat } from './constants';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { usePatient, useLayoutType, isDesktop, WorkspaceOverlay } from '@openmrs/esm-framework';
import { usePatient, useLayoutType, isDesktop, WorkspaceContainer } from '@openmrs/esm-framework';
import PatientAppointmentsBase from './patient-appointments-base.component';
import { useParams } from 'react-router-dom';
import PatientAppointmentContext, { PatientAppointmentContextTypes } from '../hooks/patientAppointmentContext';
Expand All @@ -26,7 +26,7 @@ const PatientAppointmentsOverview: React.FC = () => {
<div className={styles.patientAppointmentsOverview}>
<PatientAppointmentsHeader patient={response.patient} />
<PatientAppointmentsBase patientUuid={response.patient.id} />
<WorkspaceOverlay contextKey={`patient/${params.patientUuid}`} />
<WorkspaceContainer overlay contextKey={`patient/${params.patientUuid}`} />
</div>
</PatientAppointmentContext.Provider>
);
Expand Down
5 changes: 5 additions & 0 deletions packages/esm-ward-app/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
import { configSchema } from './config-schema';
import rootComponent from './root.component';
import { moduleName } from './constant';
import WardPatientActionButton from './ward-patient-workspace/ward-patient-action-button.extension';

export const importTranslation = require.context('../translations', false, /.json$/, 'lazy');

Expand All @@ -23,6 +24,10 @@ export const admissionRequestWorkspace = getAsyncLifecycle(
options,
);

export const wardPatientWorkspace = getAsyncLifecycle(() => import('./ward-patient-workspace/ward-patient.workspace'), options);

export const wardPatientActionButtonExtension = getSyncLifecycle(WardPatientActionButton, options);

export function startupApp() {
registerBreadcrumbs([]);
defineConfigSchema(moduleName, configSchema);
Expand Down
29 changes: 21 additions & 8 deletions packages/esm-ward-app/src/routes.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,31 @@
}
}
},
"workspaces": [
"pages": [
{
"component": "root",
"route": "ward"
}
],
"extensions": [{
"component": "wardPatientActionButtonExtension",
"name": "ward-patient-action-button",
"slot": "action-menu-ward-patient-items-slot"
}],
"workspaces": [
{
"name":"admission-requests-workspace",
"component": "admissionRequestWorkspace",
"title":"admissionRequests",
"type":"admission-requests"
}
],
"pages": [
},
{
"component": "root",
"route": "ward"
}
]
"name": "ward-patient-workspace",
"component": "wardPatientWorkspace",
"type": "ward",
"title": "Ward Patient",
"width": "extra-wide",
"hasOwnSidebar": true,
"sidebarFamily": "ward-patient"
}]
}
26 changes: 26 additions & 0 deletions packages/esm-ward-app/src/ward-patient-card/ward-patient-card.scss
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,37 @@
gap: spacing.$spacing-02;
background-color: $ui-02;

position: relative; // this allows positioning the button correctly

> .wardPatientCardRow:not(:first-child) {
border-top: 1px colors.$gray-20 solid;
}
}

.wardPatientCardButton {
border: none;
padding: 0;

&::before {
content: '';
position: absolute;
inset: 0;
z-index: 1;
cursor: pointer;
border: 2px solid transparent;
transition: border-color 200ms;
}

&:hover::before,
&:focus::before {
border-color: $interactive-01;
}

&:focus {
outline: none;
}
}

.wardPatientCardRow {
width: 100%;
padding: spacing.$spacing-04;
Expand Down
10 changes: 10 additions & 0 deletions packages/esm-ward-app/src/ward-patient-card/ward-patient-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { useParams } from 'react-router-dom';
import { type WardPatientCardProps } from '../types';
import { usePatientCardRows } from './ward-patient-card-row.resources';
import styles from './ward-patient-card.scss';
import { getPatientName, launchWorkspace } from '@openmrs/esm-framework';
import { type WardPatientWorkspaceProps } from '../ward-patient-workspace/ward-patient.workspace';

const WardPatientCard: React.FC<WardPatientCardProps> = (props) => {
const { locationUuid } = useParams();
Expand All @@ -13,6 +15,14 @@ const WardPatientCard: React.FC<WardPatientCardProps> = (props) => {
{patientCardRows.map((WardPatientCardRow, i) => (
<WardPatientCardRow key={i} {...props} />
))}
<button
className={styles.wardPatientCardButton}
onClick={() =>
launchWorkspace<WardPatientWorkspaceProps>('ward-patient-workspace', { patientUuid: props.patient.uuid })
}>
{/* Name will not be displayed; just there for a11y */}
{getPatientName(props.patient.person)}
</button>
</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { UserAvatarIcon } from '@openmrs/esm-framework';
import { ActionMenuButton, launchWorkspace } from '@openmrs/esm-framework';

export default function WardPatientActionButton() {
const { t } = useTranslation();

return (
<ActionMenuButton
getIcon={(props) => <UserAvatarIcon {...props} />}
label={t('Patient', 'patient')}
iconDescription={t('Patient', 'patient')}
handler={() => launchWorkspace('ward-patient-workspace')}
type={'ward'}
/>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@use '@carbon/styles/scss/spacing';
@use '@carbon/styles/scss/type';

.workspaceContainer {
min-height: var(--desktop-workspace-window-height);
}

.headerPatientDetail {
@include type.type-style('body-compact-02');
margin: 0 spacing.$spacing-02;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React, { useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { InlineNotification } from '@carbon/react';
import { InlineLoading } from '@carbon/react';
import {
type DefaultWorkspaceProps,
ExtensionSlot,
attach,
getPatientName,
usePatient,
age,
} from '@openmrs/esm-framework';
import styles from './ward-patient.style.scss';

attach('ward-patient-workspace-header-slot', 'patient-vitals-info');

export interface WardPatientWorkspaceProps extends DefaultWorkspaceProps {
patientUuid: string;
}

export default function WardPatientWorkspace({ patientUuid, setTitle }: WardPatientWorkspaceProps) {
const { t } = useTranslation();
const { patient, isLoading, error } = usePatient(patientUuid);

useEffect(() => {
if (isLoading) {
setTitle(t('wardPatientWorkspaceTitle', 'Ward Patient'), <InlineLoading />);
} else if (patient) {
setTitle(getPatientName(patient), <PatientWorkspaceTitle patient={patient} />);
} else if (error) {
setTitle(t('wardPatientWorkspaceTitle', 'Ward Patient'));
}
}, [patient]);

return (
<div className={styles.workspaceContainer}>
{isLoading ? (
<InlineLoading />
) : patient ? (
<WardPatientWorkspaceView patient={patient} />
) : error ? (
<InlineNotification>{error.message}</InlineNotification>
) : (
<InlineNotification>
{t('failedToLoadPatientWorkspace', 'Ward patient workspace has failed to load.')}
</InlineNotification>
)}
</div>
);
}

interface WardPatientWorkspaceViewProps {
patient: fhir.Patient;
}

function WardPatientWorkspaceView({ patient }: WardPatientWorkspaceViewProps) {
const extensionSlotState = useMemo(() => ({ patient, patientUuid: patient.id }), [patient]);

return (
<>
<div>
<ExtensionSlot name="ward-patient-workspace-header-slot" state={extensionSlotState} />
</div>
<div>
<ExtensionSlot name="ward-patient-workspace-content-slot" state={extensionSlotState} />
</div>
</>
);
}

function PatientWorkspaceTitle({ patient }: { patient: fhir.Patient }) {
return (
<>
<div>{getPatientName(patient)} &nbsp;</div>
<div className={styles.headerPatientDetail}>&middot; &nbsp; {patient.gender}</div>
<div className={styles.headerPatientDetail}>&middot; &nbsp; {age(patient.birthDate)}</div>
</>
);
}
6 changes: 3 additions & 3 deletions packages/esm-ward-app/src/ward-view/ward-view.component.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { InlineNotification } from '@carbon/react';
import { WorkspaceContainer, useFeatureFlag, useLocations, useSession, type Location } from '@openmrs/esm-framework';
import React, { useMemo } from 'react';
import { InlineNotification } from '@carbon/react';
import { useTranslation } from 'react-i18next';
import { WorkspaceContainer, useFeatureFlag } from '@openmrs/esm-framework';
import EmptyBedSkeleton from '../beds/empty-bed-skeleton';
import { useAdmissionLocation } from '../hooks/useAdmissionLocation';
import WardBed from './ward-bed.component';
Expand Down Expand Up @@ -34,7 +34,7 @@ const WardView = () => {
<div className={styles.wardViewMain}>
<WardViewByLocation />
</div>
<WorkspaceContainer contextKey="ward" />
<WorkspaceContainer overlay contextKey="ward" />
</div>
);
};
Expand Down
Loading

0 comments on commit 20499f9

Please sign in to comment.