Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sort and filters #1491

Merged
merged 11 commits into from
Oct 31, 2024
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
Loading