Skip to content

Commit

Permalink
(fix)O3-3046: Allow sorting vitals, biometrics and conditions rows ba…
Browse files Browse the repository at this point in the history
…sed on different sort functions (#1779)

* Allow sorting vitals row based on different sort functions

* Allow sorting biometrics row based on different sort functions

* Allow sorting conditions row based on different sort functions

* Finalizing changes

* Fixed the default sorting

* Fixed failing tests

* Updated tests for vitals and biometrics app

---------

Co-authored-by: Dennis Kigen <kigen.work@gmail.com>
  • Loading branch information
vasharma05 and denniskigen authored Apr 12, 2024
1 parent b2b0bd4 commit 500508c
Show file tree
Hide file tree
Showing 11 changed files with 335 additions and 112 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ import { Add } from '@carbon/react/icons';
import { formatDate, parseDate, useLayoutType } from '@openmrs/esm-framework';
import { CardHeader, EmptyState, ErrorState, launchPatientWorkspace } from '@openmrs/esm-patient-common-lib';
import { ConditionsActionMenu } from './conditions-action-menu.component';
import { compare } from './utils';
import { useConditions } from './conditions.resource';
import { useConditions, type ConditionTableHeader, useConditionsSorting } from './conditions.resource';
import styles from './conditions-detailed-summary.scss';

function ConditionsDetailedSummary({ patient }) {
Expand All @@ -47,19 +46,28 @@ function ConditionsDetailedSummary({ patient }) {
return conditions;
}, [filter, conditions]);

const headers = useMemo(
const headers: Array<ConditionTableHeader> = useMemo(
() => [
{
key: 'display',
header: t('condition', 'Condition'),
isSortable: true,
sortFunc: (valueA, valueB) => valueA.display?.localeCompare(valueB.display),
},
{
key: 'onsetDateTime',
key: 'onsetDateTimeRender',
header: t('dateOfOnset', 'Date of onset'),
isSortable: true,
sortFunc: (valueA, valueB) =>
valueA.onsetDateTime && valueB.onsetDateTime
? new Date(valueA.onsetDateTime).getTime() - new Date(valueB.onsetDateTime).getTime()
: 0,
},
{
key: 'clinicalStatus',
key: 'status',
header: t('status', 'Status'),
isSortable: true,
sortFunc: (valueA, valueB) => valueA.clinicalStatus?.localeCompare(valueB.clinicalStatus),
},
],
[t],
Expand All @@ -72,22 +80,15 @@ function ConditionsDetailedSummary({ patient }) {
id: condition.id,
condition: condition.display,
abatementDateTime: condition.abatementDateTime,
onsetDateTime: {
sortKey: condition.onsetDateTime ?? '',
content: condition.onsetDateTime
? formatDate(parseDate(condition.onsetDateTime), { time: false, day: false })
: '--',
},
onsetDateTimeRender: condition.onsetDateTime
? formatDate(parseDate(condition.onsetDateTime), { time: false, day: false })
: '--',
status: condition.clinicalStatus,
};
});
}, [filteredConditions]);

const sortRow = (cellA, cellB, { sortDirection, sortStates }) => {
return sortDirection === sortStates.DESC
? compare(cellB.sortKey, cellA.sortKey)
: compare(cellA.sortKey, cellB.sortKey);
};
const { sortedRows, sortRow } = useConditionsSorting(headers, tableRows);

const launchConditionsForm = useCallback(
() =>
Expand Down Expand Up @@ -131,7 +132,7 @@ function ConditionsDetailedSummary({ patient }) {
</div>
</CardHeader>
<DataTable
rows={tableRows}
rows={sortedRows}
sortRow={sortRow}
headers={headers}
isSortable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,23 @@ import {
} from '@openmrs/esm-patient-common-lib';
import type { ConfigObject } from '../config-schema';
import { ConditionsActionMenu } from './conditions-action-menu.component';
import { useConditions } from './conditions.resource';
import { type Condition, useConditions, useConditionsSorting } from './conditions.resource';
import styles from './conditions-overview.scss';

interface ConditionTableRow extends Condition {
id: string;
condition: string;
abatementDateTime: string;
onsetDateTimeRender: string;
}

interface ConditionTableHeader {
key: 'display' | 'onsetDateTimeRender' | 'status';
header: string;
isSortable: true;
sortFunc: (valueA: ConditionTableRow, valueB: ConditionTableRow) => number;
}

interface ConditionsOverviewProps {
patientUuid: string;
}
Expand Down Expand Up @@ -74,35 +88,51 @@ const ConditionsOverview: React.FC<ConditionsOverviewProps> = ({ patientUuid })
return conditions;
}, [filter, conditions]);

const {
results: paginatedConditions,
goTo,
currentPage,
} = usePagination(filteredConditions ?? [], conditionPageSize);

const tableHeaders = [
{
key: 'display',
header: t('condition', 'Condition'),
},
{
key: 'onsetDateTime',
header: t('dateOfOnset', 'Date of onset'),
},
{
key: 'clinicalStatus',
header: t('status', 'Status'),
},
];
const headers: Array<ConditionTableHeader> = useMemo(
() => [
{
key: 'display',
header: t('condition', 'Condition'),
isSortable: true,
sortFunc: (valueA, valueB) => valueA.display?.localeCompare(valueB.display),
},
{
key: 'onsetDateTimeRender',
header: t('dateOfOnset', 'Date of onset'),
isSortable: true,
sortFunc: (valueA, valueB) =>
valueA.onsetDateTime && valueB.onsetDateTime
? new Date(valueA.onsetDateTime).getTime() - new Date(valueB.onsetDateTime).getTime()
: 0,
},
{
key: 'status',
header: t('status', 'Status'),
isSortable: true,
sortFunc: (valueA, valueB) => valueA.clinicalStatus?.localeCompare(valueB.clinicalStatus),
},
],
[t],
);

const tableRows = useMemo(() => {
return paginatedConditions?.map((condition) => ({
...condition,
onsetDateTime: condition.onsetDateTime
? formatDate(parseDate(condition.onsetDateTime), { time: false, day: false })
: '--',
}));
}, [paginatedConditions]);
return filteredConditions?.map((condition) => {
return {
...condition,
id: condition.id,
condition: condition.display,
abatementDateTime: condition.abatementDateTime,
onsetDateTimeRender: condition.onsetDateTime
? formatDate(parseDate(condition.onsetDateTime), { time: false, day: false })
: '--',
status: condition.clinicalStatus,
};
});
}, [filteredConditions]);

const { sortedRows, sortRow } = useConditionsSorting(headers, tableRows);

const { results: paginatedConditions, goTo, currentPage } = usePagination(sortedRows, conditionPageSize);

const handleConditionStatusChange = ({ selectedItem }) => setFilter(selectedItem);

Expand Down Expand Up @@ -139,12 +169,13 @@ const ConditionsOverview: React.FC<ConditionsOverviewProps> = ({ patientUuid })
</CardHeader>
<DataTable
aria-label="conditions overview"
rows={tableRows}
headers={tableHeaders}
rows={paginatedConditions}
headers={headers}
isSortable
size={isTablet ? 'lg' : 'sm'}
useZebraStyles
overflowMenuOnHover={isDesktop}
sortRow={sortRow}
>
{({ rows, headers, getHeaderProps, getTableProps }) => (
<>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import useSWR from 'swr';
import { fhirBaseUrl, openmrsFetch, restBaseUrl, useConfig } from '@openmrs/esm-framework';
import { type FHIRCondition, type FHIRConditionResponse } from '../types';
import { useMemo, useState } from 'react';

export type Condition = {
clinicalStatus: string;
Expand Down Expand Up @@ -233,3 +234,45 @@ export async function deleteCondition(conditionId: string) {

return res;
}

export interface ConditionTableRow extends Condition {
id: string;
condition: string;
abatementDateTime: string;
onsetDateTimeRender: string;
}

export interface ConditionTableHeader {
key: 'display' | 'onsetDateTimeRender' | 'status';
header: string;
isSortable: true;
sortFunc: (valueA: ConditionTableRow, valueB: ConditionTableRow) => number;
}

export function useConditionsSorting(tableHeaders: Array<ConditionTableHeader>, tableRows: Array<ConditionTableRow>) {
const [sortParams, setSortParams] = useState<{
key: ConditionTableHeader['key'] | '';
sortDirection: 'ASC' | 'DESC' | 'NONE';
}>({ key: '', sortDirection: 'NONE' });
const sortRow = (cellA, cellB, { key, sortDirection }) => {
setSortParams({ key, sortDirection });
};
const sortedRows = useMemo(() => {
if (sortParams.sortDirection === 'NONE') {
return tableRows;
}

const { key, sortDirection } = sortParams;
const tableHeader = tableHeaders.find((h) => h.key === key);

return tableRows?.slice().sort((a, b) => {
const sortingNum = tableHeader.sortFunc(a, b);
return sortDirection === 'DESC' ? sortingNum : -sortingNum;
});
}, [sortParams, tableRows, tableHeaders]);

return {
sortedRows,
sortRow,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { type ConfigObject } from '../config-schema';
import BiometricsChart from './biometrics-chart.component';
import PaginatedBiometrics from './paginated-biometrics.component';
import styles from './biometrics-base.scss';
import type { BiometricsTableHeader, BiometricsTableRow } from './types';

interface BiometricsBaseProps {
pageSize: number;
Expand All @@ -36,28 +37,50 @@ const BiometricsBase: React.FC<BiometricsBaseProps> = ({ patientUuid, pageSize,
[config, currentVisit],
);

const tableHeaders = [
{ 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})` },
const tableHeaders: Array<BiometricsTableHeader> = [
{
key: 'muac',
key: 'dateRender',
header: t('dateAndTime', 'Date and time'),
isSortable: true,
sortFunc: (valueA, valueB) => new Date(valueA.date).getTime() - new Date(valueB.date).getTime(),
},
{
key: 'weightRender',
header: withUnit(t('weight', 'Weight'), conceptUnits.get(config.concepts.weightUuid) ?? ''),
isSortable: true,
sortFunc: (valueA, valueB) => (valueA.weight && valueB.weight ? valueA.weight - valueB.weight : 0),
},
{
key: 'heightRender',
header: withUnit(t('height', 'Height'), conceptUnits.get(config.concepts.heightUuid) ?? ''),
isSortable: true,
sortFunc: (valueA, valueB) => (valueA.height && valueB.height ? valueA.height - valueB.height : 0),
},
{
key: 'bmiRender',
header: `${t('bmi', 'BMI')} (${bmiUnit})`,
isSortable: true,
sortFunc: (valueA, valueB) => (valueA.bmi && valueB.bmi ? valueA.bmi - valueB.bmi : 0),
},
{
key: 'muacRender',
header: withUnit(t('muac', 'MUAC'), conceptUnits.get(config.concepts.midUpperArmCircumferenceUuid) ?? ''),
isSortable: true,
sortFunc: (valueA, valueB) => (valueA.muac && valueB.muac ? valueA.muac - valueB.muac : 0),
},
];

const tableRows = useMemo(
const tableRows: Array<BiometricsTableRow> = useMemo(
() =>
biometrics?.map((biometricsData, index) => {
return {
...biometricsData,
id: `${index}`,
date: formatDatetime(parseDate(biometricsData.date.toString()), { mode: 'wide' }),
weight: biometricsData.weight ?? '--',
height: biometricsData.height ?? '--',
bmi: biometricsData.bmi ?? '--',
muac: biometricsData.muac ?? '--',
dateRender: formatDatetime(parseDate(biometricsData.date.toString()), { mode: 'wide' }),
weightRender: biometricsData.weight ?? '--',
heightRender: biometricsData.height ?? '--',
bmiRender: biometricsData.bmi ?? '--',
muacRender: biometricsData.muac ?? '--',
};
}),
[biometrics],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,17 @@ describe('BiometricsOverview: ', () => {

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

// Sorting in descending order
// Since the date order is already in descending order, the rows should be the same
await user.click(sortRowsButton);
// Sorting in ascending order
await user.click(sortRowsButton);

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

// Sorting order = NONE, hence it is still in the ascending order
await user.click(sortRowsButton);
// Sorting in descending order
await user.click(sortRowsButton);

expect(screen.getAllByRole('row')).toEqual(initialRowElements);
Expand Down
Loading

0 comments on commit 500508c

Please sign in to comment.