Skip to content

Commit

Permalink
(fix) O3-3046: Allow sorting by date in vitals and biometrics table (#…
Browse files Browse the repository at this point in the history
…1775)

* Allowed sorting in vitals and biometrics table

* Add tests that verify expected behaviour

---------

Co-authored-by: Dennis Kigen <[email protected]>
  • Loading branch information
vasharma05 and denniskigen authored Apr 8, 2024
1 parent 6192518 commit f88e7f8
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 62 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const BiometricsBase: React.FC<BiometricsBaseProps> = ({ patientUuid, pageSize,
);

const tableHeaders = [
{ key: 'date', header: t('dateAndTime', 'Date and time') },
{ key: 'date', header: t('dateAndTime', 'Date and time'), isSortable: true },
{ key: 'weight', header: withUnit(t('weight', 'Weight'), conceptUnits.get(config.concepts.weightUuid) ?? '') },
{ key: 'height', header: withUnit(t('height', 'Height'), conceptUnits.get(config.concepts.heightUuid) ?? '') },
{ key: 'bmi', header: `${t('bmi', 'BMI')} (${bmiUnit})` },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { defineConfigSchema, getDefaultsFromConfigSchema, useConfig, usePagination } from '@openmrs/esm-framework';
import { defineConfigSchema, getDefaultsFromConfigSchema, useConfig } from '@openmrs/esm-framework';
import { formattedBiometrics, mockBiometricsConfig, mockConceptMetadata, mockConceptUnits } from '__mocks__';
import { configSchema } from '../config-schema';
import { mockPatient, patientChartBasePath, renderWithSwr, waitForLoadingToFinish } from 'tools';
Expand All @@ -11,7 +11,6 @@ import BiometricsOverview from './biometrics-overview.component';
defineConfigSchema('@openmrs/esm-patient-vitals-app', configSchema);
const mockedUseConfig = jest.mocked(useConfig);
const mockedUseVitalsAndBiometrics = jest.mocked(useVitalsAndBiometrics);
const mockUsePagination = jest.mocked(usePagination);

jest.mock('../common', () => {
const originalModule = jest.requireActual('../common');
Expand Down Expand Up @@ -76,22 +75,12 @@ describe('BiometricsOverview: ', () => {
});

it("renders a tabular overview of the patient's biometrics data when available", async () => {
const user = userEvent.setup();

mockedUseVitalsAndBiometrics.mockReturnValue({
data: formattedBiometrics,
} as ReturnType<typeof useVitalsAndBiometrics>);

mockUsePagination.mockReturnValue({
currentPage: 1,
goTo: () => {},
results: formattedBiometrics.slice(0, 5),
totalPages: formattedBiometrics.length / 5,
paginated: true,
showNextButton: true,
showPreviousButton: false,
goToNext: () => {},
goToPrevious: () => {},
});

renderBiometricsOverview();

await waitForLoadingToFinish();
Expand All @@ -102,13 +91,31 @@ describe('BiometricsOverview: ', () => {
expect(screen.getByRole('tab', { name: /chart view/i })).toBeInTheDocument();
expect(screen.getByRole('link', { name: /see all/i })).toBeInTheDocument();

const initialRowElements = screen.getAllByRole('row');

const expectedColumnHeaders = [/date/, /weight/, /height/, /bmi/, /muac/];
expectedColumnHeaders.map((header) =>
expect(screen.getByRole('columnheader', { name: new RegExp(header, 'i') })).toBeInTheDocument(),
);

const expectedTableRows = [/90 186 26.0 17/, /80 198 20.4 23/, /50/, /61 160 23.8/, /90 198 23.0 25/];
const expectedTableRows = [
/12 Aug 2021, 12:00 AM 90 186 26.0 17/,
/18 Jun 2021, 12:00 AM 80 198 20.4 23/,
/10 Jun 2021, 12:00 AM 50 -- -- --/,
/26 May 2021, 12:00 AM 61 160 23.8 --/,
/10 May 2021, 12:00 AM 90 198 23.0 25/,
];
expectedTableRows.map((row) => expect(screen.getByRole('row', { name: new RegExp(row, 'i') })).toBeInTheDocument());

const sortRowsButton = screen.getByRole('button', { name: /date and time/i });

await user.click(sortRowsButton);

expect(screen.getAllByRole('row')).not.toEqual(initialRowElements);

await user.click(sortRowsButton);

expect(screen.getAllByRole('row')).toEqual(initialRowElements);
});

it('toggles between rendering either a tabular view or a chart view', async () => {
Expand All @@ -118,18 +125,6 @@ describe('BiometricsOverview: ', () => {
data: formattedBiometrics.slice(0, 2),
} as ReturnType<typeof useVitalsAndBiometrics>);

mockUsePagination.mockReturnValue({
currentPage: 1,
goTo: () => {},
results: formattedBiometrics.slice(0, 5),
totalPages: formattedBiometrics.length / 5,
paginated: true,
showNextButton: true,
showPreviousButton: false,
goToNext: undefined,
goToPrevious: undefined,
});

renderBiometricsOverview();

await waitForLoadingToFinish();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useState } from 'react';
import {
DataTable,
type DataTableRow,
Expand All @@ -12,6 +12,7 @@ import {
} from '@carbon/react';
import { useLayoutType, usePagination } from '@openmrs/esm-framework';
import { PatientChartPagination } from '@openmrs/esm-patient-common-lib';
import orderBy from 'lodash/orderBy';

interface PaginatedBiometricsProps {
tableRows: Array<typeof DataTableRow>;
Expand All @@ -28,12 +29,39 @@ const PaginatedBiometrics: React.FC<PaginatedBiometricsProps> = ({
urlLabel,
tableHeaders,
}) => {
const { results: paginatedBiometrics, goTo, currentPage } = usePagination(tableRows, pageSize);
const isTablet = useLayoutType() === 'tablet';

const [sortParams, setSortParams] = useState({ key: '', order: 'NONE' });

const handleSort = (cellA, cellB, { sortDirection }) => {
setSortParams({ key: 'date', order: sortDirection });
};

const sortDate = (myArray, order) =>
order === 'ASC'
? orderBy(myArray, [(obj) => new Date(obj.encounterDate).getTime()], ['desc'])
: orderBy(myArray, [(obj) => new Date(obj.encounterDate).getTime()], ['asc']);

const { key, order } = sortParams;

const sortedData =
key === 'date'
? sortDate(tableRows, order)
: order === 'DESC'
? orderBy(tableRows, [key], ['desc'])
: orderBy(tableRows, [key], ['asc']);

const { results: paginatedBiometrics, goTo, currentPage } = usePagination(sortedData, pageSize);

return (
<div>
<DataTable rows={paginatedBiometrics} headers={tableHeaders} size={isTablet ? 'lg' : 'sm'} useZebraStyles>
<DataTable
rows={paginatedBiometrics}
headers={tableHeaders}
size={isTablet ? 'lg' : 'sm'}
useZebraStyles
sortRow={handleSort}
>
{({ rows, headers, getHeaderProps, getTableProps }) => (
<TableContainer>
<Table aria-label="biometrics" {...getTableProps()}>
Expand All @@ -43,6 +71,7 @@ const PaginatedBiometrics: React.FC<PaginatedBiometricsProps> = ({
<TableHeader
{...getHeaderProps({
header,
isSortable: header.isSortable,
})}
>
{header.header?.content ?? header.header}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ const PaginatedVitals: React.FC<PaginatedVitalsProps> = ({

const [sortParams, setSortParams] = useState({ key: '', order: 'none' });

const handleSort = (cellA, cellB, { sortDirection }) => {
setSortParams({ key: 'date', order: sortDirection });
};

const sortDate = (myArray, order) =>
order === 'ASC'
? orderBy(myArray, [(obj) => new Date(obj.encounterDate).getTime()], ['desc'])
Expand All @@ -62,7 +66,7 @@ const PaginatedVitals: React.FC<PaginatedVitalsProps> = ({
const { key, order } = sortParams;

const sortedData =
key === 'encounterDate'
key === 'date'
? sortDate(tableRows, order)
: order === 'DESC'
? orderBy(tableRows, [key], ['desc'])
Expand All @@ -74,14 +78,16 @@ const PaginatedVitals: React.FC<PaginatedVitalsProps> = ({

return (
<div>
<DataTable rows={rows} headers={tableHeaders} size={isTablet ? 'lg' : 'sm'} useZebraStyles>
{({ rows, headers, getTableProps }) => (
<DataTable rows={rows} headers={tableHeaders} size={isTablet ? 'lg' : 'sm'} useZebraStyles sortRow={handleSort}>
{({ rows, headers, getTableProps, getHeaderProps }) => (
<TableContainer>
<Table className={styles.table} aria-label="vitals" {...getTableProps()}>
<TableHead>
<TableRow>
{headers.map((header) => (
<TableHeader key={header.key}>{header.header?.content ?? header.header}</TableHeader>
<TableHeader {...getHeaderProps({ header, isSortable: header.isSortable })} key={header.key}>
{header.header?.content ?? header.header}
</TableHeader>
))}
</TableRow>
</TableHead>
Expand Down
50 changes: 23 additions & 27 deletions packages/esm-patient-vitals-app/src/vitals/vitals-overview.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import userEvent from '@testing-library/user-event';
import { screen } from '@testing-library/react';
import { defineConfigSchema, getDefaultsFromConfigSchema, useConfig, usePagination } from '@openmrs/esm-framework';
import { defineConfigSchema, getDefaultsFromConfigSchema, useConfig } from '@openmrs/esm-framework';
import { configSchema } from '../config-schema';
import { formattedVitals, mockConceptMetadata, mockConceptUnits, mockVitalsConfig } from '__mocks__';
import { mockPatient, renderWithSwr, waitForLoadingToFinish } from 'tools';
Expand All @@ -11,7 +11,6 @@ import VitalsOverview from './vitals-overview.component';
defineConfigSchema('@openmrs/esm-patient-vitals-app', configSchema);
const mockedUseConfig = jest.mocked(useConfig);
const mockedUseVitalsAndBiometrics = jest.mocked(useVitalsAndBiometrics);
const mockUsePagination = jest.mocked(usePagination);

global.ResizeObserver = jest.fn().mockImplementation(() => ({
observe: jest.fn(),
Expand Down Expand Up @@ -91,34 +90,43 @@ describe('VitalsOverview', () => {
});

it("renders a tabular overview of the patient's vital signs", async () => {
const user = userEvent.setup();

mockedUseVitalsAndBiometrics.mockReturnValue({
data: formattedVitals,
} as ReturnType<typeof useVitalsAndBiometrics>);

mockUsePagination.mockReturnValue({
currentPage: 1,
goTo: () => {},
results: formattedVitals.slice(0, 5),
totalPages: formattedVitals.length / 5,
paginated: true,
showNextButton: true,
showPreviousButton: false,
goToNext: undefined,
goToPrevious: undefined,
} as ReturnType<typeof usePagination>);

renderVitalsOverview();

await waitForLoadingToFinish();
expect(screen.getByRole('table', { name: /vitals/i })).toBeInTheDocument();

const initialRowElements = screen.getAllByRole('row');

const expectedColumnHeaders = [/date and time/, /bp/, /r. rate/, /pulse/, /spO2/, /temp/];

expectedColumnHeaders.map((header) =>
expect(screen.getByRole('columnheader', { name: new RegExp(header, 'i') })).toBeInTheDocument(),
);

const expectedTableRows = [/37 76 12/, /37 66 45 90/, /36.5 78 65/];
const expectedTableRows = [
/19 May 2021, 04:26 AM 37 121 \/ 89 76 12 --/,
/10 May 2021, 06:41 AM 37 120 \/ 90 66 45 90/,
/07 May 2021, 09:04 AM -- 120 \/ 80 -- -- --/,
/08 Apr 2021, 02:44 PM 36.5 -- \/ -- 78 65 --/,
];

expectedTableRows.map((row) => expect(screen.getByRole('row', { name: new RegExp(row, 'i') })).toBeInTheDocument());

const sortRowsButton = screen.getByRole('button', { name: /date and time/i });

await user.click(sortRowsButton);

expect(screen.getAllByRole('row')).not.toEqual(initialRowElements);

await user.click(sortRowsButton);

expect(screen.getAllByRole('row')).toEqual(initialRowElements);
});

it('toggles between rendering either a tabular view or a chart view', async () => {
Expand All @@ -128,18 +136,6 @@ describe('VitalsOverview', () => {
data: formattedVitals,
} as ReturnType<typeof useVitalsAndBiometrics>);

mockUsePagination.mockReturnValue({
currentPage: 1,
goTo: () => {},
results: formattedVitals.slice(0, 5),
totalPages: formattedVitals.length / 5,
paginated: true,
showNextButton: true,
showPreviousButton: false,
goToNext: undefined,
goToPrevious: undefined,
} as ReturnType<typeof usePagination>);

renderVitalsOverview();

await waitForLoadingToFinish();
Expand Down

0 comments on commit f88e7f8

Please sign in to comment.