mirror of
https://github.com/twentyhq/twenty.git
synced 2024-12-24 04:23:57 +03:00
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:
parent
f022bf8335
commit
b8cd842633
@ -87,7 +87,7 @@ const StyledTableScrollableContainer = styled.div`
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
function Table<TData, SortField extends string, FilterProperies>({
|
function Table<TData extends { id: string }, SortField, FilterProperies>({
|
||||||
data,
|
data,
|
||||||
columns,
|
columns,
|
||||||
viewName,
|
viewName,
|
||||||
@ -140,7 +140,7 @@ function Table<TData, SortField extends string, FilterProperies>({
|
|||||||
<tr key={row.id} data-testid={`row-id-${row.index}`}>
|
<tr key={row.id} data-testid={`row-id-${row.index}`}>
|
||||||
{row.getVisibleCells().map((cell) => {
|
{row.getVisibleCells().map((cell) => {
|
||||||
return (
|
return (
|
||||||
<td key={cell.id}>
|
<td key={cell.id + row.original.id}>
|
||||||
{flexRender(
|
{flexRender(
|
||||||
cell.column.columnDef.cell,
|
cell.column.columnDef.cell,
|
||||||
cell.getContext(),
|
cell.getContext(),
|
||||||
|
@ -84,10 +84,11 @@ export function FilterDropdownButton<FilterProperties>({
|
|||||||
label: selectedFilter.label,
|
label: selectedFilter.label,
|
||||||
value: value.displayValue,
|
value: value.displayValue,
|
||||||
icon: selectedFilter.icon,
|
icon: selectedFilter.icon,
|
||||||
where: selectedFilter.whereTemplate(
|
where:
|
||||||
|
selectedFilter.whereTemplate(
|
||||||
selectedFilterOperand,
|
selectedFilterOperand,
|
||||||
value.value,
|
value.value,
|
||||||
),
|
) || ({} as FilterProperties),
|
||||||
searchResultMapper: selectedFilter.searchResultMapper,
|
searchResultMapper: selectedFilter.searchResultMapper,
|
||||||
});
|
});
|
||||||
setIsUnfolded(false);
|
setIsUnfolded(false);
|
||||||
|
@ -41,7 +41,7 @@ const StyledCancelButton = styled.button`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
function SortAndFilterBar<SortField extends string, FilterProperties>({
|
function SortAndFilterBar<SortField, FilterProperties>({
|
||||||
sorts,
|
sorts,
|
||||||
onRemoveSort,
|
onRemoveSort,
|
||||||
filters,
|
filters,
|
||||||
|
@ -8,9 +8,9 @@ type OwnProps<SortField> = {
|
|||||||
availableSorts: SortType<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,
|
isSortSelected,
|
||||||
availableSorts,
|
availableSorts,
|
||||||
onSortSelect,
|
onSortSelect,
|
||||||
|
@ -66,7 +66,7 @@ const StyledFilters = styled.div`
|
|||||||
margin-right: ${(props) => props.theme.spacing(2)};
|
margin-right: ${(props) => props.theme.spacing(2)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
function TableHeader<SortField extends string, FilterProperties>({
|
function TableHeader<SortField, FilterProperties>({
|
||||||
viewName,
|
viewName,
|
||||||
viewIcon,
|
viewIcon,
|
||||||
availableSorts,
|
availableSorts,
|
||||||
@ -84,37 +84,40 @@ function TableHeader<SortField extends string, FilterProperties>({
|
|||||||
>([]);
|
>([]);
|
||||||
|
|
||||||
const sortSelect = useCallback(
|
const sortSelect = useCallback(
|
||||||
(sort: SelectedSortType<SortField>) => {
|
(newSort: SelectedSortType<SortField>) => {
|
||||||
innerSetSorts([sort]);
|
const newSorts = updateSortOrFilterByKey(sorts, newSort);
|
||||||
onSortsUpdate && onSortsUpdate([sort]);
|
|
||||||
},
|
|
||||||
[onSortsUpdate],
|
|
||||||
);
|
|
||||||
|
|
||||||
const sortUnselect = useCallback(
|
|
||||||
(sortId: string) => {
|
|
||||||
const newSorts = [] as SelectedSortType<SortField>[];
|
|
||||||
innerSetSorts(newSorts);
|
innerSetSorts(newSorts);
|
||||||
onSortsUpdate && onSortsUpdate(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(
|
const filterSelect = useCallback(
|
||||||
(filter: SelectedFilterType<FilterProperties>) => {
|
(filter: SelectedFilterType<FilterProperties>) => {
|
||||||
innerSetFilters([filter]);
|
const newFilters = updateSortOrFilterByKey(filters, filter);
|
||||||
onFiltersUpdate && onFiltersUpdate([filter]);
|
|
||||||
|
innerSetFilters(newFilters);
|
||||||
|
onFiltersUpdate && onFiltersUpdate(newFilters);
|
||||||
},
|
},
|
||||||
[onFiltersUpdate],
|
[onFiltersUpdate, filters],
|
||||||
);
|
);
|
||||||
|
|
||||||
const filterUnselect = useCallback(
|
const filterUnselect = useCallback(
|
||||||
(filterId: SelectedFilterType<FilterProperties>['key']) => {
|
(filterId: SelectedFilterType<FilterProperties>['key']) => {
|
||||||
const newFilters = [] as SelectedFilterType<FilterProperties>[];
|
const newFilters = filters.filter((filter) => filter.key !== filterId);
|
||||||
innerSetFilters(newFilters);
|
innerSetFilters(newFilters);
|
||||||
onFiltersUpdate && onFiltersUpdate(newFilters);
|
onFiltersUpdate && onFiltersUpdate(newFilters);
|
||||||
},
|
},
|
||||||
[onFiltersUpdate],
|
[onFiltersUpdate, filters],
|
||||||
);
|
);
|
||||||
|
|
||||||
const filterSearch = useCallback(
|
const filterSearch = useCallback(
|
||||||
@ -161,3 +164,19 @@ function TableHeader<SortField extends string, FilterProperties>({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default TableHeader;
|
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',
|
order: 'asc',
|
||||||
key: 'test_sort',
|
key: 'test_sort',
|
||||||
icon: <FaArrowDown />,
|
icon: <FaArrowDown />,
|
||||||
|
_type: 'default_sort',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Test sort 2',
|
label: 'Test sort 2',
|
||||||
order: 'desc',
|
order: 'desc',
|
||||||
key: 'test_sort_2',
|
key: 'test_sort_2',
|
||||||
icon: <FaArrowDown />,
|
icon: <FaArrowDown />,
|
||||||
|
_type: 'default_sort',
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
onRemoveSort={removeFunction}
|
onRemoveSort={removeFunction}
|
||||||
|
@ -11,6 +11,7 @@ import {
|
|||||||
} from 'react-icons/fa';
|
} from 'react-icons/fa';
|
||||||
import { SortDropdownButton } from '../SortDropdownButton';
|
import { SortDropdownButton } from '../SortDropdownButton';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
import { Order_By, People_Order_By } from '../../../../generated/graphql';
|
||||||
|
|
||||||
const component = {
|
const component = {
|
||||||
title: 'SortDropdownButton',
|
title: 'SortDropdownButton',
|
||||||
@ -28,25 +29,31 @@ const availableSorts = [
|
|||||||
key: 'fullname',
|
key: 'fullname',
|
||||||
label: 'People',
|
label: 'People',
|
||||||
icon: <FaRegUser />,
|
icon: <FaRegUser />,
|
||||||
|
_type: 'custom_sort',
|
||||||
|
orderByTemplate: () => ({ email: Order_By.Asc }),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'company_name',
|
key: 'company_name',
|
||||||
label: 'Company',
|
label: 'Company',
|
||||||
icon: <FaRegBuilding />,
|
icon: <FaRegBuilding />,
|
||||||
|
_type: 'custom_sort',
|
||||||
|
orderByTemplate: () => ({ email: Order_By.Asc }),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'email',
|
key: 'email',
|
||||||
label: 'Email',
|
label: 'Email',
|
||||||
icon: <FaEnvelope />,
|
icon: <FaEnvelope />,
|
||||||
|
_type: 'default_sort',
|
||||||
},
|
},
|
||||||
{ key: 'phone', label: 'Phone', icon: <FaPhone /> },
|
{ key: 'phone', label: 'Phone', icon: <FaPhone />, _type: 'default_sort' },
|
||||||
{
|
{
|
||||||
key: 'created_at',
|
key: 'created_at',
|
||||||
label: 'Created at',
|
label: 'Created at',
|
||||||
icon: <FaCalendar />,
|
icon: <FaCalendar />,
|
||||||
|
_type: 'default_sort',
|
||||||
},
|
},
|
||||||
{ key: 'city', label: 'City', icon: <FaMapPin /> },
|
{ key: 'city', label: 'City', icon: <FaMapPin />, _type: 'default_sort' },
|
||||||
] satisfies SortType[];
|
] satisfies SortType<People_Order_By>[];
|
||||||
|
|
||||||
const StyleDiv = styled.div`
|
const StyleDiv = styled.div`
|
||||||
height: 200px;
|
height: 200px;
|
||||||
@ -57,7 +64,7 @@ export const RegularSortDropdownButton = ({ setSorts }: OwnProps) => {
|
|||||||
return (
|
return (
|
||||||
<ThemeProvider theme={lightTheme}>
|
<ThemeProvider theme={lightTheme}>
|
||||||
<StyleDiv>
|
<StyleDiv>
|
||||||
<SortDropdownButton
|
<SortDropdownButton<People_Order_By>
|
||||||
isSortSelected={true}
|
isSortSelected={true}
|
||||||
availableSorts={availableSorts}
|
availableSorts={availableSorts}
|
||||||
onSortSelect={setSorts}
|
onSortSelect={setSorts}
|
||||||
|
@ -12,11 +12,12 @@ const component = {
|
|||||||
export default component;
|
export default component;
|
||||||
|
|
||||||
export const RegularTableHeader = () => {
|
export const RegularTableHeader = () => {
|
||||||
const availableSorts: Array<SortType> = [
|
const availableSorts: Array<SortType<Record<'created_at', 'asc'>>> = [
|
||||||
{
|
{
|
||||||
key: 'created_at',
|
key: 'created_at',
|
||||||
label: 'Created at',
|
label: 'Created at',
|
||||||
icon: <FaCalendar />,
|
icon: <FaCalendar />,
|
||||||
|
_type: 'default_sort',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
return (
|
return (
|
||||||
|
@ -19,6 +19,7 @@ it('Checks the default top option is Ascending', async () => {
|
|||||||
key: 'email',
|
key: 'email',
|
||||||
icon: <FaEnvelope />,
|
icon: <FaEnvelope />,
|
||||||
order: 'asc',
|
order: 'asc',
|
||||||
|
_type: 'default_sort',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -45,5 +46,6 @@ it('Checks the selection of Descending', async () => {
|
|||||||
key: 'email',
|
key: 'email',
|
||||||
icon: <FaEnvelope />,
|
icon: <FaEnvelope />,
|
||||||
order: 'desc',
|
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 { ReactNode } from 'react';
|
||||||
import {
|
import {
|
||||||
Companies_Bool_Exp,
|
Companies_Bool_Exp,
|
||||||
|
Order_By,
|
||||||
People_Bool_Exp,
|
People_Bool_Exp,
|
||||||
Users_Bool_Exp,
|
Users_Bool_Exp,
|
||||||
} from '../../../generated/graphql';
|
} 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> = {
|
export type SortType<OrderByTemplate> =
|
||||||
|
| {
|
||||||
|
_type: 'default_sort';
|
||||||
label: string;
|
label: string;
|
||||||
key: SortKey;
|
key: keyof OrderByTemplate & string;
|
||||||
icon?: ReactNode;
|
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';
|
order: 'asc' | 'desc';
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -30,7 +34,7 @@ export type FilterType<WhereTemplate, FilterValue = Record<string, any>> = {
|
|||||||
whereTemplate: (
|
whereTemplate: (
|
||||||
operand: FilterOperandType,
|
operand: FilterOperandType,
|
||||||
value: FilterValue,
|
value: FilterValue,
|
||||||
) => WhereTemplate;
|
) => WhereTemplate | undefined;
|
||||||
searchQuery: DocumentNode;
|
searchQuery: DocumentNode;
|
||||||
searchTemplate: (
|
searchTemplate: (
|
||||||
searchInput: string,
|
searchInput: string,
|
||||||
@ -52,25 +56,3 @@ export type SelectedFilterType<WhereTemplate> = FilterType<WhereTemplate> & {
|
|||||||
operand: FilterOperandType;
|
operand: FilterOperandType;
|
||||||
where: WhereTemplate;
|
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;
|
|
||||||
}
|
|
||||||
|
@ -5,12 +5,12 @@ import { useState, useCallback } from 'react';
|
|||||||
import {
|
import {
|
||||||
CompaniesSelectedSortType,
|
CompaniesSelectedSortType,
|
||||||
defaultOrderBy,
|
defaultOrderBy,
|
||||||
reduceSortsToOrderBy,
|
|
||||||
useCompaniesQuery,
|
useCompaniesQuery,
|
||||||
} from '../../services/companies';
|
} from '../../services/companies';
|
||||||
import Table from '../../components/table/Table';
|
import Table from '../../components/table/Table';
|
||||||
import { mapCompany } from '../../interfaces/company.interface';
|
import { mapCompany } from '../../interfaces/company.interface';
|
||||||
import { companiesColumns, sortsAvailable } from './companies-table';
|
import { companiesColumns, sortsAvailable } from './companies-table';
|
||||||
|
import { reduceSortsToOrderBy } from '../../components/table/table-header/helpers';
|
||||||
|
|
||||||
const StyledCompaniesContainer = styled.div`
|
const StyledCompaniesContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { createColumnHelper } from '@tanstack/react-table';
|
import { createColumnHelper } from '@tanstack/react-table';
|
||||||
import { Company } from '../../interfaces/company.interface';
|
import { Company } from '../../interfaces/company.interface';
|
||||||
import { OrderByFields, updateCompany } from '../../services/companies';
|
import { updateCompany } from '../../services/companies';
|
||||||
import ColumnHead from '../../components/table/ColumnHead';
|
import ColumnHead from '../../components/table/ColumnHead';
|
||||||
import Checkbox from '../../components/form/Checkbox';
|
import Checkbox from '../../components/form/Checkbox';
|
||||||
import CompanyChip from '../../components/chips/CompanyChip';
|
import CompanyChip from '../../components/chips/CompanyChip';
|
||||||
@ -17,21 +17,24 @@ import {
|
|||||||
} from 'react-icons/fa';
|
} from 'react-icons/fa';
|
||||||
import ClickableCell from '../../components/table/ClickableCell';
|
import ClickableCell from '../../components/table/ClickableCell';
|
||||||
import PersonChip from '../../components/chips/PersonChip';
|
import PersonChip from '../../components/chips/PersonChip';
|
||||||
import { SortType } from '../../components/table/table-header/interface';
|
|
||||||
import EditableChip from '../../components/table/editable-cell/EditableChip';
|
import EditableChip from '../../components/table/editable-cell/EditableChip';
|
||||||
|
import { SortType } from '../../components/table/table-header/interface';
|
||||||
|
import { Companies_Order_By } from '../../generated/graphql';
|
||||||
|
|
||||||
export const sortsAvailable = [
|
export const sortsAvailable = [
|
||||||
{
|
{
|
||||||
key: 'name',
|
key: 'name',
|
||||||
label: 'Name',
|
label: 'Name',
|
||||||
icon: undefined,
|
icon: undefined,
|
||||||
|
_type: 'default_sort',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'domain_name',
|
key: 'domain_name',
|
||||||
label: 'Domain',
|
label: 'Domain',
|
||||||
icon: undefined,
|
icon: undefined,
|
||||||
|
_type: 'default_sort',
|
||||||
},
|
},
|
||||||
] satisfies Array<SortType<OrderByFields>>;
|
] satisfies Array<SortType<Companies_Order_By>>;
|
||||||
|
|
||||||
const columnHelper = createColumnHelper<Company>();
|
const columnHelper = createColumnHelper<Company>();
|
||||||
export const companiesColumns = [
|
export const companiesColumns = [
|
||||||
|
@ -12,13 +12,15 @@ import { useCallback, useState } from 'react';
|
|||||||
import {
|
import {
|
||||||
PeopleSelectedSortType,
|
PeopleSelectedSortType,
|
||||||
defaultOrderBy,
|
defaultOrderBy,
|
||||||
reduceFiltersToWhere,
|
|
||||||
reduceSortsToOrderBy,
|
|
||||||
usePeopleQuery,
|
usePeopleQuery,
|
||||||
} from '../../services/people';
|
} from '../../services/people';
|
||||||
import { useSearch } from '../../services/search/search';
|
import { useSearch } from '../../services/search/search';
|
||||||
import { People_Bool_Exp } from '../../generated/graphql';
|
import { People_Bool_Exp } from '../../generated/graphql';
|
||||||
import { SelectedFilterType } from '../../components/table/table-header/interface';
|
import { SelectedFilterType } from '../../components/table/table-header/interface';
|
||||||
|
import {
|
||||||
|
reduceFiltersToWhere,
|
||||||
|
reduceSortsToOrderBy,
|
||||||
|
} from '../../components/table/table-header/helpers';
|
||||||
|
|
||||||
const StyledPeopleContainer = styled.div`
|
const StyledPeopleContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -93,3 +93,44 @@ Object {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`PeopleFilter should render the serch city with the searchValue 1`] = `
|
||||||
|
Object {
|
||||||
|
"city": Object {
|
||||||
|
"_ilike": "%Search value%",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`PeopleFilter should render the serch company_name with the searchValue 1`] = `
|
||||||
|
Object {
|
||||||
|
"name": Object {
|
||||||
|
"_ilike": "%Search value%",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`PeopleFilter should render the serch email with the searchValue 1`] = `
|
||||||
|
Object {
|
||||||
|
"email": Object {
|
||||||
|
"_ilike": "%Search value%",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`PeopleFilter should render the serch fullname with the searchValue 1`] = `
|
||||||
|
Object {
|
||||||
|
"_or": Array [
|
||||||
|
Object {
|
||||||
|
"firstname": Object {
|
||||||
|
"_ilike": "%Search value%",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"lastname": Object {
|
||||||
|
"_ilike": "%Search value%",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
@ -1,12 +1,37 @@
|
|||||||
import {
|
import { FilterType } from '../../../components/table/table-header/interface';
|
||||||
assertFilterUseCompanySearch,
|
import { People_Bool_Exp } from '../../../generated/graphql';
|
||||||
assertFilterUsePeopleSearch,
|
import { GraphqlQueryCompany } from '../../../interfaces/company.interface';
|
||||||
} from '../../../components/table/table-header/interface';
|
|
||||||
import { GraphqlQueryPerson } from '../../../interfaces/person.interface';
|
import { GraphqlQueryPerson } from '../../../interfaces/person.interface';
|
||||||
|
import {
|
||||||
|
SEARCH_COMPANY_QUERY,
|
||||||
|
SEARCH_PEOPLE_QUERY,
|
||||||
|
} from '../../../services/search/search';
|
||||||
import { mockCompanyData } from '../../companies/__stories__/mock-data';
|
import { mockCompanyData } from '../../companies/__stories__/mock-data';
|
||||||
import { defaultData } from '../default-data';
|
import { defaultData } from '../default-data';
|
||||||
import { availableFilters } from '../people-table';
|
import { availableFilters } from '../people-table';
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
const JohnDoeUser = defaultData.find(
|
const JohnDoeUser = defaultData.find(
|
||||||
(user) => user.email === 'john@linkedin.com',
|
(user) => user.email === 'john@linkedin.com',
|
||||||
) as GraphqlQueryPerson;
|
) as GraphqlQueryPerson;
|
||||||
@ -33,5 +58,8 @@ describe('PeopleFilter', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
it(`should render the serch ${filter.key} with the searchValue`, () => {
|
||||||
|
expect(filter.searchTemplate('Search value')).toMatchSnapshot();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -17,12 +17,16 @@ import CompanyChip from '../../components/chips/CompanyChip';
|
|||||||
import { GraphqlQueryPerson, Person } from '../../interfaces/person.interface';
|
import { GraphqlQueryPerson, Person } from '../../interfaces/person.interface';
|
||||||
import PipeChip from '../../components/chips/PipeChip';
|
import PipeChip from '../../components/chips/PipeChip';
|
||||||
import EditableText from '../../components/table/editable-cell/EditableText';
|
import EditableText from '../../components/table/editable-cell/EditableText';
|
||||||
import { OrderByFields, updatePerson } from '../../services/people';
|
import { updatePerson } from '../../services/people';
|
||||||
import {
|
import {
|
||||||
FilterType,
|
FilterType,
|
||||||
SortType,
|
SortType,
|
||||||
} from '../../components/table/table-header/interface';
|
} from '../../components/table/table-header/interface';
|
||||||
import { People_Bool_Exp } from '../../generated/graphql';
|
import {
|
||||||
|
Order_By,
|
||||||
|
People_Bool_Exp,
|
||||||
|
People_Order_By,
|
||||||
|
} from '../../generated/graphql';
|
||||||
import {
|
import {
|
||||||
SEARCH_COMPANY_QUERY,
|
SEARCH_COMPANY_QUERY,
|
||||||
SEARCH_PEOPLE_QUERY,
|
SEARCH_PEOPLE_QUERY,
|
||||||
@ -36,25 +40,44 @@ export const availableSorts = [
|
|||||||
key: 'fullname',
|
key: 'fullname',
|
||||||
label: 'People',
|
label: 'People',
|
||||||
icon: <FaRegUser />,
|
icon: <FaRegUser />,
|
||||||
|
_type: 'custom_sort',
|
||||||
|
orderByTemplate: (order: Order_By) => ({
|
||||||
|
firstname: order,
|
||||||
|
lastname: order,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'company_name',
|
key: 'company_name',
|
||||||
label: 'Company',
|
label: 'Company',
|
||||||
icon: <FaRegBuilding />,
|
icon: <FaRegBuilding />,
|
||||||
|
_type: 'custom_sort',
|
||||||
|
orderByTemplate: (order: Order_By) => ({ company: { name: order } }),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'email',
|
key: 'email',
|
||||||
label: 'Email',
|
label: 'Email',
|
||||||
icon: <FaEnvelope />,
|
icon: <FaEnvelope />,
|
||||||
|
_type: 'default_sort',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'phone',
|
||||||
|
label: 'Phone',
|
||||||
|
icon: <FaPhone />,
|
||||||
|
_type: 'default_sort',
|
||||||
},
|
},
|
||||||
{ key: 'phone', label: 'Phone', icon: <FaPhone /> },
|
|
||||||
{
|
{
|
||||||
key: 'created_at',
|
key: 'created_at',
|
||||||
label: 'Created at',
|
label: 'Created at',
|
||||||
icon: <FaCalendar />,
|
icon: <FaCalendar />,
|
||||||
|
_type: 'default_sort',
|
||||||
},
|
},
|
||||||
{ key: 'city', label: 'City', icon: <FaMapPin /> },
|
{
|
||||||
] satisfies Array<SortType<OrderByFields>>;
|
key: 'city',
|
||||||
|
label: 'City',
|
||||||
|
icon: <FaMapPin />,
|
||||||
|
_type: 'default_sort',
|
||||||
|
},
|
||||||
|
] satisfies Array<SortType<People_Order_By>>;
|
||||||
|
|
||||||
const fullnameFilter = {
|
const fullnameFilter = {
|
||||||
key: 'fullname',
|
key: 'fullname',
|
||||||
@ -80,8 +103,6 @@ const fullnameFilter = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
console.error(Error(`Unhandled operand: ${operand.keyWord}`));
|
|
||||||
return {};
|
|
||||||
},
|
},
|
||||||
searchQuery: SEARCH_PEOPLE_QUERY,
|
searchQuery: SEARCH_PEOPLE_QUERY,
|
||||||
searchTemplate: (searchInput: string) => ({
|
searchTemplate: (searchInput: string) => ({
|
||||||
@ -116,8 +137,6 @@ const companyFilter = {
|
|||||||
_not: { company: { name: { _eq: companyName } } },
|
_not: { company: { name: { _eq: companyName } } },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
console.error(Error(`Unhandled operand: ${operand.keyWord}`));
|
|
||||||
return {};
|
|
||||||
},
|
},
|
||||||
searchQuery: SEARCH_COMPANY_QUERY,
|
searchQuery: SEARCH_COMPANY_QUERY,
|
||||||
searchTemplate: (searchInput: string) => ({
|
searchTemplate: (searchInput: string) => ({
|
||||||
@ -149,8 +168,6 @@ const emailFilter = {
|
|||||||
_not: { email: { _eq: email } },
|
_not: { email: { _eq: email } },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
console.error(Error(`Unhandled operand: ${operand.keyWord}`));
|
|
||||||
return {};
|
|
||||||
},
|
},
|
||||||
searchQuery: SEARCH_PEOPLE_QUERY,
|
searchQuery: SEARCH_PEOPLE_QUERY,
|
||||||
searchTemplate: (searchInput: string) => ({
|
searchTemplate: (searchInput: string) => ({
|
||||||
@ -182,8 +199,6 @@ const cityFilter = {
|
|||||||
_not: { city: { _eq: city } },
|
_not: { city: { _eq: city } },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
console.error(Error(`Unhandled operand: ${operand.keyWord}`));
|
|
||||||
return {};
|
|
||||||
},
|
},
|
||||||
searchQuery: SEARCH_PEOPLE_QUERY,
|
searchQuery: SEARCH_PEOPLE_QUERY,
|
||||||
searchTemplate: (searchInput: string) => ({
|
searchTemplate: (searchInput: string) => ({
|
||||||
|
@ -1,12 +1,18 @@
|
|||||||
import { CompaniesSelectedSortType, reduceSortsToOrderBy } from './select';
|
import { reduceSortsToOrderBy } from '../../components/table/table-header/helpers';
|
||||||
|
import { CompaniesSelectedSortType } from './select';
|
||||||
|
|
||||||
describe('reduceSortsToOrderBy', () => {
|
describe('reduceSortsToOrderBy', () => {
|
||||||
it('should return an array of objects with the id as key and the order as value', () => {
|
it('should return an array of objects with the id as key and the order as value', () => {
|
||||||
const sorts = [
|
const sorts = [
|
||||||
{ key: 'name', label: 'name', order: 'asc' },
|
{ key: 'name', label: 'name', order: 'asc', _type: 'default_sort' },
|
||||||
{ key: 'domain_name', label: 'domain_name', order: 'desc' },
|
{
|
||||||
|
key: 'domain_name',
|
||||||
|
label: 'domain_name',
|
||||||
|
order: 'desc',
|
||||||
|
_type: 'default_sort',
|
||||||
|
},
|
||||||
] satisfies CompaniesSelectedSortType[];
|
] satisfies CompaniesSelectedSortType[];
|
||||||
const result = reduceSortsToOrderBy(sorts);
|
const result = reduceSortsToOrderBy(sorts);
|
||||||
expect(result).toEqual([{ name: 'asc', domain_name: 'desc' }]);
|
expect(result).toEqual([{ name: 'asc' }, { domain_name: 'desc' }]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -3,25 +3,7 @@ import { Order_By, Companies_Order_By } from '../../generated/graphql';
|
|||||||
import { GraphqlQueryCompany } from '../../interfaces/company.interface';
|
import { GraphqlQueryCompany } from '../../interfaces/company.interface';
|
||||||
import { SelectedSortType } from '../../components/table/table-header/interface';
|
import { SelectedSortType } from '../../components/table/table-header/interface';
|
||||||
|
|
||||||
export type OrderByFields = keyof Companies_Order_By | 'domain_name' | 'name';
|
export type CompaniesSelectedSortType = SelectedSortType<Companies_Order_By>;
|
||||||
|
|
||||||
export type CompaniesSelectedSortType = SelectedSortType<OrderByFields>;
|
|
||||||
|
|
||||||
const mapOrder = (order: 'asc' | 'desc'): Order_By => {
|
|
||||||
return order === 'asc' ? Order_By.Asc : Order_By.Desc;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const reduceSortsToOrderBy = (
|
|
||||||
sorts: Array<CompaniesSelectedSortType>,
|
|
||||||
): Companies_Order_By[] => {
|
|
||||||
const mappedSorts = sorts.reduce((acc, sort) => {
|
|
||||||
const id = sort.key;
|
|
||||||
const order = mapOrder(sort.order);
|
|
||||||
acc[id] = order;
|
|
||||||
return acc;
|
|
||||||
}, {} as Companies_Order_By);
|
|
||||||
return [mappedSorts];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const GET_COMPANIES = gql`
|
export const GET_COMPANIES = gql`
|
||||||
query GetCompanies($orderBy: [companies_order_by!]) {
|
query GetCompanies($orderBy: [companies_order_by!]) {
|
||||||
|
@ -1,12 +1,23 @@
|
|||||||
import { PeopleSelectedSortType, reduceSortsToOrderBy } from './select';
|
import { reduceSortsToOrderBy } from '../../components/table/table-header/helpers';
|
||||||
|
import { PeopleSelectedSortType } from './select';
|
||||||
|
|
||||||
describe('reduceSortsToOrderBy', () => {
|
describe('reduceSortsToOrderBy', () => {
|
||||||
it('should return an array of objects with the id as key and the order as value', () => {
|
it('should return an array of objects with the id as key and the order as value', () => {
|
||||||
const sorts = [
|
const sorts = [
|
||||||
{ key: 'firstname', label: 'firstname', order: 'asc' },
|
{
|
||||||
{ key: 'lastname', label: 'lastname', order: 'desc' },
|
key: 'firstname',
|
||||||
|
label: 'firstname',
|
||||||
|
order: 'asc',
|
||||||
|
_type: 'default_sort',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'lastname',
|
||||||
|
label: 'lastname',
|
||||||
|
order: 'desc',
|
||||||
|
_type: 'default_sort',
|
||||||
|
},
|
||||||
] satisfies PeopleSelectedSortType[];
|
] satisfies PeopleSelectedSortType[];
|
||||||
const result = reduceSortsToOrderBy(sorts);
|
const result = reduceSortsToOrderBy(sorts);
|
||||||
expect(result).toEqual([{ firstname: 'asc', lastname: 'desc' }]);
|
expect(result).toEqual([{ firstname: 'asc' }, { lastname: 'desc' }]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -5,47 +5,9 @@ import {
|
|||||||
People_Bool_Exp,
|
People_Bool_Exp,
|
||||||
People_Order_By,
|
People_Order_By,
|
||||||
} from '../../generated/graphql';
|
} from '../../generated/graphql';
|
||||||
import {
|
import { SelectedSortType } from '../../components/table/table-header/interface';
|
||||||
SelectedFilterType,
|
|
||||||
SelectedSortType,
|
|
||||||
} from '../../components/table/table-header/interface';
|
|
||||||
|
|
||||||
export type OrderByFields = keyof People_Order_By | 'fullname' | 'company_name';
|
export type PeopleSelectedSortType = SelectedSortType<People_Order_By>;
|
||||||
|
|
||||||
export type PeopleSelectedSortType = SelectedSortType<OrderByFields>;
|
|
||||||
|
|
||||||
const mapOrder = (order: 'asc' | 'desc'): Order_By => {
|
|
||||||
return order === 'asc' ? Order_By.Asc : Order_By.Desc;
|
|
||||||
};
|
|
||||||
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const reduceSortsToOrderBy = (
|
|
||||||
sorts: Array<PeopleSelectedSortType>,
|
|
||||||
): People_Order_By[] => {
|
|
||||||
const mappedSorts = sorts.reduce((acc, sort) => {
|
|
||||||
const id = sort.key;
|
|
||||||
const order = mapOrder(sort.order);
|
|
||||||
if (id === 'fullname') {
|
|
||||||
acc['firstname'] = order;
|
|
||||||
acc['lastname'] = order;
|
|
||||||
} else if (id === 'company_name') {
|
|
||||||
acc['company'] = { name: order };
|
|
||||||
} else {
|
|
||||||
acc[id] = order;
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, {} as People_Order_By);
|
|
||||||
return [mappedSorts];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const GET_PEOPLE = gql`
|
export const GET_PEOPLE = gql`
|
||||||
query GetPeople(
|
query GetPeople(
|
||||||
|
Loading…
Reference in New Issue
Block a user