* Add filter search logic WIP Filter search Implement filters test: fix sorts tests test: fix filter test feature: search person and display firstname in results feature: fix test for filter component test: mock search filters refactor: create a useSearch hook refactor: move debounce in useSearch and reset status of filter selection feature: debounce set filters refactor: remove useless setSorts feature: add where variable to people query feature: strongly type Filters feature: update WhereTemplate method feature: implement filtering on full name feature: type the useSearch hook feature: use where reducer refactor: create a type for readability feature: use query and mapper from filters feature: implement filter by company feature: search filter results on filter select feature: add loading and results to search results in filters refactor: move render search results in a function feature: display a LOADING when it loads feature: split search input and search filter for different debounce refactor: remove some warnings refactor: remove some warnings * Write test 1 * Write test 2 * test: useSearch is tested * test: update names of default people data * test: add a filter search * Test 3 * Fix tests --------- Co-authored-by: Charles Bochet <charles@twenty.com>
183 lines
4.8 KiB
TypeScript
183 lines
4.8 KiB
TypeScript
import * as React from 'react';
|
|
|
|
import {
|
|
ColumnDef,
|
|
flexRender,
|
|
getCoreRowModel,
|
|
useReactTable,
|
|
} from '@tanstack/react-table';
|
|
import TableHeader from './table-header/TableHeader';
|
|
import styled from '@emotion/styled';
|
|
import {
|
|
FilterType,
|
|
SelectedFilterType,
|
|
SelectedSortType,
|
|
SortType,
|
|
} from './table-header/interface';
|
|
|
|
type OwnProps<TData, SortField, FilterProperties> = {
|
|
data: Array<TData>;
|
|
columns: Array<ColumnDef<TData, any>>;
|
|
viewName: string;
|
|
viewIcon?: React.ReactNode;
|
|
availableSorts?: Array<SortType<SortField>>;
|
|
availableFilters?: FilterType<FilterProperties>[];
|
|
filterSearchResults?: {
|
|
results: { displayValue: string; value: any }[];
|
|
loading: boolean;
|
|
};
|
|
onSortsUpdate?: (sorts: Array<SelectedSortType<SortField>>) => void;
|
|
onFiltersUpdate?: (
|
|
sorts: Array<SelectedFilterType<FilterProperties>>,
|
|
) => void;
|
|
onFilterSearch?: (
|
|
filter: FilterType<FilterProperties> | null,
|
|
searchValue: string,
|
|
) => void;
|
|
};
|
|
|
|
const StyledTable = styled.table`
|
|
min-width: 1000px;
|
|
width: calc(100% - ${(props) => props.theme.spacing(4)});
|
|
border-radius: 4px;
|
|
border-spacing: 0;
|
|
border-collapse: collapse;
|
|
margin-left: ${(props) => props.theme.spacing(2)};
|
|
margin-right: ${(props) => props.theme.spacing(2)};
|
|
|
|
th {
|
|
border-collapse: collapse;
|
|
color: ${(props) => props.theme.text40};
|
|
padding: 0;
|
|
border: 1px solid ${(props) => props.theme.tertiaryBackground};
|
|
text-align: left;
|
|
:last-child {
|
|
border-right-color: transparent;
|
|
}
|
|
:first-of-type {
|
|
border-left-color: transparent;
|
|
}
|
|
}
|
|
|
|
td {
|
|
border-collapse: collapse;
|
|
color: ${(props) => props.theme.text80};
|
|
padding: 0;
|
|
border: 1px solid ${(props) => props.theme.tertiaryBackground};
|
|
text-align: left;
|
|
:last-child {
|
|
border-right-color: transparent;
|
|
}
|
|
:first-of-type {
|
|
border-left-color: transparent;
|
|
}
|
|
}
|
|
`;
|
|
|
|
const StyledTableWithHeader = styled.div`
|
|
display: flex;
|
|
flex-direction: column;
|
|
flex: 1;
|
|
width: 100%;
|
|
`;
|
|
|
|
const StyledTableScrollableContainer = styled.div`
|
|
overflow: auto;
|
|
height: 100%;
|
|
flex: 1;
|
|
`;
|
|
|
|
function Table<TData, SortField extends string, FilterProperies>({
|
|
data,
|
|
columns,
|
|
viewName,
|
|
viewIcon,
|
|
availableSorts,
|
|
availableFilters,
|
|
filterSearchResults,
|
|
onSortsUpdate,
|
|
onFiltersUpdate,
|
|
onFilterSearch,
|
|
}: OwnProps<TData, SortField, FilterProperies>) {
|
|
const table = useReactTable({
|
|
data,
|
|
columns,
|
|
getCoreRowModel: getCoreRowModel(),
|
|
});
|
|
|
|
return (
|
|
<StyledTableWithHeader>
|
|
<TableHeader
|
|
viewName={viewName}
|
|
viewIcon={viewIcon}
|
|
availableSorts={availableSorts}
|
|
availableFilters={availableFilters}
|
|
filterSearchResults={filterSearchResults}
|
|
onSortsUpdate={onSortsUpdate}
|
|
onFiltersUpdate={onFiltersUpdate}
|
|
onFilterSearch={onFilterSearch}
|
|
/>
|
|
<StyledTableScrollableContainer>
|
|
<StyledTable>
|
|
<thead>
|
|
{table.getHeaderGroups().map((headerGroup) => (
|
|
<tr key={headerGroup.id}>
|
|
{headerGroup.headers.map((header) => (
|
|
<th key={header.id}>
|
|
{header.isPlaceholder
|
|
? null
|
|
: flexRender(
|
|
header.column.columnDef.header,
|
|
header.getContext(),
|
|
)}
|
|
</th>
|
|
))}
|
|
</tr>
|
|
))}
|
|
</thead>
|
|
<tbody>
|
|
{table.getRowModel().rows.map((row, index) => (
|
|
<tr key={row.id} data-testid={`row-id-${row.index}`}>
|
|
{row.getVisibleCells().map((cell) => {
|
|
return (
|
|
<td key={cell.id}>
|
|
{flexRender(
|
|
cell.column.columnDef.cell,
|
|
cell.getContext(),
|
|
)}
|
|
</td>
|
|
);
|
|
})}
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
{table
|
|
.getFooterGroups()
|
|
.flatMap((group) => group.headers)
|
|
.filter((header) => !!header.column.columnDef.footer).length >
|
|
0 && (
|
|
<tfoot>
|
|
{table.getFooterGroups().map((footerGroup) => (
|
|
<tr key={footerGroup.id}>
|
|
{footerGroup.headers.map((header) => (
|
|
<th key={header.id}>
|
|
{header.isPlaceholder
|
|
? null
|
|
: flexRender(
|
|
header.column.columnDef.footer,
|
|
header.getContext(),
|
|
)}
|
|
</th>
|
|
))}
|
|
</tr>
|
|
))}
|
|
</tfoot>
|
|
)}
|
|
</StyledTable>
|
|
</StyledTableScrollableContainer>
|
|
</StyledTableWithHeader>
|
|
);
|
|
}
|
|
|
|
export default Table;
|