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

feat(manager-react-components): add filters in datagrid #14203

Merged
merged 8 commits into from
Jan 29, 2025
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import React, { useState } from 'react';
import { ColumnSort } from '@tanstack/react-table';
import { OdsDivider } from '@ovhcloud/ods-components/react';
import { ODS_BUTTON_VARIANT } from '@ovhcloud/ods-components';
import { withRouter } from 'storybook-addon-react-router-v6';
import { Datagrid, DatagridProps } from './datagrid.component';
import { DataGridTextCell } from './text-cell.component';
import { useSearchParams } from 'react-router-dom';
import { Datagrid } from './datagrid.component';
import { useColumnFilters } from '../filters';
import { columns, columsFilters } from './datagrid.mock';
import { ActionMenu } from '../navigation';

interface Item {
Expand All @@ -12,30 +15,11 @@ interface Item {
actions: React.ReactElement;
}

const columns = [
{
id: 'label',
cell: (item: Item) => {
return <DataGridTextCell>{item.label}</DataGridTextCell>;
},
label: 'Label',
},
{
id: 'price',
cell: (item: Item) => {
return <DataGridTextCell>{item.price} €</DataGridTextCell>;
},
label: 'Price',
},
];

const DatagridStory = ({
items,
isSortable,
...args
}: { isSortable?: boolean } & DatagridProps<unknown>) => {
const DatagridStory = (args) => {
const [sorting, setSorting] = useState<ColumnSort>();
const [data, setData] = useState(items);
const [data, setData] = useState(args.items);
const [searchParams] = useSearchParams();
const { filters, addFilter, removeFilter } = useColumnFilters();

const fetchNextPage = () => {
const itemsIndex = data?.length;
Expand All @@ -47,20 +31,29 @@ const DatagridStory = ({
};

return (
<Datagrid
{...args}
items={data}
hasNextPage={data?.length > 0 && data.length < 30}
onFetchNextPage={fetchNextPage}
totalItems={data?.length}
{...(isSortable
? {
sorting,
onSortChange: setSorting,
manualSorting: false,
}
: {})}
/>
<>
{`${searchParams}` && (
<>
<pre>Search params: ?{`${searchParams}`}</pre>
<OdsDivider />
</>
)}
<Datagrid
items={data}
columns={args.columns}
hasNextPage={data?.length > 0 && data.length < 30}
onFetchNextPage={fetchNextPage}
totalItems={data?.length}
filters={{ filters, add: addFilter, remove: removeFilter }}
{...(args.isSortable
? {
sorting,
onSortChange: setSorting,
manualSorting: false,
}
: {})}
/>
</>
);
};

Expand Down Expand Up @@ -143,6 +136,17 @@ WithActions.args = {
isSortable: true,
};

export const Filters = DatagridStory.bind({});

Filters.args = {
items: [...Array(10).keys()].map((_, i) => ({
label: `Item #${i}`,
price: Math.floor(1 + Math.random() * 100),
})),
isSortable: true,
columns: columsFilters,
};

export default {
title: 'Components/Datagrid Cursor',
component: Datagrid,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import { useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import {
ColumnDef,
ColumnSort as TanstackColumnSort,
Expand All @@ -8,15 +9,27 @@ import {
useReactTable,
getSortedRowModel,
} from '@tanstack/react-table';
import { ODS_ICON_NAME, ODS_BUTTON_VARIANT } from '@ovhcloud/ods-components';
import {
ODS_ICON_NAME,
ODS_BUTTON_VARIANT,
ODS_BUTTON_SIZE,
} from '@ovhcloud/ods-components';
import {
OdsPopover,
OdsButton,
OdsIcon,
OdsPagination,
OdsSkeleton,
OdsTable,
} from '@ovhcloud/ods-components/react';
import { useTranslation } from 'react-i18next';
import {
FilterComparator,
FilterCategories,
FilterTypeCategories,
} from '@ovh-ux/manager-core-api';
import { FilterAdd, FilterList } from '../filters';
import { ColumnFilter } from '../filters/filter-add.component';
import { FilterWithLabel } from '../filters/interface';
import { DataGridTextCell } from './text-cell.component';
import { defaultNumberOfLoadingRows } from './datagrid.contants';
import './translations';
Expand All @@ -37,6 +50,25 @@ export interface DatagridColumn<T> {
label: string;
/** is the column sortable ? (defaults is true) */
isSortable?: boolean;
/** set column comparator for the filter */
comparator?: FilterComparator[];
/** Filters displayed for the column */
type?: FilterTypeCategories;
/** Trigger the column filter */
isFilterable?: boolean;
}

type ColumnFilterProps = {
key: string;
value: string | string[];
comparator: FilterComparator;
label: string;
};

export interface FilterProps {
filters: FilterWithLabel[];
add: (filters: ColumnFilterProps) => void;
remove: (filter: FilterWithLabel) => void;
}

export interface DatagridProps<T> {
Expand Down Expand Up @@ -74,11 +106,14 @@ export interface DatagridProps<T> {
isLoading?: boolean;
/** number of loading rows to show when table is in loading state, defaults to pagination.pageSize or 5 */
numberOfLoadingRows?: number;
/** List of filters and handlers to add, remove */
filters?: FilterProps;
}

export const Datagrid = <T,>({
columns,
items,
filters,
totalItems,
pagination,
sorting,
Expand All @@ -95,6 +130,8 @@ export const Datagrid = <T,>({
numberOfLoadingRows,
}: DatagridProps<T>) => {
const { t } = useTranslation('datagrid');
const { t: tfilters } = useTranslation('filters');
const filterPopoverRef = useRef(null);
const pageCount = pagination
? Math.ceil(totalItems / pagination.pageSize)
: 1;
Expand Down Expand Up @@ -139,8 +176,64 @@ export const Datagrid = <T,>({
}),
});

const columnsFilters = useMemo<ColumnFilter[]>(
() =>
columns
.filter(
(item) =>
('comparator' in item || 'type' in item) &&
'isFilterable' in item &&
item.isFilterable,
)
.map((column) => ({
id: column.id,
label: column.label,
...(column?.type && { comparators: FilterCategories[column.type] }),
...(column?.comparator && { comparators: column.comparator }),
})),
[columns],
);

return (
<div>
{columnsFilters.length > 0 && (
<div className="flex flex-row-reverse py-[24px]">
<OdsButton
id="datagrid-filter-popover-trigger"
slot="datagrid-filter-popover-trigger"
size={ODS_BUTTON_SIZE.sm}
variant={ODS_BUTTON_VARIANT.ghost}
icon={ODS_ICON_NAME.filter}
aria-label={tfilters('common_criteria_adder_filter_label')}
label=""
/>
<OdsPopover
ref={filterPopoverRef}
triggerId="datagrid-filter-popover-trigger"
with-arrow
>
<FilterAdd
columns={columnsFilters}
onAddFilter={(addedFilter, column) => {
filters.add({
...addedFilter,
label: column.label,
});
filterPopoverRef.current?.hide();
}}
/>
</OdsPopover>
</div>
)}
{filters?.filters.length > 0 && (
<div id="datagrid-filter-list" className="mb-[24px]">
<FilterList
filters={filters.filters}
onRemoveFilter={filters.remove}
/>
</div>
)}

<div className={`contents px-[1px] ${className || ''}`}>
<OdsTable className="overflow-x-visible">
<table className="w-full border-collapse">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { FilterCategories } from '@ovh-ux/manager-core-api';
import { DataGridTextCell } from './text-cell.component';

export interface Item {
label: string;
price: number;
}

export const columns = [
{
id: 'label',
cell: (item: Item) => {
return <DataGridTextCell>{item.label}</DataGridTextCell>;
},
label: 'Label',
},
{
id: 'price',
cell: (item: Item) => {
return <DataGridTextCell>{item.price} €</DataGridTextCell>;
},
label: 'Price',
},
];

export const columsFilters = [
{
id: 'label',
cell: (item: Item) => {
return <DataGridTextCell>{item.label}</DataGridTextCell>;
},
label: 'Label',
isFilterable: true,
comparator: FilterCategories.String,
},
{
id: 'price',
cell: (item: Item) => {
return <DataGridTextCell>{item.price} €</DataGridTextCell>;
},
label: 'Price',
isFilterable: true,
comparator: FilterCategories.String,
},
];
Loading
Loading