Skip to content

Commit

Permalink
Sort and filters (#1491)
Browse files Browse the repository at this point in the history
* Add search input className

* Expose client side grid filter hook api

* Style tweak of filter on location inventory page

* Refactor commodity list view to filter data clientside

* Revert "Add search input className"

This reverts commit fa2e027.

* Fix lint errors

* Update fhir user management with active filter

* Fix lint errors

* Update snap regressions
  • Loading branch information
peterMuriuki authored Oct 31, 2024
1 parent 13f57fe commit c4fcd61
Show file tree
Hide file tree
Showing 12 changed files with 448 additions and 68 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import React, { ChangeEvent, ReactNode } from 'react';
import { Helmet } from 'react-helmet';
import { Row, Col } from 'antd';
import {
BodyLayout,
FilterDescription,
RegisterFilter,
useClientSideActionsDataGrid,
} from '@opensrp/react-utils';
import { parseGroup, ViewDetailsProps, ViewDetailsWrapper } from '../GroupDetail';
import { groupResourceType } from '../../../constants';
import {
SearchForm,
BrokenPage,
TableLayout,
Column,
viewDetailsQuery,
useSearchParams,
} from '@opensrp/react-utils';
import { useTranslation } from '../../../mls';
import { TFunction } from '@opensrp/i18n';
import { IBundle } from '@smile-cdr/fhirts/dist/FHIR-R4/interfaces/IBundle';

export type DefaultTableData = ReturnType<typeof parseGroup> & Record<string, unknown>;

export type ExtendableTableData = Pick<
ReturnType<typeof parseGroup>,
'id' | 'name' | 'active' | 'identifier' | 'lastUpdated'
>;

export type ClientSideActionsBaseListViewProps<
TableData extends ExtendableTableData = DefaultTableData
> = Partial<Pick<ViewDetailsProps, 'keyValueMapperRenderProp'>> & {
fhirBaseURL: string;
getColumns: (t: TFunction) => Column<TableData>[];
extraQueryFilters?: Record<string, string>;
addGroupBtnRender?: () => ReactNode;
pageTitle: string;
dataTransformer?: (groups: IBundle) => TableData[];
viewDetailsRender?: (fhirBaseURL: string, resourceId?: string) => ReactNode;
filterRowRender?: (
registerFilter: RegisterFilter<TableData>,
filterRegistry: FilterDescription<TableData>
) => ReactNode;
};

/**
* Shows the list of all group and there details
*
* @param props - GroupList component props
* @returns returns healthcare display
*/
export function ClientSideActionsBaseListView<
TableData extends ExtendableTableData = DefaultTableData
>(props: ClientSideActionsBaseListViewProps<TableData>) {
const {
fhirBaseURL,
extraQueryFilters,
filterRowRender,
getColumns,
addGroupBtnRender,
keyValueMapperRenderProp,
pageTitle,
viewDetailsRender,
dataTransformer,
} = props;

const { sParams } = useSearchParams();
const resourceId = sParams.get(viewDetailsQuery) ?? undefined;
const { t } = useTranslation();

const {
queryValues: { data, isFetching, isLoading, error },
tablePaginationProps,
searchFormProps,
filterOptions: { registerFilter, filterRegistry, deregisterFilter },
} = useClientSideActionsDataGrid<TableData>(
fhirBaseURL,
groupResourceType,
extraQueryFilters,
dataTransformer
);

if (error && !data.length) {
return <BrokenPage errorMessage={(error as Error).message} />;
}

const tableData = data;

const columns = getColumns(t);

const tableProps = {
datasource: tableData,
columns,
loading: isFetching || isLoading,
pagination: tablePaginationProps,
};
const headerProps = {
pageHeaderProps: {
title: pageTitle,
onBack: undefined,
},
};

const nameFilterKey = 'name';
const searchInputProps = {
...searchFormProps,
wrapperClassName: 'elongate-search-bar',
onChangeHandler: (event: ChangeEvent<HTMLInputElement>) => {
searchFormProps.onChangeHandler(event);
const searchText = event.target.value;
if (searchText) {
registerFilter(
nameFilterKey,
(el) => {
return (el.name ?? '').toLowerCase().includes(searchText.toLowerCase());
},
searchText
);
} else {
deregisterFilter(nameFilterKey);
}
},
};

return (
<BodyLayout headerProps={headerProps}>
<Helmet>
<title>{pageTitle}</title>
</Helmet>
<Row className="list-view">
<Col className="main-content">
<div className="main-content__header">
<SearchForm data-testid="search-form" {...searchInputProps} />
{addGroupBtnRender?.()}
</div>
{filterRowRender?.(registerFilter, filterRegistry)}
<TableLayout {...tableProps} />
</Col>
{viewDetailsRender?.(fhirBaseURL, resourceId) ?? (
<ViewDetailsWrapper
resourceId={resourceId}
fhirBaseURL={fhirBaseURL}
keyValueMapperRenderProp={keyValueMapperRenderProp}
/>
)}
</Row>
</BodyLayout>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { Space, Radio } from 'antd';
import React from 'react';
import { FilterDescription, RegisterFilter } from '@opensrp/react-utils';
import { useTranslation } from '../../../mls';
import { Trans } from '@opensrp/i18n';
import { parseEusmCommodity } from './ViewDetails';

export type TableData = ReturnType<typeof parseEusmCommodity>;

export interface GroupGridFilerRowProps {
updateFilterParams: RegisterFilter<TableData>;
currentFilters: FilterDescription<TableData>;
}

const isAnAssetDataIdx = 'isAnAsset';
const statusFilterDataIdx = 'status';

export const GroupGridFilerRow = (props: GroupGridFilerRowProps) => {
const { t } = useTranslation();
const { updateFilterParams, currentFilters } = props;
return (
<div className="filter-row" data-testid="filter-row">
<Space size={'large'}>
<Trans t={t} i18nKey="attractiveFilter">
<Space>
Asset:
<Radio.Group
size="small"
value={currentFilters[isAnAssetDataIdx]?.value}
buttonStyle="solid"
onChange={(event) => {
const val = event.target.value;
if (val !== undefined) {
updateFilterParams(
isAnAssetDataIdx,
(el: TableData) => {
return el.attractive === val;
},
val
);
} else {
updateFilterParams(isAnAssetDataIdx, undefined);
}
}}
>
<Radio.Button value={true}>{t('Yes')}</Radio.Button>
<Radio.Button value={false}>{t('No')}</Radio.Button>
<Radio.Button value={undefined}>{t('Show all')}</Radio.Button>
</Radio.Group>
</Space>
</Trans>
<Trans t={t} i18nKey="groupStatusFilter">
<Space>
Status:
<Radio.Group
size="small"
value={currentFilters[statusFilterDataIdx]?.value}
buttonStyle="solid"
onChange={(event) => {
const val = event.target.value;
if (val !== undefined) {
updateFilterParams(
statusFilterDataIdx,
(el: TableData) => {
return el.active === val;
},
val
);
} else {
updateFilterParams(statusFilterDataIdx, undefined);
}
}}
>
<Radio.Button value={true}>{t('Active')}</Radio.Button>
<Radio.Button value={false}>{t('Inactive')}</Radio.Button>
<Radio.Button value={undefined}>{t('Show all')}</Radio.Button>
</Radio.Group>
</Space>
</Trans>
</Space>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
import React from 'react';
import { Button, Divider, Dropdown, MenuProps } from 'antd';
import { MoreOutlined } from '@ant-design/icons';
import { MoreOutlined, PlusOutlined } from '@ant-design/icons';
import { ADD_EDIT_COMMODITY_URL } from '../../../constants';
import { Link } from 'react-router-dom';
import { Link, useHistory } from 'react-router-dom';
import { useTranslation } from '../../../mls';
import { BaseListView, BaseListViewProps } from '../../BaseComponents/BaseGroupsListView';
import { TFunction } from '@opensrp/i18n';
import { useSearchParams, viewDetailsQuery } from '@opensrp/react-utils';
import { getResourcesFromBundle, useSearchParams, viewDetailsQuery } from '@opensrp/react-utils';
import { supplyMgSnomedCode, snomedCodeSystem } from '../../../helpers/utils';
import { RbacCheck, useUserRole } from '@opensrp/rbac';
import { ViewDetailsWrapper, parseEusmCommodity } from './ViewDetails';
import { IGroup } from '@smile-cdr/fhirts/dist/FHIR-R4/interfaces/IGroup';
import { GroupGridFilerRow, TableData } from './GroupGridFilterRow';
import {
ClientSideActionsBaseListView,
ClientSideActionsBaseListViewProps,
} from '../../BaseComponents/BaseGroupsListView/ClientSideActionsGrid';
import { IBundle } from '@smile-cdr/fhirts/dist/FHIR-R4/interfaces/IBundle';

interface GroupListProps {
fhirBaseURL: string;
listId: string; // commodities are added to list resource with this id
}

type TableData = ReturnType<typeof parseEusmCommodity>;

/**
* Shows the list of all group and there details
*
Expand All @@ -28,6 +31,7 @@ type TableData = ReturnType<typeof parseEusmCommodity>;
export const EusmCommodityList = (props: GroupListProps) => {
const { fhirBaseURL, listId } = props;

const history = useHistory();
const { t } = useTranslation();
const { addParam } = useSearchParams();
const userRole = useUserRole();
Expand Down Expand Up @@ -60,23 +64,21 @@ export const EusmCommodityList = (props: GroupListProps) => {
title: t('Material Number'),
dataIndex: 'identifier' as const,
key: 'identifier' as const,
sorter: (a: TableData, b: TableData) =>
(a.identifier ?? '').localeCompare(b.identifier ?? ''),
},
{
title: t('Name'),
dataIndex: 'name' as const,
key: 'name' as const,
sorter: (a: TableData, b: TableData) => (a.name ?? '').localeCompare(b.name ?? ''),
},
{
title: t('Is it an Asset'),
dataIndex: 'attractive' as const,
key: 'attractive' as const,
render: (value: boolean) => <div>{value ? t('Yes') : t('No')}</div>,
},
{
title: t('type'),
dataIndex: 'type' as const,
key: 'type' as const,
},
{
title: t('Active'),
dataIndex: 'active' as const,
Expand Down Expand Up @@ -110,12 +112,22 @@ export const EusmCommodityList = (props: GroupListProps) => {
},
];

const baseListViewProps: BaseListViewProps<TableData> = {
const baseListViewProps: ClientSideActionsBaseListViewProps<TableData> = {
getColumns: getColumns,
createButtonLabel: t('Add commodity'),
createButtonUrl: ADD_EDIT_COMMODITY_URL,
addGroupBtnRender: () => {
return (
<RbacCheck permissions={['Group.create', 'List.create', 'List.update']}>
<Button type="primary" onClick={() => history.push(ADD_EDIT_COMMODITY_URL)}>
<PlusOutlined />
{t('Add commodity')}
</Button>
</RbacCheck>
);
},
fhirBaseURL,
generateTableData: (group: IGroup) => parseEusmCommodity(group),
dataTransformer(bundle: IBundle) {
return getResourcesFromBundle<IGroup>(bundle).map((group) => parseEusmCommodity(group));
},
pageTitle: t('Commodity List'),
extraQueryFilters: {
code: `${snomedCodeSystem}|${supplyMgSnomedCode}`,
Expand All @@ -124,7 +136,12 @@ export const EusmCommodityList = (props: GroupListProps) => {
viewDetailsRender: (fhirBaseURL, resourceId) => (
<ViewDetailsWrapper fhirBaseURL={fhirBaseURL} resourceId={resourceId} />
),
filterRowRender(registerFilter, filterRegistry) {
return (
<GroupGridFilerRow updateFilterParams={registerFilter} currentFilters={filterRegistry} />
);
},
};

return <BaseListView {...baseListViewProps} />;
return <ClientSideActionsBaseListView {...baseListViewProps} />;
};
Original file line number Diff line number Diff line change
Expand Up @@ -165,14 +165,6 @@ exports[`renders correctly when listing resources: table row 1 page 1 3`] = `
`;

exports[`renders correctly when listing resources: table row 1 page 1 4`] = `
<td
class="ant-table-cell"
>
substance
</td>
`;

exports[`renders correctly when listing resources: table row 1 page 1 5`] = `
<td
class="ant-table-cell"
>
Expand All @@ -182,7 +174,7 @@ exports[`renders correctly when listing resources: table row 1 page 1 5`] = `
</td>
`;

exports[`renders correctly when listing resources: table row 1 page 1 6`] = `
exports[`renders correctly when listing resources: table row 1 page 1 5`] = `
<td
class="ant-table-cell"
>
Expand Down
Loading

0 comments on commit c4fcd61

Please sign in to comment.