Skip to content

Commit

Permalink
O3-2857 Add configurable current visit tab in visits widget
Browse files Browse the repository at this point in the history
  • Loading branch information
CynthiaKamau committed Sep 5, 2024
1 parent 5e7a458 commit 939ce19
Show file tree
Hide file tree
Showing 8 changed files with 209 additions and 18 deletions.
7 changes: 7 additions & 0 deletions packages/esm-patient-chart-app/src/dashboard.meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,10 @@ export const encountersDashboardMeta = {
path: 'Visits',
title: 'Visits',
};

export const activeVisitDashboardMeta = {
slot: 'patient-chart-active-visit-dashboard-slot',
columns: 1,
path: 'Active Visit',
title: 'Active Visit',
};
25 changes: 22 additions & 3 deletions packages/esm-patient-chart-app/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import {
import * as PatientCommonLib from '@openmrs/esm-patient-common-lib';
import { createDashboardLink } from '@openmrs/esm-patient-common-lib';
import { esmPatientChartSchema } from './config-schema';
import { moduleName, spaBasePath } from './constants';
import { summaryDashboardMeta, encountersDashboardMeta, activeVisitDashboardMeta } from './dashboard.meta';
import { setupOfflineVisitsSync, setupCacheableRoutes } from './offline';
import { genericDashboardConfigSchema } from './side-nav/generic-dashboard.component';
import { genericNavGroupConfigSchema } from './side-nav/generic-nav-group.component';
import { moduleName } from './constants';
import { setupOfflineVisitsSync, setupCacheableRoutes } from './offline';
import { summaryDashboardMeta, encountersDashboardMeta } from './dashboard.meta';
import addPastVisitActionButtonComponent from './actions-buttons/add-past-visit.component';
import cancelVisitActionButtonComponent from './actions-buttons/cancel-visit.component';
import currentVisitSummaryComponent from './visit/visits-widget/current-visit-summary.component';
Expand All @@ -30,6 +30,7 @@ import startVisitActionButtonOnPatientSearch from './visit/start-visit-button.co
import startVisitFormComponent from './visit/visit-form/visit-form.component';
import stopVisitActionButtonComponent from './actions-buttons/stop-visit.component';
import visitAttributeTagsComponent from './patient-banner-tags/visit-attribute-tags.component';
import activeVisitDetailOverviewComponent from './visit/visits-widget/current-visit-summary.component';

// This allows @openmrs/esm-patient-common-lib to be accessed by modules that are not
// using webpack. This is used for ngx-formentry.
Expand All @@ -55,6 +56,11 @@ export function startupApp() {
'Print patient identifier sticker',
'Features to support printing a patient identifier sticker',
);
registerFeatureFlag(
'activeVisitSummaryTab',
'Active Visit Summary Tab',
'This feature displays a summary of all forms filled in an encounter instead of displaying the encounters tab.',
);
}

export const root = getSyncLifecycle(patientChartPageComponent, { featureName: 'patient-chart', moduleName });
Expand Down Expand Up @@ -238,3 +244,16 @@ export const activeVisitActionsComponent = getAsyncLifecycle(
() => import('./visit/visits-widget/active-visit-buttons/active-visit-buttons'),
{ featureName: 'active-visit-actions', moduleName },
);

export const activeVisitSummaryDashboardLink = getSyncLifecycle(
createDashboardLink({
...activeVisitDashboardMeta,
moduleName,
}),
{ featureName: 'activeVisitSummaryTab', moduleName },
);

export const activeVisitDetailOverview = getSyncLifecycle(activeVisitDetailOverviewComponent, {
featureName: 'active-visit-overview',
moduleName,
});
24 changes: 24 additions & 0 deletions packages/esm-patient-chart-app/src/routes.json
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,30 @@
"online": true,
"offline": true,
"order": 1
},
{
"name": "active-visit-summary-dashboard",
"slot": "patient-chart-dashboard-slot",
"component": "activeVisitSummaryDashboardLink",
"featureFlag": "activeVisitSummaryTab",
"meta": {
"slot": "patient-chart-active-visit-dashboard-slot",
"columns": 1,
"path": "Active Visit"
},
"order": 12,
"online": true,
"offline": true
},
{
"name": "active-visit-overview",
"component": "activeVisitDetailOverview",
"slot": "patient-chart-active-visit-dashboard-slot",
"order": 1,
"meta": {
"title": "Active Visits",
"view": "Active Visits"
}
}
],
"modals": [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import React from 'react';
import { InlineLoading, Tab, Tabs, TabList, TabPanel, TabPanels } from '@carbon/react';
import { EmptyState, ErrorState } from '@openmrs/esm-patient-common-lib';
import { ExtensionSlot, formatDatetime, parseDate } from '@openmrs/esm-framework';
import { useTranslation } from 'react-i18next';
import { useVisits } from './visit.resource';
import VisitSummary from './past-visits-components/visit-summary.component';
import styles from './visit-detail-overview.scss';

interface ActiveVisitOverviewComponentProps {
patientUuid: string;
}

function ActiveVisitDetailOverviewComponent({ patientUuid }: ActiveVisitOverviewComponentProps) {
const { t } = useTranslation();
const { visits, error, isLoading, mutateVisits } = useVisits(patientUuid);

const activeVisits = visits?.filter((visit) => visit.stopDatetime === null);

if (isLoading) {
return (
<InlineLoading
status="active"
iconDescription={t('loading', 'Loading')}
description={t('loadingVisit', 'Loading active visits...')}
/>
);
}

if (error) {
return <ErrorState headerTitle={t('failedToLoadActiveVisits', 'Failed loading active visits')} error={error} />;
}

return (
<div className={styles.tabs}>
<Tabs>
<TabList aria-label="Active visit tabs" contained>
{activeVisits?.map((visit, index) => (
<Tab label={visit?.visitType?.name} key={index} id={`${index}-id}`}>
{t('activeVisitType', '{{visitType}} Visit', { visitType: visit?.visitType?.name })}
</Tab>
))}
</TabList>
<TabPanels>
{activeVisits?.map((visit, index) => (
<TabPanel key={index}>
{visit && (
<div className={styles.container}>
<div className={styles.header}>
<div className={styles.visitInfo}>
<div>
<h4 className={styles.visitType}>{visit.visitType.name}</h4>
<div className={styles.displayFlex}>
<h6 className={styles.dateLabel}>{t('start', 'Start')}:</h6>
<span className={styles.date}>{formatDatetime(parseDate(visit.startDatetime))}</span>
{visit.stopDatetime ? (
<>
<h6 className={styles.dateLabel}>{t('end', 'End')}:</h6>
<span className={styles.date}>{formatDatetime(parseDate(visit.stopDatetime))}</span>
</>
) : null}
</div>
</div>
<div>
<ExtensionSlot name="active-visit-actions" />
</div>
</div>
</div>
<VisitSummary visit={visit} patientUuid={patientUuid} />
</div>
)}
</TabPanel>
))}
</TabPanels>
</Tabs>
</div>
);
}

export default ActiveVisitDetailOverviewComponent;
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import { useVisit, getConfig } from '@openmrs/esm-framework';
import { useVisit, getConfig, useFeatureFlag } from '@openmrs/esm-framework';
import { waitForLoadingToFinish } from 'tools';
import CurrentVisitSummary from './current-visit-summary.component';

const mockGetConfig = jest.mocked(getConfig);
const mockUseVisits = jest.mocked(useVisit);
const mockedUseFeatureFlag = useFeatureFlag as jest.Mock;

describe('CurrentVisitSummary', () => {
test('renders an empty state when there is no active visit', () => {
Expand All @@ -18,6 +19,7 @@ describe('CurrentVisitSummary', () => {
isValidating: false,
mutate: jest.fn(),
});
mockedUseFeatureFlag.mockReturnValueOnce(false);

render(<CurrentVisitSummary patientUuid="some-uuid" />);
expect(screen.getByText(/current visit/i)).toBeInTheDocument();
Expand All @@ -26,6 +28,7 @@ describe('CurrentVisitSummary', () => {

test('renders a visit summary when for the active visit', async () => {
mockGetConfig.mockResolvedValue({ htmlFormEntryForms: [] });
mockedUseFeatureFlag.mockReturnValueOnce(false);
mockUseVisits.mockReturnValueOnce({
activeVisit: null,
currentVisit: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useMemo } from 'react';
import React, { useMemo, useState } from 'react';
import classNames from 'classnames';
import { useTranslation } from 'react-i18next';
import { Tab, Tabs, TabList, TabPanel, TabPanels, Tag } from '@carbon/react';
Expand All @@ -12,6 +12,7 @@ import {
useConnectedExtensions,
useLayoutType,
type Visit,
useFeatureFlag,
} from '@openmrs/esm-framework';
import {
type Order,
Expand All @@ -22,12 +23,12 @@ import {
type Diagnosis,
mapEncounters,
} from '../visit.resource';
import VisitsTable from './visits-table/visits-table.component';
import MedicationSummary from './medications-summary.component';
import NotesSummary from './notes-summary.component';
import TestsSummary from './tests-summary.component';
import type { ExternalOverviewProps } from '@openmrs/esm-patient-common-lib';
import styles from './visit-summary.scss';
import VisitsTable from './visits-table';

interface DiagnosisItem {
diagnosis: string;
Expand Down Expand Up @@ -112,6 +113,15 @@ const VisitSummary: React.FC<VisitSummaryProps> = ({ visit, patientUuid }) => {
};
}, [visit?.encounters]);

const isactiveVisitSummaryTabEnabled = useFeatureFlag('activeVisitSummaryTab');
const [selectedIndex, setSelectedIndex] = useState(0);
const [selectedTab, setSelectedTab] = useState(null);

const handleTabChange = (evt) => {
setSelectedTab(visit.encounters[evt.selectedIndex - 3]?.uuid || ''); // Assuming the first 3 tabs are predefined
setSelectedIndex(evt.selectedIndex);
};

return (
<div className={styles.summaryContainer}>
<p className={styles.diagnosisLabel}>{t('diagnoses', 'Diagnoses')}</p>
Expand All @@ -128,7 +138,11 @@ const VisitSummary: React.FC<VisitSummaryProps> = ({ visit, patientUuid }) => {
</p>
)}
</div>
<Tabs className={classNames(styles.verticalTabs, layout === 'tablet' ? styles.tabletTabs : styles.desktopTabs)}>
<Tabs
onChange={handleTabChange}
selected={selectedIndex}
className={classNames(styles.verticalTabs, layout === 'tablet' ? styles.tabletTabs : styles.desktopTabs)}
>
<TabList aria-label="Visit summary tabs" className={styles.tablist}>
<Tab
className={classNames(styles.tab, styles.bodyLong01)}
Expand All @@ -147,13 +161,24 @@ const VisitSummary: React.FC<VisitSummaryProps> = ({ visit, patientUuid }) => {
>
{t('medications', 'Medications')}
</Tab>
<Tab
className={styles.tab}
id="encounters-tab"
disabled={visit?.encounters.length <= 0 && config.disableEmptyTabs}
>
{t('encounters_title', 'Encounters')}
</Tab>
{!isactiveVisitSummaryTabEnabled ? (
<Tab
className={styles.tab}
id="encounters-tab"
disabled={visit?.encounters.length <= 0 && config.disableEmptyTabs}
>
{t('encounters_title', 'Encounters')}
</Tab>
) : (
visit?.encounters?.length > 0 &&
visit?.encounters
.filter((enc) => !!enc.form)
.map((enc, ind) => (
<Tab i id={'tab-' + ind} key={ind} className={classNames(styles.tab, styles.bodyLong01)}>
{enc?.form?.name ? enc?.form?.name : enc?.form?.display}
</Tab>
))
)}
{extensions.map((extension, index) => (
<Tab key={index} className={styles.tab} id={`${extension.meta.title || index}-tab`}>
{t(extension.meta.title, {
Expand All @@ -173,9 +198,31 @@ const VisitSummary: React.FC<VisitSummaryProps> = ({ visit, patientUuid }) => {
<TabPanel>
<MedicationSummary medications={medications} />
</TabPanel>
<TabPanel>
<VisitsTable visits={mapEncounters(visit)} showAllEncounters={false} patientUuid={patientUuid} />
</TabPanel>
{!isactiveVisitSummaryTabEnabled ? (
<TabPanel>
<VisitsTable visits={mapEncounters(visit)} showAllEncounters={false} patientUuid={patientUuid} />
</TabPanel>
) : (
visit?.encounters?.length > 0 &&
visit?.encounters
.filter((enc) => !!enc.form)
.map((enc, ind) => (
<TabPanel key={ind}>
{selectedTab === enc.uuid && (
<ExtensionSlot
name="form-widget-slot"
state={{
additionalProps: { mode: 'embedded-view' },
formUuid: enc.form?.uuid,
encounterUuid: enc.uuid,
patientUuid: patientUuid,
promptBeforeClosing: () => {},
}}
/>
)}
</TabPanel>
))
)}
<ExtensionSlot name={visitSummaryPanelSlot}>
<TabPanel>
<Extension state={{ patientUuid, visit }} />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import React from 'react';
import userEvent from '@testing-library/user-event';
import { screen } from '@testing-library/react';
import { openmrsFetch, getConfig, useConfig, getDefaultsFromConfigSchema } from '@openmrs/esm-framework';
import {
openmrsFetch,
getConfig,
useConfig,
getDefaultsFromConfigSchema,
useFeatureFlag,
} from '@openmrs/esm-framework';
import { esmPatientChartSchema, type ChartConfig } from '../../config-schema';
import { mockPatient, renderWithSwr, waitForLoadingToFinish } from 'tools';
import { visitOverviewDetailMockData } from '__mocks__';
Expand All @@ -10,6 +16,7 @@ import VisitDetailOverview from './visit-detail-overview.component';
const mockGetConfig = getConfig as jest.Mock;
const mockOpenmrsFetch = openmrsFetch as jest.Mock;
const mockUseConfig = jest.mocked(useConfig<ChartConfig>);
const mockedUseFeatureFlag = useFeatureFlag as jest.Mock;

mockUseConfig.mockReturnValue({
...getDefaultsFromConfigSchema(esmPatientChartSchema),
Expand Down Expand Up @@ -60,6 +67,7 @@ describe('VisitDetailOverview', () => {
...getDefaultsFromConfigSchema(esmPatientChartSchema),
showAllEncountersTab: true,
});
mockedUseFeatureFlag.mockReturnValueOnce(false);

renderWithSwr(<VisitDetailOverview patientUuid={mockPatient.id} />);

Expand Down Expand Up @@ -96,6 +104,7 @@ describe('VisitDetailOverview', () => {
...getDefaultsFromConfigSchema(esmPatientChartSchema),
showAllEncountersTab: false,
});
mockedUseFeatureFlag.mockReturnValueOnce(false);

renderWithSwr(<VisitDetailOverview patientUuid={mockPatient.id} />);

Expand Down
2 changes: 2 additions & 0 deletions packages/esm-patient-chart-app/translations/en.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"activeVisitType": "{{visitType}} Visit",
"addAPastVisit": "Add a past visit",
"addPastVisit": "Add past visit",
"addPastVisitText": "You can add a new past visit or update an old one. Choose from one of the options below to continue.",
Expand Down Expand Up @@ -67,6 +68,7 @@
"errorUpdatingVisitDetails": "Error updating visit details",
"errorWhenRestoringVisit": "Error occured when restoring {{visit}}",
"failedDeleting": "couldn't be deleted",
"failedToLoadActiveVisits": "Failed loading active visits",
"failedToLoadCurrentVisit": "Failed loading current visit",
"female": "Female",
"fieldRequired": "This field is required",
Expand Down

0 comments on commit 939ce19

Please sign in to comment.