Sammy/t 194 aau when i set sort back and forth the (#103)
* bugfix: use original row id in cells to make sure it rerenders * feature: implement multiple sorts * bugfix: recreate new array to make sure component rerenders * feature: orderBy is an array to keep orders * test: snapshot the searchTemplate methods * feature: remove the console log and return undefined * feature: use orderByTemplate instead of hardcoded orderBy * refactor: move sort and where filters helpers out of service * refactor: rename file helper * refactor: move assert function in test
This commit is contained in:
@ -87,7 +87,7 @@ const StyledTableScrollableContainer = styled.div`
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
function Table<TData, SortField extends string, FilterProperies>({
|
||||
function Table<TData extends { id: string }, SortField, FilterProperies>({
|
||||
data,
|
||||
columns,
|
||||
viewName,
|
||||
@ -140,7 +140,7 @@ function Table<TData, SortField extends string, FilterProperies>({
|
||||
<tr key={row.id} data-testid={`row-id-${row.index}`}>
|
||||
{row.getVisibleCells().map((cell) => {
|
||||
return (
|
||||
<td key={cell.id}>
|
||||
<td key={cell.id + row.original.id}>
|
||||
{flexRender(
|
||||
cell.column.columnDef.cell,
|
||||
cell.getContext(),
|
||||
|
||||
@ -84,10 +84,11 @@ export function FilterDropdownButton<FilterProperties>({
|
||||
label: selectedFilter.label,
|
||||
value: value.displayValue,
|
||||
icon: selectedFilter.icon,
|
||||
where: selectedFilter.whereTemplate(
|
||||
selectedFilterOperand,
|
||||
value.value,
|
||||
),
|
||||
where:
|
||||
selectedFilter.whereTemplate(
|
||||
selectedFilterOperand,
|
||||
value.value,
|
||||
) || ({} as FilterProperties),
|
||||
searchResultMapper: selectedFilter.searchResultMapper,
|
||||
});
|
||||
setIsUnfolded(false);
|
||||
|
||||
@ -41,7 +41,7 @@ const StyledCancelButton = styled.button`
|
||||
}
|
||||
`;
|
||||
|
||||
function SortAndFilterBar<SortField extends string, FilterProperties>({
|
||||
function SortAndFilterBar<SortField, FilterProperties>({
|
||||
sorts,
|
||||
onRemoveSort,
|
||||
filters,
|
||||
|
||||
@ -8,9 +8,9 @@ type OwnProps<SortField> = {
|
||||
availableSorts: SortType<SortField>[];
|
||||
};
|
||||
|
||||
const options: Array<SelectedSortType<string>['order']> = ['asc', 'desc'];
|
||||
const options: Array<SelectedSortType<any>['order']> = ['asc', 'desc'];
|
||||
|
||||
export function SortDropdownButton<SortField extends string>({
|
||||
export function SortDropdownButton<SortField>({
|
||||
isSortSelected,
|
||||
availableSorts,
|
||||
onSortSelect,
|
||||
|
||||
@ -66,7 +66,7 @@ const StyledFilters = styled.div`
|
||||
margin-right: ${(props) => props.theme.spacing(2)};
|
||||
`;
|
||||
|
||||
function TableHeader<SortField extends string, FilterProperties>({
|
||||
function TableHeader<SortField, FilterProperties>({
|
||||
viewName,
|
||||
viewIcon,
|
||||
availableSorts,
|
||||
@ -84,37 +84,40 @@ function TableHeader<SortField extends string, FilterProperties>({
|
||||
>([]);
|
||||
|
||||
const sortSelect = useCallback(
|
||||
(sort: SelectedSortType<SortField>) => {
|
||||
innerSetSorts([sort]);
|
||||
onSortsUpdate && onSortsUpdate([sort]);
|
||||
},
|
||||
[onSortsUpdate],
|
||||
);
|
||||
|
||||
const sortUnselect = useCallback(
|
||||
(sortId: string) => {
|
||||
const newSorts = [] as SelectedSortType<SortField>[];
|
||||
(newSort: SelectedSortType<SortField>) => {
|
||||
const newSorts = updateSortOrFilterByKey(sorts, newSort);
|
||||
innerSetSorts(newSorts);
|
||||
onSortsUpdate && onSortsUpdate(newSorts);
|
||||
},
|
||||
[onSortsUpdate],
|
||||
[onSortsUpdate, sorts],
|
||||
);
|
||||
|
||||
const sortUnselect = useCallback(
|
||||
(sortKey: string) => {
|
||||
const newSorts = sorts.filter((sort) => sort.key !== sortKey);
|
||||
innerSetSorts(newSorts);
|
||||
onSortsUpdate && onSortsUpdate(newSorts);
|
||||
},
|
||||
[onSortsUpdate, sorts],
|
||||
);
|
||||
|
||||
const filterSelect = useCallback(
|
||||
(filter: SelectedFilterType<FilterProperties>) => {
|
||||
innerSetFilters([filter]);
|
||||
onFiltersUpdate && onFiltersUpdate([filter]);
|
||||
const newFilters = updateSortOrFilterByKey(filters, filter);
|
||||
|
||||
innerSetFilters(newFilters);
|
||||
onFiltersUpdate && onFiltersUpdate(newFilters);
|
||||
},
|
||||
[onFiltersUpdate],
|
||||
[onFiltersUpdate, filters],
|
||||
);
|
||||
|
||||
const filterUnselect = useCallback(
|
||||
(filterId: SelectedFilterType<FilterProperties>['key']) => {
|
||||
const newFilters = [] as SelectedFilterType<FilterProperties>[];
|
||||
const newFilters = filters.filter((filter) => filter.key !== filterId);
|
||||
innerSetFilters(newFilters);
|
||||
onFiltersUpdate && onFiltersUpdate(newFilters);
|
||||
},
|
||||
[onFiltersUpdate],
|
||||
[onFiltersUpdate, filters],
|
||||
);
|
||||
|
||||
const filterSearch = useCallback(
|
||||
@ -161,3 +164,19 @@ function TableHeader<SortField extends string, FilterProperties>({
|
||||
}
|
||||
|
||||
export default TableHeader;
|
||||
|
||||
function updateSortOrFilterByKey<SortOrFilter extends { key: string }>(
|
||||
sorts: Readonly<SortOrFilter[]>,
|
||||
newSort: SortOrFilter,
|
||||
): SortOrFilter[] {
|
||||
const newSorts = [...sorts];
|
||||
const existingSortIndex = sorts.findIndex((sort) => sort.key === newSort.key);
|
||||
|
||||
if (existingSortIndex !== -1) {
|
||||
newSorts[existingSortIndex] = newSort;
|
||||
} else {
|
||||
newSorts.push(newSort);
|
||||
}
|
||||
|
||||
return newSorts;
|
||||
}
|
||||
|
||||
@ -25,12 +25,14 @@ export const RegularSortAndFilterBar = ({ removeFunction }: OwnProps) => {
|
||||
order: 'asc',
|
||||
key: 'test_sort',
|
||||
icon: <FaArrowDown />,
|
||||
_type: 'default_sort',
|
||||
},
|
||||
{
|
||||
label: 'Test sort 2',
|
||||
order: 'desc',
|
||||
key: 'test_sort_2',
|
||||
icon: <FaArrowDown />,
|
||||
_type: 'default_sort',
|
||||
},
|
||||
]}
|
||||
onRemoveSort={removeFunction}
|
||||
|
||||
@ -11,6 +11,7 @@ import {
|
||||
} from 'react-icons/fa';
|
||||
import { SortDropdownButton } from '../SortDropdownButton';
|
||||
import styled from '@emotion/styled';
|
||||
import { Order_By, People_Order_By } from '../../../../generated/graphql';
|
||||
|
||||
const component = {
|
||||
title: 'SortDropdownButton',
|
||||
@ -28,25 +29,31 @@ const availableSorts = [
|
||||
key: 'fullname',
|
||||
label: 'People',
|
||||
icon: <FaRegUser />,
|
||||
_type: 'custom_sort',
|
||||
orderByTemplate: () => ({ email: Order_By.Asc }),
|
||||
},
|
||||
{
|
||||
key: 'company_name',
|
||||
label: 'Company',
|
||||
icon: <FaRegBuilding />,
|
||||
_type: 'custom_sort',
|
||||
orderByTemplate: () => ({ email: Order_By.Asc }),
|
||||
},
|
||||
{
|
||||
key: 'email',
|
||||
label: 'Email',
|
||||
icon: <FaEnvelope />,
|
||||
_type: 'default_sort',
|
||||
},
|
||||
{ key: 'phone', label: 'Phone', icon: <FaPhone /> },
|
||||
{ key: 'phone', label: 'Phone', icon: <FaPhone />, _type: 'default_sort' },
|
||||
{
|
||||
key: 'created_at',
|
||||
label: 'Created at',
|
||||
icon: <FaCalendar />,
|
||||
_type: 'default_sort',
|
||||
},
|
||||
{ key: 'city', label: 'City', icon: <FaMapPin /> },
|
||||
] satisfies SortType[];
|
||||
{ key: 'city', label: 'City', icon: <FaMapPin />, _type: 'default_sort' },
|
||||
] satisfies SortType<People_Order_By>[];
|
||||
|
||||
const StyleDiv = styled.div`
|
||||
height: 200px;
|
||||
@ -57,7 +64,7 @@ export const RegularSortDropdownButton = ({ setSorts }: OwnProps) => {
|
||||
return (
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<StyleDiv>
|
||||
<SortDropdownButton
|
||||
<SortDropdownButton<People_Order_By>
|
||||
isSortSelected={true}
|
||||
availableSorts={availableSorts}
|
||||
onSortSelect={setSorts}
|
||||
|
||||
@ -12,11 +12,12 @@ const component = {
|
||||
export default component;
|
||||
|
||||
export const RegularTableHeader = () => {
|
||||
const availableSorts: Array<SortType> = [
|
||||
const availableSorts: Array<SortType<Record<'created_at', 'asc'>>> = [
|
||||
{
|
||||
key: 'created_at',
|
||||
label: 'Created at',
|
||||
icon: <FaCalendar />,
|
||||
_type: 'default_sort',
|
||||
},
|
||||
];
|
||||
return (
|
||||
|
||||
@ -19,6 +19,7 @@ it('Checks the default top option is Ascending', async () => {
|
||||
key: 'email',
|
||||
icon: <FaEnvelope />,
|
||||
order: 'asc',
|
||||
_type: 'default_sort',
|
||||
});
|
||||
});
|
||||
|
||||
@ -45,5 +46,6 @@ it('Checks the selection of Descending', async () => {
|
||||
key: 'email',
|
||||
icon: <FaEnvelope />,
|
||||
order: 'desc',
|
||||
_type: 'default_sort',
|
||||
});
|
||||
});
|
||||
|
||||
33
front/src/components/table/table-header/helpers.ts
Normal file
33
front/src/components/table/table-header/helpers.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { Order_By } from '../../../generated/graphql';
|
||||
import { SelectedFilterType, SelectedSortType } from './interface';
|
||||
|
||||
export const reduceFiltersToWhere = <T>(
|
||||
filters: Array<SelectedFilterType<T>>,
|
||||
): T => {
|
||||
const where = filters.reduce((acc, filter) => {
|
||||
const { where } = filter;
|
||||
return { ...acc, ...where };
|
||||
}, {} as T);
|
||||
return where;
|
||||
};
|
||||
|
||||
const mapOrderToOrder_By = (order: string) => {
|
||||
if (order === 'asc') return Order_By.Asc;
|
||||
return Order_By.Desc;
|
||||
};
|
||||
|
||||
export const defaultOrderByTemplateFactory =
|
||||
(key: string) => (order: string) => ({
|
||||
[key]: order,
|
||||
});
|
||||
|
||||
export const reduceSortsToOrderBy = <OrderByTemplate>(
|
||||
sorts: Array<SelectedSortType<OrderByTemplate>>,
|
||||
): OrderByTemplate[] => {
|
||||
const mappedSorts = sorts.map((sort) => {
|
||||
if (sort._type === 'custom_sort')
|
||||
return sort.orderByTemplate(mapOrderToOrder_By(sort.order));
|
||||
return defaultOrderByTemplateFactory(sort.key as string)(sort.order);
|
||||
});
|
||||
return mappedSorts as OrderByTemplate[];
|
||||
};
|
||||
@ -2,23 +2,27 @@ import { DocumentNode } from 'graphql';
|
||||
import { ReactNode } from 'react';
|
||||
import {
|
||||
Companies_Bool_Exp,
|
||||
Order_By,
|
||||
People_Bool_Exp,
|
||||
Users_Bool_Exp,
|
||||
} from '../../../generated/graphql';
|
||||
import { GraphqlQueryCompany } from '../../../interfaces/company.interface';
|
||||
import {
|
||||
SEARCH_COMPANY_QUERY,
|
||||
SEARCH_PEOPLE_QUERY,
|
||||
} from '../../../services/search/search';
|
||||
import { GraphqlQueryPerson } from '../../../interfaces/person.interface';
|
||||
|
||||
export type SortType<SortKey = string> = {
|
||||
label: string;
|
||||
key: SortKey;
|
||||
icon?: ReactNode;
|
||||
};
|
||||
export type SortType<OrderByTemplate> =
|
||||
| {
|
||||
_type: 'default_sort';
|
||||
label: string;
|
||||
key: keyof OrderByTemplate & string;
|
||||
icon?: ReactNode;
|
||||
}
|
||||
| {
|
||||
_type: 'custom_sort';
|
||||
label: string;
|
||||
key: string;
|
||||
icon?: ReactNode;
|
||||
orderByTemplate: (order: Order_By) => OrderByTemplate;
|
||||
};
|
||||
|
||||
export type SelectedSortType<SortField = string> = SortType<SortField> & {
|
||||
export type SelectedSortType<OrderByTemplate> = SortType<OrderByTemplate> & {
|
||||
order: 'asc' | 'desc';
|
||||
};
|
||||
|
||||
@ -30,7 +34,7 @@ export type FilterType<WhereTemplate, FilterValue = Record<string, any>> = {
|
||||
whereTemplate: (
|
||||
operand: FilterOperandType,
|
||||
value: FilterValue,
|
||||
) => WhereTemplate;
|
||||
) => WhereTemplate | undefined;
|
||||
searchQuery: DocumentNode;
|
||||
searchTemplate: (
|
||||
searchInput: string,
|
||||
@ -52,25 +56,3 @@ export type SelectedFilterType<WhereTemplate> = FilterType<WhereTemplate> & {
|
||||
operand: FilterOperandType;
|
||||
where: WhereTemplate;
|
||||
};
|
||||
|
||||
export function assertFilterUseCompanySearch<FilterValue>(
|
||||
filter: FilterType<People_Bool_Exp>,
|
||||
): filter is FilterType<People_Bool_Exp> & {
|
||||
searchResultMapper: (data: GraphqlQueryCompany) => {
|
||||
displayValue: string;
|
||||
value: FilterValue;
|
||||
};
|
||||
} {
|
||||
return filter.searchQuery === SEARCH_COMPANY_QUERY;
|
||||
}
|
||||
|
||||
export function assertFilterUsePeopleSearch<FilterValue>(
|
||||
filter: FilterType<People_Bool_Exp>,
|
||||
): filter is FilterType<People_Bool_Exp> & {
|
||||
searchResultMapper: (data: GraphqlQueryPerson) => {
|
||||
displayValue: string;
|
||||
value: FilterValue;
|
||||
};
|
||||
} {
|
||||
return filter.searchQuery === SEARCH_PEOPLE_QUERY;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user