Sammy/t 134 i see all filters in the dropdown (#78)

* feature: add filter dropdown

* test: add story for FilterDropdown

* feature: display filterOperand on top of dropdown

* feature: display filter operand

* feature: fix index and display selected filter

* refactor: set TopOption button inside dropdown file

* feature: move availableFilters outside the fitler component

* refactor: make the available sorts and filter optionnal

* refactor: rename availableSorts

* feature: add a resetState property on onOutsideClick

* feature: add filters and set  filters on Dropdown component

* feature: set filters on click in dropdown

* test: verify button is active after filters are set

* feature: display sorts and filters

* refactor: move SelectedFilters in SortAndFilter

* refactor: move SelectedFilters in dedicated file

* refactor: remove Id and use Key
This commit is contained in:
Sammy Teillet
2023-04-26 16:19:34 +02:00
committed by GitHub
parent 1c8a4058c3
commit 5aec7ca730
17 changed files with 521 additions and 70 deletions

View File

@ -9,7 +9,11 @@ import {
import TableHeader from './table-header/TableHeader'; import TableHeader from './table-header/TableHeader';
import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { IconProp } from '@fortawesome/fontawesome-svg-core';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { SelectedSortType, SortType } from './table-header/SortAndFilterBar'; import {
FilterType,
SelectedSortType,
SortType,
} from './table-header/interface';
type OwnProps<TData, SortField> = { type OwnProps<TData, SortField> = {
data: Array<TData>; data: Array<TData>;
@ -17,7 +21,8 @@ type OwnProps<TData, SortField> = {
viewName: string; viewName: string;
viewIcon?: IconProp; viewIcon?: IconProp;
onSortsUpdate?: (sorts: Array<SelectedSortType<SortField>>) => void; onSortsUpdate?: (sorts: Array<SelectedSortType<SortField>>) => void;
sortsAvailable?: Array<SortType<SortField>>; availableSorts?: Array<SortType<SortField>>;
availableFilters?: FilterType[];
}; };
const StyledTable = styled.table` const StyledTable = styled.table`
@ -77,7 +82,8 @@ function Table<TData, SortField extends string>({
viewName, viewName,
viewIcon, viewIcon,
onSortsUpdate, onSortsUpdate,
sortsAvailable, availableSorts,
availableFilters,
}: OwnProps<TData, SortField>) { }: OwnProps<TData, SortField>) {
const table = useReactTable({ const table = useReactTable({
data, data,
@ -91,7 +97,8 @@ function Table<TData, SortField extends string>({
viewName={viewName} viewName={viewName}
viewIcon={viewIcon} viewIcon={viewIcon}
onSortsUpdate={onSortsUpdate} onSortsUpdate={onSortsUpdate}
sortsAvailable={sortsAvailable || []} availableSorts={availableSorts}
availableFilters={availableFilters}
/> />
<StyledTableScrollableContainer> <StyledTableScrollableContainer>
<StyledTable> <StyledTable>

View File

@ -2,6 +2,8 @@ import styled from '@emotion/styled';
import { useRef, ReactNode } from 'react'; import { useRef, ReactNode } from 'react';
import { useOutsideAlerter } from '../../../hooks/useOutsideAlerter'; import { useOutsideAlerter } from '../../../hooks/useOutsideAlerter';
import { modalBackground } from '../../../layout/styles/themes'; import { modalBackground } from '../../../layout/styles/themes';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faAngleDown } from '@fortawesome/pro-regular-svg-icons';
type OwnProps = { type OwnProps = {
label: string; label: string;
@ -9,6 +11,7 @@ type OwnProps = {
children?: ReactNode; children?: ReactNode;
isUnfolded?: boolean; isUnfolded?: boolean;
setIsUnfolded?: React.Dispatch<React.SetStateAction<boolean>>; setIsUnfolded?: React.Dispatch<React.SetStateAction<boolean>>;
resetState?: () => void;
}; };
const StyledDropdownButtonContainer = styled.div` const StyledDropdownButtonContainer = styled.div`
@ -114,6 +117,7 @@ function DropdownButton({
children, children,
isUnfolded = false, isUnfolded = false,
setIsUnfolded, setIsUnfolded,
resetState,
}: OwnProps) { }: OwnProps) {
const onButtonClick = () => { const onButtonClick = () => {
setIsUnfolded && setIsUnfolded(!isUnfolded); setIsUnfolded && setIsUnfolded(!isUnfolded);
@ -121,6 +125,7 @@ function DropdownButton({
const onOutsideClick = () => { const onOutsideClick = () => {
setIsUnfolded && setIsUnfolded(false); setIsUnfolded && setIsUnfolded(false);
resetState && resetState();
}; };
const dropdownRef = useRef(null); const dropdownRef = useRef(null);
@ -132,6 +137,7 @@ function DropdownButton({
isUnfolded={isUnfolded} isUnfolded={isUnfolded}
onClick={onButtonClick} onClick={onButtonClick}
isActive={isActive} isActive={isActive}
aria-selected={isActive}
> >
{label} {label}
</StyledDropdownButton> </StyledDropdownButton>
@ -142,8 +148,20 @@ function DropdownButton({
); );
} }
const StyleAngleDownContainer = styled.div`
margin-left: auto;
`;
function DropdownTopOptionAngleDown() {
return (
<StyleAngleDownContainer>
<FontAwesomeIcon icon={faAngleDown} />
</StyleAngleDownContainer>
);
}
DropdownButton.StyledDropdownItem = StyledDropdownItem; DropdownButton.StyledDropdownItem = StyledDropdownItem;
DropdownButton.StyledDropdownTopOption = StyledDropdownTopOption; DropdownButton.StyledDropdownTopOption = StyledDropdownTopOption;
DropdownButton.StyledDropdownTopOptionAngleDown = DropdownTopOptionAngleDown;
DropdownButton.StyledIcon = StyledIcon; DropdownButton.StyledIcon = StyledIcon;
export default DropdownButton; export default DropdownButton;

View File

@ -0,0 +1,134 @@
import { useCallback, useState } from 'react';
import DropdownButton from './DropdownButton';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { FilterType, SelectedFilterType } from './interface';
type OwnProps = {
filters: SelectedFilterType[];
setFilters: (sorts: SelectedFilterType[]) => void;
availableFilters: FilterType[];
};
type FilterOperandType = { label: string; id: string };
const filterOperands: FilterOperandType[] = [
{ label: 'Include', id: 'include' },
{ label: "Doesn't include", id: 'not-include' },
];
const someFieldRandomValue = [
'John Doe',
'Jane Doe',
'John Smith',
'Jane Smith',
'John Johnson',
'Jane Johnson',
'John Williams',
'Jane Williams',
'John Brown',
'Jane Brown',
'John Jones',
'Jane Jones',
];
export function FilterDropdownButton({
availableFilters,
setFilters,
filters,
}: OwnProps) {
const [isUnfolded, setIsUnfolded] = useState(false);
const [isOptionUnfolded, setIsOptionUnfolded] = useState(false);
const [selectedFilter, setSelectedFilter] = useState<FilterType | undefined>(
undefined,
);
const [selectedFilterOperand, setSelectedFilterOperand] =
useState<FilterOperandType>(filterOperands[0]);
const resetState = useCallback(() => {
setIsOptionUnfolded(false);
setSelectedFilter(undefined);
setSelectedFilterOperand(filterOperands[0]);
}, []);
return (
<DropdownButton
label="Filter"
isActive={filters.length > 0}
isUnfolded={isUnfolded}
setIsUnfolded={setIsUnfolded}
resetState={resetState}
>
{selectedFilter
? isOptionUnfolded
? filterOperands.map((filterOperand, index) => (
<DropdownButton.StyledDropdownItem
key={`select-filter-operand-${index}`}
onClick={() => {
setSelectedFilterOperand(filterOperand);
setIsOptionUnfolded(false);
}}
>
{filterOperand.label}
</DropdownButton.StyledDropdownItem>
))
: [
<DropdownButton.StyledDropdownTopOption
key={'selected-filter'}
onClick={() => setSelectedFilter(undefined)}
>
<DropdownButton.StyledIcon>
{selectedFilter.icon && (
<FontAwesomeIcon icon={selectedFilter.icon} />
)}
</DropdownButton.StyledIcon>
{selectedFilter.label}
<DropdownButton.StyledDropdownTopOptionAngleDown />
</DropdownButton.StyledDropdownTopOption>,
<DropdownButton.StyledDropdownTopOption
key={'selected-filter-operand'}
onClick={() => setIsOptionUnfolded(true)}
>
{selectedFilterOperand.label}
<DropdownButton.StyledDropdownTopOptionAngleDown />
</DropdownButton.StyledDropdownTopOption>,
someFieldRandomValue.map((value, index) => (
<DropdownButton.StyledDropdownItem
key={`fields-value-${index}`}
onClick={() => {
setFilters([
{
id: value,
operand: selectedFilterOperand,
label: selectedFilter.label,
value: value,
icon: selectedFilter.icon,
},
]);
setIsUnfolded(false);
setSelectedFilter(undefined);
}}
>
{value}
</DropdownButton.StyledDropdownItem>
)),
]
: availableFilters.map((filter, index) => (
<DropdownButton.StyledDropdownItem
key={`select-filter-${index}`}
onClick={() => {
setSelectedFilter(filter);
}}
>
<DropdownButton.StyledIcon>
{filter.icon && <FontAwesomeIcon icon={filter.icon} />}
</DropdownButton.StyledIcon>
{filter.label}
</DropdownButton.StyledDropdownItem>
))}
</DropdownButton>
);
}

View File

@ -1,21 +1,13 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import SortOrFilterChip from './SortOrFilterChip'; import SortOrFilterChip from './SortOrFilterChip';
import { faArrowDown, faArrowUp } from '@fortawesome/pro-regular-svg-icons'; import { faArrowDown, faArrowUp } from '@fortawesome/pro-regular-svg-icons';
import { SelectedFilterType, SelectedSortType } from './interface';
type OwnProps<SortField> = { type OwnProps<SortField> = {
sorts: Array<SelectedSortType<SortField>>; sorts: Array<SelectedSortType<SortField>>;
onRemoveSort: (sortId: string) => void; onRemoveSort: (sortId: SelectedSortType<SortField>['key']) => void;
}; filters: Array<SelectedFilterType>;
onRemoveFilter: (filterId: SelectedFilterType['id']) => void;
export type SortType<SortIds = string> = {
label: string;
id: SortIds;
icon?: IconProp;
};
export type SelectedSortType<SortField = string> = SortType<SortField> & {
order: 'asc' | 'desc';
}; };
const StyledBar = styled.div` const StyledBar = styled.div`
@ -50,24 +42,40 @@ const StyledCancelButton = styled.button`
function SortAndFilterBar<SortField extends string>({ function SortAndFilterBar<SortField extends string>({
sorts, sorts,
onRemoveSort, onRemoveSort,
filters,
onRemoveFilter,
}: OwnProps<SortField>) { }: OwnProps<SortField>) {
return ( return (
<StyledBar> <StyledBar>
{sorts.map((sort) => { {sorts.map((sort) => {
return ( return (
<SortOrFilterChip <SortOrFilterChip
key={sort.id} key={sort.key}
label={sort.label} label={sort.label}
id={sort.id} id={sort.key}
icon={sort.order === 'asc' ? faArrowDown : faArrowUp} icon={sort.order === 'asc' ? faArrowDown : faArrowUp}
onRemove={() => onRemoveSort(sort.id)} onRemove={() => onRemoveSort(sort.key)}
/> />
); );
})} })}
{sorts.length > 0 && ( {filters.map((filter) => {
return (
<SortOrFilterChip
key={filter.id}
label={`${filter.label}: ${filter.operand.label} ${filter.value}`}
id={filter.id}
icon={filter.icon}
onRemove={() => onRemoveFilter(filter.id)}
/>
);
})}
{filters.length + sorts.length > 0 && (
<StyledCancelButton <StyledCancelButton
data-testid={'cancel-button'} data-testid={'cancel-button'}
onClick={() => sorts.forEach((i) => onRemoveSort(i.id))} onClick={() => {
sorts.forEach((i) => onRemoveSort(i.key));
filters.forEach((i) => onRemoveFilter(i.id));
}}
> >
Cancel Cancel
</StyledCancelButton> </StyledCancelButton>

View File

@ -1,19 +1,18 @@
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import DropdownButton from './DropdownButton'; import DropdownButton from './DropdownButton';
import { SelectedSortType, SortType } from './SortAndFilterBar'; import { SelectedSortType, SortType } from './interface';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faAngleDown } from '@fortawesome/pro-regular-svg-icons';
type OwnProps<SortField> = { type OwnProps<SortField> = {
sorts: SelectedSortType<SortField>[]; sorts: SelectedSortType<SortField>[];
setSorts: (sorts: SelectedSortType<SortField>[]) => void; setSorts: (sorts: SelectedSortType<SortField>[]) => void;
sortsAvailable: SortType<SortField>[]; availableSorts: SortType<SortField>[];
}; };
const options: Array<SelectedSortType<string>['order']> = ['asc', 'desc']; const options: Array<SelectedSortType<string>['order']> = ['asc', 'desc'];
export function SortDropdownButton<SortField extends string>({ export function SortDropdownButton<SortField extends string>({
sortsAvailable, availableSorts,
setSorts, setSorts,
sorts, sorts,
}: OwnProps<SortField>) { }: OwnProps<SortField>) {
@ -32,12 +31,18 @@ export function SortDropdownButton<SortField extends string>({
[setSorts, selectedSortDirection], [setSorts, selectedSortDirection],
); );
const resetState = useCallback(() => {
setIsOptionUnfolded(false);
setSelectedSortDirection('asc');
}, []);
return ( return (
<DropdownButton <DropdownButton
label="Sort" label="Sort"
isActive={sorts.length > 0} isActive={sorts.length > 0}
isUnfolded={isUnfolded} isUnfolded={isUnfolded}
setIsUnfolded={setIsUnfolded} setIsUnfolded={setIsUnfolded}
resetState={resetState}
> >
{isOptionUnfolded {isOptionUnfolded
? options.map((option, index) => ( ? options.map((option, index) => (
@ -58,9 +63,9 @@ export function SortDropdownButton<SortField extends string>({
> >
{selectedSortDirection === 'asc' ? 'Ascending' : 'Descending'} {selectedSortDirection === 'asc' ? 'Ascending' : 'Descending'}
<FontAwesomeIcon icon={faAngleDown} /> <DropdownButton.StyledDropdownTopOptionAngleDown />
</DropdownButton.StyledDropdownTopOption>, </DropdownButton.StyledDropdownTopOption>,
...sortsAvailable.map((sort, index) => ( ...availableSorts.map((sort, index) => (
<DropdownButton.StyledDropdownItem <DropdownButton.StyledDropdownItem
key={index + 1} key={index + 1}
onClick={() => { onClick={() => {

View File

@ -2,18 +2,24 @@ import styled from '@emotion/styled';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import DropdownButton from './DropdownButton'; import DropdownButton from './DropdownButton';
import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { IconProp } from '@fortawesome/fontawesome-svg-core';
import SortAndFilterBar, { import {
FilterType,
SelectedFilterType,
SelectedSortType, SelectedSortType,
SortType, SortType,
} from './SortAndFilterBar'; } from './interface';
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import { SortDropdownButton } from './SortDropdownButton'; import { SortDropdownButton } from './SortDropdownButton';
import { FilterDropdownButton } from './FilterDropdownButton';
import SortAndFilterBar from './SortAndFilterBar';
type OwnProps<SortField> = { type OwnProps<SortField> = {
viewName: string; viewName: string;
viewIcon?: IconProp; viewIcon?: IconProp;
onSortsUpdate?: (sorts: Array<SelectedSortType<SortField>>) => void; onSortsUpdate?: (sorts: Array<SelectedSortType<SortField>>) => void;
sortsAvailable: Array<SortType<SortField>>; onFiltersUpdate?: (sorts: Array<SelectedFilterType>) => void;
availableSorts?: Array<SortType<SortField>>;
availableFilters?: FilterType[];
}; };
const StyledContainer = styled.div` const StyledContainer = styled.div`
@ -56,7 +62,9 @@ function TableHeader<SortField extends string>({
viewName, viewName,
viewIcon, viewIcon,
onSortsUpdate, onSortsUpdate,
sortsAvailable, onFiltersUpdate,
availableSorts,
availableFilters,
}: OwnProps<SortField>) { }: OwnProps<SortField>) {
const [sorts, innerSetSorts] = useState<Array<SelectedSortType<SortField>>>( const [sorts, innerSetSorts] = useState<Array<SelectedSortType<SortField>>>(
[], [],
@ -79,6 +87,25 @@ function TableHeader<SortField extends string>({
[onSortsUpdate], [onSortsUpdate],
); );
const [filters, innerSetFilters] = useState<Array<SelectedFilterType>>([]);
const setFilters = useCallback(
(filters: SelectedFilterType[]) => {
innerSetFilters(filters);
onFiltersUpdate && onFiltersUpdate(filters);
},
[onFiltersUpdate],
);
const onFilterItemUnSelect = useCallback(
(filterId: SelectedFilterType['id']) => {
const newFilters = [] as SelectedFilterType[];
innerSetFilters(newFilters);
onFiltersUpdate && onFiltersUpdate(newFilters);
},
[onFiltersUpdate],
);
return ( return (
<StyledContainer> <StyledContainer>
<StyledTableHeader> <StyledTableHeader>
@ -89,18 +116,27 @@ function TableHeader<SortField extends string>({
{viewName} {viewName}
</StyledViewSection> </StyledViewSection>
<StyledFilters> <StyledFilters>
<DropdownButton label="Filter" isActive={false}></DropdownButton> <FilterDropdownButton
filters={filters}
setFilters={setFilters}
availableFilters={availableFilters || []}
/>
<SortDropdownButton <SortDropdownButton
setSorts={setSorts} setSorts={setSorts}
sorts={sorts} sorts={sorts}
sortsAvailable={sortsAvailable} availableSorts={availableSorts || []}
/> />
<DropdownButton label="Settings" isActive={false}></DropdownButton> <DropdownButton label="Settings" isActive={false}></DropdownButton>
</StyledFilters> </StyledFilters>
</StyledTableHeader> </StyledTableHeader>
{sorts.length > 0 && ( {sorts.length + filters.length > 0 && (
<SortAndFilterBar sorts={sorts} onRemoveSort={onSortItemUnSelect} /> <SortAndFilterBar
sorts={sorts}
onRemoveSort={onSortItemUnSelect}
filters={filters}
onRemoveFilter={onFilterItemUnSelect}
/>
)} )}
</StyledContainer> </StyledContainer>
); );

View File

@ -0,0 +1,77 @@
import { ThemeProvider } from '@emotion/react';
import { lightTheme } from '../../../../layout/styles/themes';
import { FilterDropdownButton } from '../FilterDropdownButton';
import styled from '@emotion/styled';
import { FilterType, SelectedFilterType } from '../interface';
import {
faUser,
faBuildings,
faEnvelope,
faPhone,
faCalendar,
faMapPin,
} from '@fortawesome/pro-regular-svg-icons';
import { useCallback, useState } from 'react';
const component = {
title: 'FilterDropdownButton',
component: FilterDropdownButton,
};
export default component;
type OwnProps = {
setFilters: (filters: SelectedFilterType[]) => void;
};
const availableFilters = [
{
key: 'fullname',
label: 'People',
icon: faUser,
},
{
key: 'company_name',
label: 'Company',
icon: faBuildings,
},
{
key: 'email',
label: 'Email',
icon: faEnvelope,
},
{ key: 'phone', label: 'Phone', icon: faPhone },
{
key: 'created_at',
label: 'Created at',
icon: faCalendar,
},
{ key: 'city', label: 'City', icon: faMapPin },
] satisfies FilterType[];
const StyleDiv = styled.div`
height: 200px;
width: 200px;
`;
export const RegularFilterDropdownButton = ({ setFilters }: OwnProps) => {
const [filters, innerSetFilters] = useState<SelectedFilterType[]>([]);
const outerSetFilters = useCallback(
(filters: SelectedFilterType[]) => {
innerSetFilters(filters);
setFilters(filters);
},
[setFilters],
);
return (
<ThemeProvider theme={lightTheme}>
<StyleDiv>
<FilterDropdownButton
availableFilters={availableFilters}
filters={filters}
setFilters={outerSetFilters}
/>
</StyleDiv>
</ThemeProvider>
);
};

View File

@ -22,17 +22,27 @@ export const RegularSortAndFilterBar = ({ removeFunction }: OwnProps) => {
{ {
label: 'Test sort', label: 'Test sort',
order: 'asc', order: 'asc',
id: 'test_sort', key: 'test_sort',
icon: faArrowDown, icon: faArrowDown,
}, },
{ {
label: 'Test sort 2', label: 'Test sort 2',
order: 'desc', order: 'desc',
id: 'test_sort_2', key: 'test_sort_2',
icon: faArrowDown, icon: faArrowDown,
}, },
]} ]}
onRemoveSort={removeFunction} onRemoveSort={removeFunction}
onRemoveFilter={removeFunction}
filters={[
{
label: 'People',
operand: { id: 'include', label: 'Include' },
id: 'test_filter',
icon: faArrowDown,
value: 'John Doe',
},
]}
/> />
</ThemeProvider> </ThemeProvider>
); );

View File

@ -1,8 +1,16 @@
import { SelectedSortType, SortType } from '../SortAndFilterBar'; import { SelectedSortType, SortType } from '../interface';
import { ThemeProvider } from '@emotion/react'; import { ThemeProvider } from '@emotion/react';
import { lightTheme } from '../../../../layout/styles/themes'; import { lightTheme } from '../../../../layout/styles/themes';
import { faArrowDown } from '@fortawesome/pro-regular-svg-icons'; import {
faBuildings,
faCalendar,
faEnvelope,
faMapPin,
faPhone,
faUser,
} from '@fortawesome/pro-regular-svg-icons';
import { SortDropdownButton } from '../SortDropdownButton'; import { SortDropdownButton } from '../SortDropdownButton';
import styled from '@emotion/styled';
const component = { const component = {
title: 'SortDropdownButton', title: 'SortDropdownButton',
@ -19,20 +27,44 @@ const sorts = [] satisfies SelectedSortType[];
const availableSorts = [ const availableSorts = [
{ {
label: 'Email', key: 'fullname',
id: 'email', label: 'People',
icon: faArrowDown, icon: faUser,
}, },
{
key: 'company_name',
label: 'Company',
icon: faBuildings,
},
{
key: 'email',
label: 'Email',
icon: faEnvelope,
},
{ key: 'phone', label: 'Phone', icon: faPhone },
{
key: 'created_at',
label: 'Created at',
icon: faCalendar,
},
{ key: 'city', label: 'City', icon: faMapPin },
] satisfies SortType[]; ] satisfies SortType[];
const StyleDiv = styled.div`
height: 200px;
width: 200px;
`;
export const RegularSortDropdownButton = ({ setSorts }: OwnProps) => { export const RegularSortDropdownButton = ({ setSorts }: OwnProps) => {
return ( return (
<ThemeProvider theme={lightTheme}> <ThemeProvider theme={lightTheme}>
<SortDropdownButton <StyleDiv>
sorts={sorts} <SortDropdownButton
sortsAvailable={availableSorts} sorts={sorts}
setSorts={setSorts} availableSorts={availableSorts}
/> setSorts={setSorts}
/>
</StyleDiv>
</ThemeProvider> </ThemeProvider>
); );
}; };

View File

@ -2,7 +2,7 @@ import TableHeader from '../TableHeader';
import { ThemeProvider } from '@emotion/react'; import { ThemeProvider } from '@emotion/react';
import { lightTheme } from '../../../../layout/styles/themes'; import { lightTheme } from '../../../../layout/styles/themes';
import { faBuilding, faCalendar } from '@fortawesome/pro-regular-svg-icons'; import { faBuilding, faCalendar } from '@fortawesome/pro-regular-svg-icons';
import { SortType } from '../SortAndFilterBar'; import { SortType } from '../interface';
const component = { const component = {
title: 'TableHeader', title: 'TableHeader',
@ -12,9 +12,9 @@ const component = {
export default component; export default component;
export const RegularTableHeader = () => { export const RegularTableHeader = () => {
const sortsAvailable: Array<SortType> = [ const availableSorts: Array<SortType> = [
{ {
id: 'created_at', key: 'created_at',
label: 'Created at', label: 'Created at',
icon: faCalendar, icon: faCalendar,
}, },
@ -24,7 +24,7 @@ export const RegularTableHeader = () => {
<TableHeader <TableHeader
viewName="Test" viewName="Test"
viewIcon={faBuilding} viewIcon={faBuilding}
sortsAvailable={sortsAvailable} availableSorts={availableSorts}
/> />
</ThemeProvider> </ThemeProvider>
); );

View File

@ -0,0 +1,66 @@
import { fireEvent, render, waitFor } from '@testing-library/react';
import { RegularFilterDropdownButton } from '../__stories__/FilterDropdownButton.stories';
import { faEnvelope } from '@fortawesome/pro-regular-svg-icons';
it('Checks the default top option is Include', async () => {
const setSorts = jest.fn();
const { getByText } = render(
<RegularFilterDropdownButton setFilters={setSorts} />,
);
const sortDropdownButton = getByText('Filter');
fireEvent.click(sortDropdownButton);
const sortByEmail = getByText('Email');
fireEvent.click(sortByEmail);
const filterByJohn = getByText('John Doe');
fireEvent.click(filterByJohn);
expect(setSorts).toHaveBeenCalledWith([
{
id: 'John Doe',
value: 'John Doe',
label: 'Email',
operand: { id: 'include', label: 'Include' },
icon: faEnvelope,
},
]);
});
it('Checks the selection of top option for Doesnot include', async () => {
const setSorts = jest.fn();
const { getByText } = render(
<RegularFilterDropdownButton setFilters={setSorts} />,
);
const sortDropdownButton = getByText('Filter');
fireEvent.click(sortDropdownButton);
const sortByEmail = getByText('Email');
fireEvent.click(sortByEmail);
const openOperandOptions = getByText('Include');
fireEvent.click(openOperandOptions);
const selectOperand = getByText("Doesn't include");
fireEvent.click(selectOperand);
const filterByJohn = getByText('John Doe');
fireEvent.click(filterByJohn);
expect(setSorts).toHaveBeenCalledWith([
{
id: 'John Doe',
value: 'John Doe',
label: 'Email',
operand: { id: 'not-include', label: "Doesn't include" },
icon: faEnvelope,
},
]);
const blueSortDropdownButton = getByText('Filter');
await waitFor(() => {
expect(blueSortDropdownButton).toHaveAttribute('aria-selected', 'true');
});
});

View File

@ -1,6 +1,6 @@
import { fireEvent, render } from '@testing-library/react'; import { fireEvent, render } from '@testing-library/react';
import { RegularSortDropdownButton } from '../__stories__/SortDropdownButton.stories'; import { RegularSortDropdownButton } from '../__stories__/SortDropdownButton.stories';
import { faArrowDown } from '@fortawesome/pro-regular-svg-icons'; import { faEnvelope } from '@fortawesome/pro-regular-svg-icons';
it('Checks the default top option is Ascending', async () => { it('Checks the default top option is Ascending', async () => {
const setSorts = jest.fn(); const setSorts = jest.fn();
@ -17,8 +17,8 @@ it('Checks the default top option is Ascending', async () => {
expect(setSorts).toHaveBeenCalledWith([ expect(setSorts).toHaveBeenCalledWith([
{ {
label: 'Email', label: 'Email',
id: 'email', key: 'email',
icon: faArrowDown, icon: faEnvelope,
order: 'asc', order: 'asc',
}, },
]); ]);
@ -45,8 +45,8 @@ it('Checks the selection of Descending', async () => {
expect(setSorts).toHaveBeenCalledWith([ expect(setSorts).toHaveBeenCalledWith([
{ {
label: 'Email', label: 'Email',
id: 'email', key: 'email',
icon: faArrowDown, icon: faEnvelope,
order: 'desc', order: 'desc',
}, },
]); ]);

View File

@ -0,0 +1,25 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
export type SortType<SortKey = string> = {
label: string;
key: SortKey;
icon?: IconProp;
};
export type FilterType<FilterKey = string> = {
label: string;
key: FilterKey;
icon: IconProp;
};
export type SelectedFilterType = {
id: string;
label: string;
value: string;
operand: { id: string; label: string };
icon: IconProp;
};
export type SelectedSortType<SortField = string> = SortType<SortField> & {
order: 'asc' | 'desc';
};

View File

@ -2,7 +2,11 @@ import { faUser, faList } from '@fortawesome/pro-regular-svg-icons';
import WithTopBarContainer from '../../layout/containers/WithTopBarContainer'; import WithTopBarContainer from '../../layout/containers/WithTopBarContainer';
import Table from '../../components/table/Table'; import Table from '../../components/table/Table';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { peopleColumns, sortsAvailable } from './people-table'; import {
availableFilters,
peopleColumns,
availableSorts,
} from './people-table';
import { mapPerson } from '../../interfaces/person.interface'; import { mapPerson } from '../../interfaces/person.interface';
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import { import {
@ -38,7 +42,8 @@ function People() {
viewName="All People" viewName="All People"
viewIcon={faList} viewIcon={faList}
onSortsUpdate={updateSorts} onSortsUpdate={updateSorts}
sortsAvailable={sortsAvailable} availableSorts={availableSorts}
availableFilters={availableFilters}
/> />
} }
</StyledPeopleContainer> </StyledPeopleContainer>

View File

@ -17,35 +17,63 @@ import CompanyChip from '../../components/chips/CompanyChip';
import PersonChip from '../../components/chips/PersonChip'; import PersonChip from '../../components/chips/PersonChip';
import { Person } from '../../interfaces/person.interface'; import { Person } from '../../interfaces/person.interface';
import PipeChip from '../../components/chips/PipeChip'; import PipeChip from '../../components/chips/PipeChip';
import { SortType } from '../../components/table/table-header/SortAndFilterBar';
import EditableCell from '../../components/table/EditableCell'; import EditableCell from '../../components/table/EditableCell';
import { OrderByFields, updatePerson } from '../../services/people'; import { OrderByFields, updatePerson } from '../../services/people';
import {
FilterType,
SortType,
} from '../../components/table/table-header/interface';
export const sortsAvailable = [ export const availableSorts = [
{ {
id: 'fullname', key: 'fullname',
label: 'People', label: 'People',
icon: faUser, icon: faUser,
}, },
{ {
id: 'company_name', key: 'company_name',
label: 'Company', label: 'Company',
icon: faBuildings, icon: faBuildings,
}, },
{ {
id: 'email', key: 'email',
label: 'Email', label: 'Email',
icon: faEnvelope, icon: faEnvelope,
}, },
{ id: 'phone', label: 'Phone', icon: faPhone }, { key: 'phone', label: 'Phone', icon: faPhone },
{ {
id: 'created_at', key: 'created_at',
label: 'Created at', label: 'Created at',
icon: faCalendar, icon: faCalendar,
}, },
{ id: 'city', label: 'City', icon: faMapPin }, { key: 'city', label: 'City', icon: faMapPin },
] satisfies Array<SortType<OrderByFields>>; ] satisfies Array<SortType<OrderByFields>>;
export const availableFilters = [
{
key: 'fullname',
label: 'People',
icon: faUser,
},
{
key: 'company_name',
label: 'Company',
icon: faBuildings,
},
{
key: 'email',
label: 'Email',
icon: faEnvelope,
},
{ key: 'phone', label: 'Phone', icon: faPhone },
{
key: 'created_at',
label: 'Created at',
icon: faCalendar,
},
{ key: 'city', label: 'City', icon: faMapPin },
] satisfies FilterType[];
const columnHelper = createColumnHelper<Person>(); const columnHelper = createColumnHelper<Person>();
export const peopleColumns = [ export const peopleColumns = [
columnHelper.accessor('fullName', { columnHelper.accessor('fullName', {

View File

@ -3,8 +3,8 @@ import { PeopleSelectedSortType, reduceSortsToOrderBy } 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 = [
{ id: 'firstname', label: 'firstname', order: 'asc' }, { key: 'firstname', label: 'firstname', order: 'asc' },
{ id: 'lastname', label: 'lastname', order: 'desc' }, { key: 'lastname', label: 'lastname', order: 'desc' },
] 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' }]);

View File

@ -1,7 +1,7 @@
import { QueryResult, gql, useQuery } from '@apollo/client'; import { QueryResult, gql, useQuery } from '@apollo/client';
import { GraphqlQueryPerson } from '../../interfaces/person.interface'; import { GraphqlQueryPerson } from '../../interfaces/person.interface';
import { SelectedSortType } from '../../components/table/table-header/SortAndFilterBar';
import { Order_By, People_Order_By } from '../../generated/graphql'; import { Order_By, People_Order_By } from '../../generated/graphql';
import { SelectedSortType } from '../../components/table/table-header/interface';
export type OrderByFields = keyof People_Order_By | 'fullname' | 'company_name'; export type OrderByFields = keyof People_Order_By | 'fullname' | 'company_name';
@ -15,7 +15,7 @@ export const reduceSortsToOrderBy = (
sorts: Array<PeopleSelectedSortType>, sorts: Array<PeopleSelectedSortType>,
): People_Order_By[] => { ): People_Order_By[] => {
const mappedSorts = sorts.reduce((acc, sort) => { const mappedSorts = sorts.reduce((acc, sort) => {
const id = sort.id; const id = sort.key;
const order = mapOrder(sort.order); const order = mapOrder(sort.order);
if (id === 'fullname') { if (id === 'fullname') {
acc['firstname'] = order; acc['firstname'] = order;