Sammy/t 240 frontend filtering search is refactored (#122)
* refactor: use AnyEntity instead of any * refactor: remove any and brand company type * refactor: add typename for user and people * bugfix: await company to be created before displaying it * feature: await deletion before removing the lines * refactor: remove default tyep for filters * refactor: remove default type AnyEntity * refactor: remove USers from filterable types * refactor: do not depend on Filter types in Table * Add tests --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -3,14 +3,15 @@ import DropdownButton from './DropdownButton';
|
||||
import {
|
||||
FilterConfigType,
|
||||
FilterOperandType,
|
||||
FilterableFieldsType,
|
||||
SearchConfigType,
|
||||
SearchableType,
|
||||
SelectedFilterType,
|
||||
} from './interface';
|
||||
|
||||
type OwnProps = {
|
||||
type OwnProps<TData extends FilterableFieldsType> = {
|
||||
isFilterSelected: boolean;
|
||||
availableFilters: FilterConfigType[];
|
||||
availableFilters: FilterConfigType<TData>[];
|
||||
filterSearchResults?: {
|
||||
results: {
|
||||
render: (value: SearchableType) => string;
|
||||
@ -18,30 +19,30 @@ type OwnProps = {
|
||||
}[];
|
||||
loading: boolean;
|
||||
};
|
||||
onFilterSelect: (filter: SelectedFilterType) => void;
|
||||
onFilterSelect: (filter: SelectedFilterType<TData>) => void;
|
||||
onFilterSearch: (
|
||||
filter: SearchConfigType<any> | null,
|
||||
searchValue: string,
|
||||
) => void;
|
||||
};
|
||||
|
||||
export function FilterDropdownButton({
|
||||
export const FilterDropdownButton = <TData extends FilterableFieldsType>({
|
||||
availableFilters,
|
||||
filterSearchResults,
|
||||
onFilterSearch,
|
||||
onFilterSelect,
|
||||
isFilterSelected,
|
||||
}: OwnProps) {
|
||||
}: OwnProps<TData>) => {
|
||||
const [isUnfolded, setIsUnfolded] = useState(false);
|
||||
|
||||
const [isOptionUnfolded, setIsOptionUnfolded] = useState(false);
|
||||
|
||||
const [selectedFilter, setSelectedFilter] = useState<
|
||||
FilterConfigType | undefined
|
||||
FilterConfigType<TData> | undefined
|
||||
>(undefined);
|
||||
|
||||
const [selectedFilterOperand, setSelectedFilterOperand] = useState<
|
||||
FilterOperandType | undefined
|
||||
FilterOperandType<TData> | undefined
|
||||
>(undefined);
|
||||
|
||||
const resetState = useCallback(() => {
|
||||
@ -66,9 +67,9 @@ export function FilterDropdownButton({
|
||||
);
|
||||
|
||||
const renderSearchResults = (
|
||||
filterSearchResults: NonNullable<OwnProps['filterSearchResults']>,
|
||||
selectedFilter: FilterConfigType,
|
||||
selectedFilterOperand: FilterOperandType,
|
||||
filterSearchResults: NonNullable<OwnProps<TData>['filterSearchResults']>,
|
||||
selectedFilter: FilterConfigType<TData>,
|
||||
selectedFilterOperand: FilterOperandType<TData>,
|
||||
) => {
|
||||
if (filterSearchResults.loading) {
|
||||
return (
|
||||
@ -114,8 +115,8 @@ export function FilterDropdownButton({
|
||||
));
|
||||
|
||||
function renderFilterDropdown(
|
||||
selectedFilter: FilterConfigType,
|
||||
selectedFilterOperand: FilterOperandType,
|
||||
selectedFilter: FilterConfigType<TData>,
|
||||
selectedFilterOperand: FilterOperandType<TData>,
|
||||
) {
|
||||
return (
|
||||
<>
|
||||
@ -161,4 +162,4 @@ export function FilterDropdownButton({
|
||||
: renderSelectFilterITems}
|
||||
</DropdownButton>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@ -1,13 +1,17 @@
|
||||
import styled from '@emotion/styled';
|
||||
import SortOrFilterChip from './SortOrFilterChip';
|
||||
import { FaArrowDown, FaArrowUp } from 'react-icons/fa';
|
||||
import { SelectedFilterType, SelectedSortType } from './interface';
|
||||
import {
|
||||
FilterableFieldsType,
|
||||
SelectedFilterType,
|
||||
SelectedSortType,
|
||||
} from './interface';
|
||||
|
||||
type OwnProps<SortField> = {
|
||||
type OwnProps<SortField, TData extends FilterableFieldsType> = {
|
||||
sorts: Array<SelectedSortType<SortField>>;
|
||||
onRemoveSort: (sortId: SelectedSortType<SortField>['key']) => void;
|
||||
filters: Array<SelectedFilterType>;
|
||||
onRemoveFilter: (filterId: SelectedFilterType['key']) => void;
|
||||
filters: Array<SelectedFilterType<TData>>;
|
||||
onRemoveFilter: (filterId: SelectedFilterType<TData>['key']) => void;
|
||||
onCancelClick: () => void;
|
||||
};
|
||||
|
||||
@ -40,13 +44,13 @@ const StyledCancelButton = styled.button`
|
||||
}
|
||||
`;
|
||||
|
||||
function SortAndFilterBar<SortField>({
|
||||
function SortAndFilterBar<SortField, TData extends FilterableFieldsType>({
|
||||
sorts,
|
||||
onRemoveSort,
|
||||
filters,
|
||||
onRemoveFilter,
|
||||
onCancelClick,
|
||||
}: OwnProps<SortField>) {
|
||||
}: OwnProps<SortField, TData>) {
|
||||
return (
|
||||
<StyledBar>
|
||||
{sorts.map((sort) => {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import styled from '@emotion/styled';
|
||||
import {
|
||||
FilterConfigType,
|
||||
FilterableFieldsType,
|
||||
SearchConfigType,
|
||||
SearchableType,
|
||||
SelectedFilterType,
|
||||
@ -12,11 +13,11 @@ import { SortDropdownButton } from './SortDropdownButton';
|
||||
import { FilterDropdownButton } from './FilterDropdownButton';
|
||||
import SortAndFilterBar from './SortAndFilterBar';
|
||||
|
||||
type OwnProps<SortField> = {
|
||||
type OwnProps<SortField, TData extends FilterableFieldsType> = {
|
||||
viewName: string;
|
||||
viewIcon?: ReactNode;
|
||||
availableSorts?: Array<SortType<SortField>>;
|
||||
availableFilters?: FilterConfigType[];
|
||||
availableFilters?: FilterConfigType<TData>[];
|
||||
filterSearchResults?: {
|
||||
results: {
|
||||
render: (value: SearchableType) => string;
|
||||
@ -25,7 +26,7 @@ type OwnProps<SortField> = {
|
||||
loading: boolean;
|
||||
};
|
||||
onSortsUpdate?: (sorts: Array<SelectedSortType<SortField>>) => void;
|
||||
onFiltersUpdate?: (sorts: Array<SelectedFilterType>) => void;
|
||||
onFiltersUpdate?: (sorts: Array<SelectedFilterType<TData>>) => void;
|
||||
onFilterSearch?: (
|
||||
filter: SearchConfigType<any> | null,
|
||||
searchValue: string,
|
||||
@ -68,7 +69,7 @@ const StyledFilters = styled.div`
|
||||
margin-right: ${(props) => props.theme.spacing(2)};
|
||||
`;
|
||||
|
||||
function TableHeader<SortField>({
|
||||
function TableHeader<SortField, TData extends FilterableFieldsType>({
|
||||
viewName,
|
||||
viewIcon,
|
||||
availableSorts,
|
||||
@ -77,11 +78,13 @@ function TableHeader<SortField>({
|
||||
onSortsUpdate,
|
||||
onFiltersUpdate,
|
||||
onFilterSearch,
|
||||
}: OwnProps<SortField>) {
|
||||
}: OwnProps<SortField, TData>) {
|
||||
const [sorts, innerSetSorts] = useState<Array<SelectedSortType<SortField>>>(
|
||||
[],
|
||||
);
|
||||
const [filters, innerSetFilters] = useState<Array<SelectedFilterType>>([]);
|
||||
const [filters, innerSetFilters] = useState<Array<SelectedFilterType<TData>>>(
|
||||
[],
|
||||
);
|
||||
|
||||
const sortSelect = useCallback(
|
||||
(newSort: SelectedSortType<SortField>) => {
|
||||
@ -102,7 +105,7 @@ function TableHeader<SortField>({
|
||||
);
|
||||
|
||||
const filterSelect = useCallback(
|
||||
(filter: SelectedFilterType) => {
|
||||
(filter: SelectedFilterType<TData>) => {
|
||||
const newFilters = updateSortOrFilterByKey(filters, filter);
|
||||
|
||||
innerSetFilters(newFilters);
|
||||
@ -112,7 +115,7 @@ function TableHeader<SortField>({
|
||||
);
|
||||
|
||||
const filterUnselect = useCallback(
|
||||
(filterId: SelectedFilterType['key']) => {
|
||||
(filterId: SelectedFilterType<TData>['key']) => {
|
||||
const newFilters = filters.filter((filter) => filter.key !== filterId);
|
||||
innerSetFilters(newFilters);
|
||||
onFiltersUpdate && onFiltersUpdate(newFilters);
|
||||
|
||||
@ -2,7 +2,7 @@ import { ThemeProvider } from '@emotion/react';
|
||||
import { lightTheme } from '../../../../layout/styles/themes';
|
||||
import { FilterDropdownButton } from '../FilterDropdownButton';
|
||||
import styled from '@emotion/styled';
|
||||
import { FilterConfigType, SelectedFilterType } from '../interface';
|
||||
import { FilterableFieldsType, SelectedFilterType } from '../interface';
|
||||
import { useCallback, useState } from 'react';
|
||||
import {
|
||||
SEARCH_PEOPLE_QUERY,
|
||||
@ -20,7 +20,7 @@ const component = {
|
||||
|
||||
export default component;
|
||||
|
||||
type OwnProps<FilterProperties> = {
|
||||
type OwnProps<FilterProperties extends FilterableFieldsType> = {
|
||||
setFilter: (filters: SelectedFilterType<FilterProperties>) => void;
|
||||
};
|
||||
|
||||
@ -98,8 +98,8 @@ const InnerRegularFilterDropdownButton = ({
|
||||
);
|
||||
return (
|
||||
<StyleDiv>
|
||||
<FilterDropdownButton
|
||||
availableFilters={availableFilters as FilterConfigType[]}
|
||||
<FilterDropdownButton<Person>
|
||||
availableFilters={availableFilters}
|
||||
isFilterSelected={true}
|
||||
onFilterSelect={outerSetFilters}
|
||||
filterSearchResults={filterSearchResults}
|
||||
|
||||
@ -3,6 +3,7 @@ import { ThemeProvider } from '@emotion/react';
|
||||
import { lightTheme } from '../../../../layout/styles/themes';
|
||||
import { FaArrowDown } from 'react-icons/fa';
|
||||
import { SelectedFilterType } from '../interface';
|
||||
import { Person } from '../../../../interfaces/person.interface';
|
||||
|
||||
const component = {
|
||||
title: 'SortAndFilterBar',
|
||||
@ -20,6 +21,31 @@ export const RegularSortAndFilterBar = ({
|
||||
removeFunction,
|
||||
cancelFunction,
|
||||
}: OwnProps) => {
|
||||
const personFilter = {
|
||||
label: 'People',
|
||||
operand: {
|
||||
label: 'Include',
|
||||
id: 'include',
|
||||
whereTemplate: (person: Person) => {
|
||||
return { email: { _eq: person.email } };
|
||||
},
|
||||
},
|
||||
key: 'test_filter',
|
||||
icon: <FaArrowDown />,
|
||||
displayValue: 'john@doedoe.com',
|
||||
value: {
|
||||
__typename: 'people',
|
||||
id: 'test',
|
||||
email: 'john@doedoe.com',
|
||||
firstname: 'John',
|
||||
lastname: 'Doe',
|
||||
phone: '123456789',
|
||||
company: null,
|
||||
creationDate: new Date(),
|
||||
pipes: null,
|
||||
city: 'Paris',
|
||||
},
|
||||
} satisfies SelectedFilterType<Person, Person>;
|
||||
return (
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<SortAndFilterBar
|
||||
@ -42,32 +68,7 @@ export const RegularSortAndFilterBar = ({
|
||||
onRemoveSort={removeFunction}
|
||||
onRemoveFilter={removeFunction}
|
||||
onCancelClick={cancelFunction}
|
||||
filters={[
|
||||
{
|
||||
label: 'People',
|
||||
operand: {
|
||||
label: 'Include',
|
||||
id: 'include',
|
||||
whereTemplate: (person) => {
|
||||
return { email: { _eq: person.email } };
|
||||
},
|
||||
},
|
||||
key: 'test_filter',
|
||||
icon: <FaArrowDown />,
|
||||
displayValue: 'john@doedoe.com',
|
||||
value: {
|
||||
id: 'test',
|
||||
email: 'john@doedoe.com',
|
||||
firstname: 'John',
|
||||
lastname: 'Doe',
|
||||
phone: '123456789',
|
||||
company: null,
|
||||
creationDate: new Date(),
|
||||
pipe: null,
|
||||
city: 'Paris',
|
||||
},
|
||||
} satisfies SelectedFilterType,
|
||||
]}
|
||||
filters={[personFilter] as SelectedFilterType<Person>[]}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
@ -1,7 +1,16 @@
|
||||
import { Order_By } from '../../../generated/graphql';
|
||||
import { BoolExpType, SelectedFilterType, SelectedSortType } from './interface';
|
||||
import {
|
||||
BoolExpType,
|
||||
FilterWhereType,
|
||||
FilterableFieldsType,
|
||||
SelectedFilterType,
|
||||
SelectedSortType,
|
||||
} from './interface';
|
||||
|
||||
export const reduceFiltersToWhere = <ValueType, WhereTemplateType>(
|
||||
export const reduceFiltersToWhere = <
|
||||
ValueType extends FilterableFieldsType,
|
||||
WhereTemplateType extends FilterWhereType,
|
||||
>(
|
||||
filters: Array<SelectedFilterType<ValueType, WhereTemplateType>>,
|
||||
): BoolExpType<WhereTemplateType> => {
|
||||
const where = filters.reduce((acc, filter) => {
|
||||
|
||||
@ -35,8 +35,13 @@ export type SelectedSortType<OrderByTemplate> = SortType<OrderByTemplate> & {
|
||||
order: 'asc' | 'desc';
|
||||
};
|
||||
|
||||
type AnyEntity = {
|
||||
id: string;
|
||||
__typename: string;
|
||||
} & Record<string, any>;
|
||||
|
||||
export type FilterableFieldsType = Person | Company;
|
||||
export type FilterWhereType = Person | Company | User;
|
||||
export type FilterWhereType = Person | Company | User | AnyEntity;
|
||||
|
||||
type FilterConfigGqlType<WhereType> = WhereType extends Company
|
||||
? GraphqlQueryCompany
|
||||
@ -50,9 +55,14 @@ export type BoolExpType<T> = T extends Company
|
||||
? Companies_Bool_Exp
|
||||
: T extends Person
|
||||
? People_Bool_Exp
|
||||
: T extends User
|
||||
? Users_Bool_Exp
|
||||
: never;
|
||||
|
||||
export type FilterConfigType<FilteredType = any, WhereType = any> = {
|
||||
export type FilterConfigType<
|
||||
FilteredType extends FilterableFieldsType,
|
||||
WhereType extends FilterWhereType = any,
|
||||
> = {
|
||||
key: string;
|
||||
label: string;
|
||||
icon: ReactNode;
|
||||
@ -77,17 +87,33 @@ export type SearchConfigType<SearchType extends SearchableType> = {
|
||||
};
|
||||
|
||||
export type FilterOperandType<
|
||||
FilteredType = FilterableFieldsType,
|
||||
WhereType = any,
|
||||
FilteredType extends FilterableFieldsType,
|
||||
WhereType extends FilterWhereType = AnyEntity,
|
||||
> =
|
||||
| FilterOperandExactMatchType<FilteredType, WhereType>
|
||||
| FilterOperandComparativeType<FilteredType, WhereType>;
|
||||
|
||||
type FilterOperandExactMatchType<
|
||||
FilteredType extends FilterableFieldsType,
|
||||
WhereType extends FilterWhereType,
|
||||
> = {
|
||||
label: string;
|
||||
id: string;
|
||||
label: 'Equal' | 'Not equal';
|
||||
id: 'equal' | 'not-equal';
|
||||
whereTemplate: (value: WhereType) => BoolExpType<FilteredType>;
|
||||
};
|
||||
|
||||
type FilterOperandComparativeType<
|
||||
FilteredType extends FilterableFieldsType,
|
||||
WhereType extends FilterWhereType,
|
||||
> = {
|
||||
label: 'Like' | 'Not like' | 'Include';
|
||||
id: 'like' | 'not_like' | 'include';
|
||||
whereTemplate: (value: WhereType) => BoolExpType<FilteredType>;
|
||||
};
|
||||
|
||||
export type SelectedFilterType<
|
||||
FilteredType = FilterableFieldsType,
|
||||
WhereType = any,
|
||||
FilteredType extends FilterableFieldsType,
|
||||
WhereType extends FilterWhereType = AnyEntity,
|
||||
> = {
|
||||
key: string;
|
||||
value: WhereType;
|
||||
|
||||
Reference in New Issue
Block a user