src/presentational-components/shared/table-composable-toolbar-view.tsx
import React, { Fragment } from 'react';
import { useIntl } from 'react-intl';
import messages from '../../Messages';
import { TableVariant, Table, Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table';
import TableToolbar from '@redhat-cloud-services/frontend-components/TableToolbar';
import SkeletonTable from '@patternfly/react-component-groups/dist/dynamic/SkeletonTable';
import { Button, Pagination, EmptyStateActions } from '@patternfly/react-core';
import { PlusCircleIcon } from '@patternfly/react-icons';
import Toolbar, { paginationBuilder } from './toolbar';
import EmptyWithAction from './empty-state';
import './table-toolbar-view.scss';
import { ISortBy, OnSort } from '@patternfly/react-table';
import { CellObject } from '../../smart-components/user/user-table-helpers';
interface FilterProps {
username?: string;
email?: string;
status?: string[];
}
interface FetchDataProps {
limit?: number;
offset?: number;
count?: number;
noBottom?: boolean;
filters?: FilterProps;
orderBy?: string; // TODO: make required later
username?: string;
email?: string;
status?: string[];
}
function isCellObject(cell: any): cell is CellObject {
return typeof cell === 'object' && typeof cell.title !== 'undefined';
}
interface TableProps extends MainTableProps {
emptyProps?: unknown;
rowWrapper?: any;
isExpandable?: boolean;
hideHeader?: boolean;
}
interface MainTableProps {
columns: Array<{ title: string; key?: string; sortable?: boolean }>;
isSelectable: boolean;
isLoading: boolean;
noData?: boolean;
data?: Array<unknown>; // used only in toolbar for selectable items
title: { singular: string; plural: string };
filterValue?: string;
setFilterValue: (value: FilterProps) => void;
pagination: { limit?: number; offset?: number; count?: number; noBottom?: boolean };
fetchData: (config: FetchDataProps) => void;
toolbarButtons?: () => React.ReactNode[];
filterPlaceholder?: string;
filters: Array<{
value: string | number | Array<unknown>;
key: string;
placeholder?: string;
innerRef?: React.RefObject<HTMLInputElement | HTMLTextAreaElement>;
label?: string;
type?: any;
items?: any;
}>;
isFilterable?: boolean;
onShowMore?: () => void;
showMoreTitle?: string;
onFilter?: () => void;
onChange?: () => void;
value?: unknown;
hideFilterChips?: boolean;
tableId?: string;
textFilterRef?: undefined; // TODO: check the usage
rows: Array<any>;
sortBy: ISortBy;
onSort: OnSort;
isCompact: boolean;
borders: boolean;
ouiaId: string;
noDataDescription?: Array<React.ReactNode>;
emptyFilters: FilterProps;
}
const MainTable = ({
// props for toolbar
columns,
isSelectable,
isLoading,
noData,
data,
title,
filterValue,
setFilterValue,
pagination,
fetchData,
toolbarButtons,
filterPlaceholder,
filters,
isFilterable,
onShowMore,
showMoreTitle,
onFilter,
onChange,
value,
hideFilterChips,
tableId,
textFilterRef,
//
rows,
sortBy,
onSort,
isCompact,
borders,
ouiaId,
noDataDescription,
emptyFilters,
}: MainTableProps) => {
const orderBy = sortBy?.index ? `${sortBy?.direction === 'desc' ? '-' : ''}${columns[sortBy.index]?.key}` : undefined;
const intl = useIntl();
return (
<Fragment>
<Toolbar
isSelectable={isSelectable}
isLoading={isLoading || noData}
data={data}
titleSingular={title.singular}
filterValue={filterValue}
setFilterValue={setFilterValue}
sortBy={orderBy}
pagination={pagination}
fetchData={fetchData}
toolbarButtons={toolbarButtons}
filterPlaceholder={filterPlaceholder}
filters={filters}
isFilterable={isFilterable}
onShowMore={onShowMore}
showMoreTitle={showMoreTitle}
onFilter={onFilter}
onChange={onChange}
value={value}
hideFilterChips={hideFilterChips}
tableId={tableId}
textFilterRef={textFilterRef}
/>
{isLoading ? (
<SkeletonTable variant={isCompact ? TableVariant.compact : undefined} rows={pagination?.limit} columns={columns.map((item) => item.title)} />
) : (
<Table
aria-label={`${title.plural.toLowerCase()} table`}
variant={isCompact ? TableVariant.compact : undefined}
borders={borders}
ouiaId={ouiaId} // [PF]: Value to overwrite the randomly generated data-ouia-component-id
>
<Thead>
<Tr>
{columns.map((column, i) => (
<Th key={i} sort={column?.sortable ? { columnIndex: i, sortBy, onSort } : undefined}>
{column.title}
</Th>
))}
</Tr>
</Thead>
<Tbody>
{rows?.length > 0 ? (
rows?.map((row, i) => (
<Tr key={i}>
{row.cells.map((cell: CellObject, j: number) => (
<Td key={j} dataLabel={columns[j].title}>
{/* TODO: make more general */}
{isCellObject(cell) ? (cell.title as string) : (cell as unknown as React.ReactNode)}
</Td>
))}
</Tr>
))
) : (
<Tr>
{/* render one component full width for the entire row */}
<Td colSpan={columns.length}>
<EmptyWithAction
title={intl.formatMessage(messages.noMatchingItemsFound, { items: title.plural })}
description={
noData && noDataDescription
? noDataDescription
: [
intl.formatMessage(messages.filterMatchesNoItems, { items: title.plural }),
intl.formatMessage(messages.tryChangingFilters),
]
}
actions={
noData && noDataDescription
? undefined
: [
<EmptyStateActions key="clear-filters">
<Button
variant="link"
ouiaId="clear-filters-button"
onClick={() => {
setFilterValue(emptyFilters);
fetchData({
...pagination,
offset: 0,
...(emptyFilters ? emptyFilters : { name: '' }),
});
}}
>
{intl.formatMessage(messages.clearAllFilters)}
</Button>
</EmptyStateActions>,
]
}
/>
</Td>
</Tr>
)}
</Tbody>
</Table>
)}
{!pagination.noBottom && (
<TableToolbar>
{!isLoading && <Pagination {...paginationBuilder(pagination, fetchData, filterValue, orderBy)} variant="bottom" dropDirection="up" />}
</TableToolbar>
)}
</Fragment>
);
};
export const TableComposableToolbarView = ({
isCompact = false,
borders,
columns,
rows,
data,
toolbarButtons,
title,
pagination,
filterValue,
isLoading,
emptyFilters,
setFilterValue,
isSelectable = false,
fetchData,
emptyProps,
filterPlaceholder,
filters,
isFilterable,
onShowMore,
showMoreTitle,
onFilter,
onChange,
value,
sortBy,
onSort,
hideFilterChips,
noData,
noDataDescription,
ouiaId,
tableId,
textFilterRef,
}: TableProps) => {
const intl = useIntl();
return (
<Fragment>
{!isLoading && rows?.length === 0 && filterValue?.length === 0 && filters.every(({ value }) => !value) ? (
<EmptyWithAction
title={intl.formatMessage(messages.configureItems, { items: title.plural })}
icon={PlusCircleIcon}
description={[
intl.formatMessage(messages.toConfigureUserAccess),
intl.formatMessage(messages.createAtLeastOneItem, { item: title.singular }),
]}
actions={toolbarButtons ? toolbarButtons()[0] : false}
{...(typeof emptyProps === 'object' ? emptyProps : {})}
/>
) : (
<MainTable
columns={columns}
isSelectable={isSelectable}
isLoading={isLoading}
noData={noData}
data={data}
title={title}
filterValue={filterValue}
setFilterValue={setFilterValue}
pagination={pagination}
fetchData={fetchData}
toolbarButtons={toolbarButtons}
filterPlaceholder={filterPlaceholder}
filters={filters}
isFilterable={isFilterable}
onShowMore={onShowMore}
showMoreTitle={showMoreTitle}
onFilter={onFilter}
onChange={onChange}
value={value}
hideFilterChips={hideFilterChips}
tableId={tableId}
textFilterRef={textFilterRef}
rows={rows}
sortBy={sortBy}
onSort={onSort}
isCompact={isCompact}
borders={borders}
ouiaId={ouiaId}
noDataDescription={noDataDescription}
emptyFilters={emptyFilters}
/>
)}
</Fragment>
);
};