diff --git a/packages/esm-patient-chart-app/src/dashboard.meta.ts b/packages/esm-patient-chart-app/src/dashboard.meta.ts index dc827279a1..06fee43c45 100644 --- a/packages/esm-patient-chart-app/src/dashboard.meta.ts +++ b/packages/esm-patient-chart-app/src/dashboard.meta.ts @@ -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', +}; diff --git a/packages/esm-patient-chart-app/src/index.ts b/packages/esm-patient-chart-app/src/index.ts index d2831af5af..bf364f6b49 100644 --- a/packages/esm-patient-chart-app/src/index.ts +++ b/packages/esm-patient-chart-app/src/index.ts @@ -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'; @@ -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. @@ -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 }); @@ -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, +}); diff --git a/packages/esm-patient-chart-app/src/routes.json b/packages/esm-patient-chart-app/src/routes.json index 3bcd6bc778..8e0ce7a3e0 100644 --- a/packages/esm-patient-chart-app/src/routes.json +++ b/packages/esm-patient-chart-app/src/routes.json @@ -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": [ diff --git a/packages/esm-patient-chart-app/src/visit/visits-widget/active-visits-summary.component.tsx b/packages/esm-patient-chart-app/src/visit/visits-widget/active-visits-summary.component.tsx new file mode 100644 index 0000000000..2de069bf0b --- /dev/null +++ b/packages/esm-patient-chart-app/src/visit/visits-widget/active-visits-summary.component.tsx @@ -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 ( + + ); + } + + if (error) { + return ; + } + + return ( +
+ + + {activeVisits?.map((visit, index) => ( + + {t('activeVisitType', '{{visitType}} Visit', { visitType: visit?.visitType?.name })} + + ))} + + + {activeVisits?.map((visit, index) => ( + + {visit && ( +
+
+
+
+

{visit.visitType.name}

+
+
{t('start', 'Start')}:
+ {formatDatetime(parseDate(visit.startDatetime))} + {visit.stopDatetime ? ( + <> +
{t('end', 'End')}:
+ {formatDatetime(parseDate(visit.stopDatetime))} + + ) : null} +
+
+
+ +
+
+
+ +
+ )} +
+ ))} +
+
+
+ ); +} + +export default ActiveVisitDetailOverviewComponent; diff --git a/packages/esm-patient-chart-app/src/visit/visits-widget/current-visit-summary.test.tsx b/packages/esm-patient-chart-app/src/visit/visits-widget/current-visit-summary.test.tsx index ee15bddb87..b8833c048d 100644 --- a/packages/esm-patient-chart-app/src/visit/visits-widget/current-visit-summary.test.tsx +++ b/packages/esm-patient-chart-app/src/visit/visits-widget/current-visit-summary.test.tsx @@ -1,11 +1,18 @@ 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; + +jest.mock('@openmrs/esm-framework', () => ({ + ...jest.requireActual('@openmrs/esm-framework/mock'), + useVisits: jest.fn(), + getConfig: jest.fn(), +})); describe('CurrentVisitSummary', () => { test('renders an empty state when there is no active visit', () => { @@ -18,6 +25,7 @@ describe('CurrentVisitSummary', () => { isValidating: false, mutate: jest.fn(), }); + mockedUseFeatureFlag.mockReturnValueOnce(false); render(); expect(screen.getByText(/current visit/i)).toBeInTheDocument(); @@ -26,6 +34,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: { diff --git a/packages/esm-patient-chart-app/src/visit/visits-widget/past-visits-components/visit-summary.component.tsx b/packages/esm-patient-chart-app/src/visit/visits-widget/past-visits-components/visit-summary.component.tsx index 8489747d4a..f1b7b051cd 100644 --- a/packages/esm-patient-chart-app/src/visit/visits-widget/past-visits-components/visit-summary.component.tsx +++ b/packages/esm-patient-chart-app/src/visit/visits-widget/past-visits-components/visit-summary.component.tsx @@ -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'; @@ -12,6 +12,7 @@ import { useConnectedExtensions, useLayoutType, type Visit, + useFeatureFlag, } from '@openmrs/esm-framework'; import { type Order, @@ -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; @@ -112,6 +113,15 @@ const VisitSummary: React.FC = ({ 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 (

{t('diagnoses', 'Diagnoses')}

@@ -128,7 +138,11 @@ const VisitSummary: React.FC = ({ visit, patientUuid }) => {

)}
- + = ({ visit, patientUuid }) => { > {t('medications', 'Medications')} - - {t('encounters_title', 'Encounters')} - + {!isactiveVisitSummaryTabEnabled ? ( + + {t('encounters_title', 'Encounters')} + + ) : ( + visit?.encounters?.length > 0 && + visit?.encounters + .filter((enc) => !!enc.form) + .map((enc, ind) => ( + + {enc?.form?.name ? enc?.form?.name : enc?.form?.display} + + )) + )} {extensions.map((extension, index) => ( {t(extension.meta.title, { @@ -173,9 +198,31 @@ const VisitSummary: React.FC = ({ visit, patientUuid }) => { - - - + {!isactiveVisitSummaryTabEnabled ? ( + + + + ) : ( + visit?.encounters?.length > 0 && + visit?.encounters + .filter((enc) => !!enc.form) + .map((enc, ind) => ( + + {selectedTab === enc.uuid && ( + {}, + }} + /> + )} + + )) + )} diff --git a/packages/esm-patient-chart-app/src/visit/visits-widget/visit-detail-overview.test.tsx b/packages/esm-patient-chart-app/src/visit/visits-widget/visit-detail-overview.test.tsx index d89434fb9b..8fc31f140b 100644 --- a/packages/esm-patient-chart-app/src/visit/visits-widget/visit-detail-overview.test.tsx +++ b/packages/esm-patient-chart-app/src/visit/visits-widget/visit-detail-overview.test.tsx @@ -1,7 +1,7 @@ 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__'; @@ -10,6 +10,13 @@ import VisitDetailOverview from './visit-detail-overview.component'; const mockGetConfig = getConfig as jest.Mock; const mockOpenmrsFetch = openmrsFetch as jest.Mock; const mockUseConfig = jest.mocked(useConfig); +const mockedUseFeatureFlag = useFeatureFlag as jest.Mock; + +jest.mock('@openmrs/esm-framework', () => ({ + ...jest.requireActual('@openmrs/esm-framework'), + getVisitsForPatient: jest.fn(), + userHasAccess: jest.fn().mockImplementation((privilege, _) => (privilege ? false : true)), +})); mockUseConfig.mockReturnValue({ ...getDefaultsFromConfigSchema(esmPatientChartSchema), @@ -60,6 +67,7 @@ describe('VisitDetailOverview', () => { ...getDefaultsFromConfigSchema(esmPatientChartSchema), showAllEncountersTab: true, }); + mockedUseFeatureFlag.mockReturnValueOnce(false); renderWithSwr(); @@ -96,6 +104,7 @@ describe('VisitDetailOverview', () => { ...getDefaultsFromConfigSchema(esmPatientChartSchema), showAllEncountersTab: false, }); + mockedUseFeatureFlag.mockReturnValueOnce(false); renderWithSwr(); diff --git a/packages/esm-patient-chart-app/translations/en.json b/packages/esm-patient-chart-app/translations/en.json index c17f4b4a89..86810e6106 100644 --- a/packages/esm-patient-chart-app/translations/en.json +++ b/packages/esm-patient-chart-app/translations/en.json @@ -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.", @@ -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",