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:
@ -9,7 +9,11 @@ import {
|
||||
import TableHeader from './table-header/TableHeader';
|
||||
import { IconProp } from '@fortawesome/fontawesome-svg-core';
|
||||
import styled from '@emotion/styled';
|
||||
import { SelectedSortType, SortType } from './table-header/SortAndFilterBar';
|
||||
import {
|
||||
FilterType,
|
||||
SelectedSortType,
|
||||
SortType,
|
||||
} from './table-header/interface';
|
||||
|
||||
type OwnProps<TData, SortField> = {
|
||||
data: Array<TData>;
|
||||
@ -17,7 +21,8 @@ type OwnProps<TData, SortField> = {
|
||||
viewName: string;
|
||||
viewIcon?: IconProp;
|
||||
onSortsUpdate?: (sorts: Array<SelectedSortType<SortField>>) => void;
|
||||
sortsAvailable?: Array<SortType<SortField>>;
|
||||
availableSorts?: Array<SortType<SortField>>;
|
||||
availableFilters?: FilterType[];
|
||||
};
|
||||
|
||||
const StyledTable = styled.table`
|
||||
@ -77,7 +82,8 @@ function Table<TData, SortField extends string>({
|
||||
viewName,
|
||||
viewIcon,
|
||||
onSortsUpdate,
|
||||
sortsAvailable,
|
||||
availableSorts,
|
||||
availableFilters,
|
||||
}: OwnProps<TData, SortField>) {
|
||||
const table = useReactTable({
|
||||
data,
|
||||
@ -91,7 +97,8 @@ function Table<TData, SortField extends string>({
|
||||
viewName={viewName}
|
||||
viewIcon={viewIcon}
|
||||
onSortsUpdate={onSortsUpdate}
|
||||
sortsAvailable={sortsAvailable || []}
|
||||
availableSorts={availableSorts}
|
||||
availableFilters={availableFilters}
|
||||
/>
|
||||
<StyledTableScrollableContainer>
|
||||
<StyledTable>
|
||||
|
||||
@ -2,6 +2,8 @@ import styled from '@emotion/styled';
|
||||
import { useRef, ReactNode } from 'react';
|
||||
import { useOutsideAlerter } from '../../../hooks/useOutsideAlerter';
|
||||
import { modalBackground } from '../../../layout/styles/themes';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faAngleDown } from '@fortawesome/pro-regular-svg-icons';
|
||||
|
||||
type OwnProps = {
|
||||
label: string;
|
||||
@ -9,6 +11,7 @@ type OwnProps = {
|
||||
children?: ReactNode;
|
||||
isUnfolded?: boolean;
|
||||
setIsUnfolded?: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
resetState?: () => void;
|
||||
};
|
||||
|
||||
const StyledDropdownButtonContainer = styled.div`
|
||||
@ -114,6 +117,7 @@ function DropdownButton({
|
||||
children,
|
||||
isUnfolded = false,
|
||||
setIsUnfolded,
|
||||
resetState,
|
||||
}: OwnProps) {
|
||||
const onButtonClick = () => {
|
||||
setIsUnfolded && setIsUnfolded(!isUnfolded);
|
||||
@ -121,6 +125,7 @@ function DropdownButton({
|
||||
|
||||
const onOutsideClick = () => {
|
||||
setIsUnfolded && setIsUnfolded(false);
|
||||
resetState && resetState();
|
||||
};
|
||||
|
||||
const dropdownRef = useRef(null);
|
||||
@ -132,6 +137,7 @@ function DropdownButton({
|
||||
isUnfolded={isUnfolded}
|
||||
onClick={onButtonClick}
|
||||
isActive={isActive}
|
||||
aria-selected={isActive}
|
||||
>
|
||||
{label}
|
||||
</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.StyledDropdownTopOption = StyledDropdownTopOption;
|
||||
DropdownButton.StyledDropdownTopOptionAngleDown = DropdownTopOptionAngleDown;
|
||||
DropdownButton.StyledIcon = StyledIcon;
|
||||
|
||||
export default DropdownButton;
|
||||
|
||||
134
front/src/components/table/table-header/FilterDropdownButton.tsx
Normal file
134
front/src/components/table/table-header/FilterDropdownButton.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@ -1,21 +1,13 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { IconProp } from '@fortawesome/fontawesome-svg-core';
|
||||
import SortOrFilterChip from './SortOrFilterChip';
|
||||
import { faArrowDown, faArrowUp } from '@fortawesome/pro-regular-svg-icons';
|
||||
import { SelectedFilterType, SelectedSortType } from './interface';
|
||||
|
||||
type OwnProps<SortField> = {
|
||||
sorts: Array<SelectedSortType<SortField>>;
|
||||
onRemoveSort: (sortId: string) => void;
|
||||
};
|
||||
|
||||
export type SortType<SortIds = string> = {
|
||||
label: string;
|
||||
id: SortIds;
|
||||
icon?: IconProp;
|
||||
};
|
||||
|
||||
export type SelectedSortType<SortField = string> = SortType<SortField> & {
|
||||
order: 'asc' | 'desc';
|
||||
onRemoveSort: (sortId: SelectedSortType<SortField>['key']) => void;
|
||||
filters: Array<SelectedFilterType>;
|
||||
onRemoveFilter: (filterId: SelectedFilterType['id']) => void;
|
||||
};
|
||||
|
||||
const StyledBar = styled.div`
|
||||
@ -50,24 +42,40 @@ const StyledCancelButton = styled.button`
|
||||
function SortAndFilterBar<SortField extends string>({
|
||||
sorts,
|
||||
onRemoveSort,
|
||||
filters,
|
||||
onRemoveFilter,
|
||||
}: OwnProps<SortField>) {
|
||||
return (
|
||||
<StyledBar>
|
||||
{sorts.map((sort) => {
|
||||
return (
|
||||
<SortOrFilterChip
|
||||
key={sort.id}
|
||||
key={sort.key}
|
||||
label={sort.label}
|
||||
id={sort.id}
|
||||
id={sort.key}
|
||||
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
|
||||
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
|
||||
</StyledCancelButton>
|
||||
|
||||
@ -1,19 +1,18 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import DropdownButton from './DropdownButton';
|
||||
import { SelectedSortType, SortType } from './SortAndFilterBar';
|
||||
import { SelectedSortType, SortType } from './interface';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faAngleDown } from '@fortawesome/pro-regular-svg-icons';
|
||||
|
||||
type OwnProps<SortField> = {
|
||||
sorts: SelectedSortType<SortField>[];
|
||||
setSorts: (sorts: SelectedSortType<SortField>[]) => void;
|
||||
sortsAvailable: SortType<SortField>[];
|
||||
availableSorts: SortType<SortField>[];
|
||||
};
|
||||
|
||||
const options: Array<SelectedSortType<string>['order']> = ['asc', 'desc'];
|
||||
|
||||
export function SortDropdownButton<SortField extends string>({
|
||||
sortsAvailable,
|
||||
availableSorts,
|
||||
setSorts,
|
||||
sorts,
|
||||
}: OwnProps<SortField>) {
|
||||
@ -32,12 +31,18 @@ export function SortDropdownButton<SortField extends string>({
|
||||
[setSorts, selectedSortDirection],
|
||||
);
|
||||
|
||||
const resetState = useCallback(() => {
|
||||
setIsOptionUnfolded(false);
|
||||
setSelectedSortDirection('asc');
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<DropdownButton
|
||||
label="Sort"
|
||||
isActive={sorts.length > 0}
|
||||
isUnfolded={isUnfolded}
|
||||
setIsUnfolded={setIsUnfolded}
|
||||
resetState={resetState}
|
||||
>
|
||||
{isOptionUnfolded
|
||||
? options.map((option, index) => (
|
||||
@ -58,9 +63,9 @@ export function SortDropdownButton<SortField extends string>({
|
||||
>
|
||||
{selectedSortDirection === 'asc' ? 'Ascending' : 'Descending'}
|
||||
|
||||
<FontAwesomeIcon icon={faAngleDown} />
|
||||
<DropdownButton.StyledDropdownTopOptionAngleDown />
|
||||
</DropdownButton.StyledDropdownTopOption>,
|
||||
...sortsAvailable.map((sort, index) => (
|
||||
...availableSorts.map((sort, index) => (
|
||||
<DropdownButton.StyledDropdownItem
|
||||
key={index + 1}
|
||||
onClick={() => {
|
||||
|
||||
@ -2,18 +2,24 @@ import styled from '@emotion/styled';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import DropdownButton from './DropdownButton';
|
||||
import { IconProp } from '@fortawesome/fontawesome-svg-core';
|
||||
import SortAndFilterBar, {
|
||||
import {
|
||||
FilterType,
|
||||
SelectedFilterType,
|
||||
SelectedSortType,
|
||||
SortType,
|
||||
} from './SortAndFilterBar';
|
||||
} from './interface';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { SortDropdownButton } from './SortDropdownButton';
|
||||
import { FilterDropdownButton } from './FilterDropdownButton';
|
||||
import SortAndFilterBar from './SortAndFilterBar';
|
||||
|
||||
type OwnProps<SortField> = {
|
||||
viewName: string;
|
||||
viewIcon?: IconProp;
|
||||
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`
|
||||
@ -56,7 +62,9 @@ function TableHeader<SortField extends string>({
|
||||
viewName,
|
||||
viewIcon,
|
||||
onSortsUpdate,
|
||||
sortsAvailable,
|
||||
onFiltersUpdate,
|
||||
availableSorts,
|
||||
availableFilters,
|
||||
}: OwnProps<SortField>) {
|
||||
const [sorts, innerSetSorts] = useState<Array<SelectedSortType<SortField>>>(
|
||||
[],
|
||||
@ -79,6 +87,25 @@ function TableHeader<SortField extends string>({
|
||||
[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 (
|
||||
<StyledContainer>
|
||||
<StyledTableHeader>
|
||||
@ -89,18 +116,27 @@ function TableHeader<SortField extends string>({
|
||||
{viewName}
|
||||
</StyledViewSection>
|
||||
<StyledFilters>
|
||||
<DropdownButton label="Filter" isActive={false}></DropdownButton>
|
||||
<FilterDropdownButton
|
||||
filters={filters}
|
||||
setFilters={setFilters}
|
||||
availableFilters={availableFilters || []}
|
||||
/>
|
||||
<SortDropdownButton
|
||||
setSorts={setSorts}
|
||||
sorts={sorts}
|
||||
sortsAvailable={sortsAvailable}
|
||||
availableSorts={availableSorts || []}
|
||||
/>
|
||||
|
||||
<DropdownButton label="Settings" isActive={false}></DropdownButton>
|
||||
</StyledFilters>
|
||||
</StyledTableHeader>
|
||||
{sorts.length > 0 && (
|
||||
<SortAndFilterBar sorts={sorts} onRemoveSort={onSortItemUnSelect} />
|
||||
{sorts.length + filters.length > 0 && (
|
||||
<SortAndFilterBar
|
||||
sorts={sorts}
|
||||
onRemoveSort={onSortItemUnSelect}
|
||||
filters={filters}
|
||||
onRemoveFilter={onFilterItemUnSelect}
|
||||
/>
|
||||
)}
|
||||
</StyledContainer>
|
||||
);
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
@ -22,17 +22,27 @@ export const RegularSortAndFilterBar = ({ removeFunction }: OwnProps) => {
|
||||
{
|
||||
label: 'Test sort',
|
||||
order: 'asc',
|
||||
id: 'test_sort',
|
||||
key: 'test_sort',
|
||||
icon: faArrowDown,
|
||||
},
|
||||
{
|
||||
label: 'Test sort 2',
|
||||
order: 'desc',
|
||||
id: 'test_sort_2',
|
||||
key: 'test_sort_2',
|
||||
icon: faArrowDown,
|
||||
},
|
||||
]}
|
||||
onRemoveSort={removeFunction}
|
||||
onRemoveFilter={removeFunction}
|
||||
filters={[
|
||||
{
|
||||
label: 'People',
|
||||
operand: { id: 'include', label: 'Include' },
|
||||
id: 'test_filter',
|
||||
icon: faArrowDown,
|
||||
value: 'John Doe',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
@ -1,8 +1,16 @@
|
||||
import { SelectedSortType, SortType } from '../SortAndFilterBar';
|
||||
import { SelectedSortType, SortType } from '../interface';
|
||||
import { ThemeProvider } from '@emotion/react';
|
||||
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 styled from '@emotion/styled';
|
||||
|
||||
const component = {
|
||||
title: 'SortDropdownButton',
|
||||
@ -19,20 +27,44 @@ const sorts = [] satisfies SelectedSortType[];
|
||||
|
||||
const availableSorts = [
|
||||
{
|
||||
label: 'Email',
|
||||
id: 'email',
|
||||
icon: faArrowDown,
|
||||
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 SortType[];
|
||||
|
||||
const StyleDiv = styled.div`
|
||||
height: 200px;
|
||||
width: 200px;
|
||||
`;
|
||||
|
||||
export const RegularSortDropdownButton = ({ setSorts }: OwnProps) => {
|
||||
return (
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<SortDropdownButton
|
||||
sorts={sorts}
|
||||
sortsAvailable={availableSorts}
|
||||
setSorts={setSorts}
|
||||
/>
|
||||
<StyleDiv>
|
||||
<SortDropdownButton
|
||||
sorts={sorts}
|
||||
availableSorts={availableSorts}
|
||||
setSorts={setSorts}
|
||||
/>
|
||||
</StyleDiv>
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@ -2,7 +2,7 @@ import TableHeader from '../TableHeader';
|
||||
import { ThemeProvider } from '@emotion/react';
|
||||
import { lightTheme } from '../../../../layout/styles/themes';
|
||||
import { faBuilding, faCalendar } from '@fortawesome/pro-regular-svg-icons';
|
||||
import { SortType } from '../SortAndFilterBar';
|
||||
import { SortType } from '../interface';
|
||||
|
||||
const component = {
|
||||
title: 'TableHeader',
|
||||
@ -12,9 +12,9 @@ const component = {
|
||||
export default component;
|
||||
|
||||
export const RegularTableHeader = () => {
|
||||
const sortsAvailable: Array<SortType> = [
|
||||
const availableSorts: Array<SortType> = [
|
||||
{
|
||||
id: 'created_at',
|
||||
key: 'created_at',
|
||||
label: 'Created at',
|
||||
icon: faCalendar,
|
||||
},
|
||||
@ -24,7 +24,7 @@ export const RegularTableHeader = () => {
|
||||
<TableHeader
|
||||
viewName="Test"
|
||||
viewIcon={faBuilding}
|
||||
sortsAvailable={sortsAvailable}
|
||||
availableSorts={availableSorts}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
@ -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');
|
||||
});
|
||||
});
|
||||
@ -1,6 +1,6 @@
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
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 () => {
|
||||
const setSorts = jest.fn();
|
||||
@ -17,8 +17,8 @@ it('Checks the default top option is Ascending', async () => {
|
||||
expect(setSorts).toHaveBeenCalledWith([
|
||||
{
|
||||
label: 'Email',
|
||||
id: 'email',
|
||||
icon: faArrowDown,
|
||||
key: 'email',
|
||||
icon: faEnvelope,
|
||||
order: 'asc',
|
||||
},
|
||||
]);
|
||||
@ -45,8 +45,8 @@ it('Checks the selection of Descending', async () => {
|
||||
expect(setSorts).toHaveBeenCalledWith([
|
||||
{
|
||||
label: 'Email',
|
||||
id: 'email',
|
||||
icon: faArrowDown,
|
||||
key: 'email',
|
||||
icon: faEnvelope,
|
||||
order: 'desc',
|
||||
},
|
||||
]);
|
||||
|
||||
25
front/src/components/table/table-header/interface.ts
Normal file
25
front/src/components/table/table-header/interface.ts
Normal 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';
|
||||
};
|
||||
@ -2,7 +2,11 @@ import { faUser, faList } from '@fortawesome/pro-regular-svg-icons';
|
||||
import WithTopBarContainer from '../../layout/containers/WithTopBarContainer';
|
||||
import Table from '../../components/table/Table';
|
||||
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 { useCallback, useState } from 'react';
|
||||
import {
|
||||
@ -38,7 +42,8 @@ function People() {
|
||||
viewName="All People"
|
||||
viewIcon={faList}
|
||||
onSortsUpdate={updateSorts}
|
||||
sortsAvailable={sortsAvailable}
|
||||
availableSorts={availableSorts}
|
||||
availableFilters={availableFilters}
|
||||
/>
|
||||
}
|
||||
</StyledPeopleContainer>
|
||||
|
||||
@ -17,35 +17,63 @@ import CompanyChip from '../../components/chips/CompanyChip';
|
||||
import PersonChip from '../../components/chips/PersonChip';
|
||||
import { Person } from '../../interfaces/person.interface';
|
||||
import PipeChip from '../../components/chips/PipeChip';
|
||||
import { SortType } from '../../components/table/table-header/SortAndFilterBar';
|
||||
import EditableCell from '../../components/table/EditableCell';
|
||||
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',
|
||||
icon: faUser,
|
||||
},
|
||||
{
|
||||
id: 'company_name',
|
||||
key: 'company_name',
|
||||
label: 'Company',
|
||||
icon: faBuildings,
|
||||
},
|
||||
{
|
||||
id: 'email',
|
||||
key: 'email',
|
||||
label: 'Email',
|
||||
icon: faEnvelope,
|
||||
},
|
||||
{ id: 'phone', label: 'Phone', icon: faPhone },
|
||||
{ key: 'phone', label: 'Phone', icon: faPhone },
|
||||
{
|
||||
id: 'created_at',
|
||||
key: 'created_at',
|
||||
label: 'Created at',
|
||||
icon: faCalendar,
|
||||
},
|
||||
{ id: 'city', label: 'City', icon: faMapPin },
|
||||
{ key: 'city', label: 'City', icon: faMapPin },
|
||||
] 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>();
|
||||
export const peopleColumns = [
|
||||
columnHelper.accessor('fullName', {
|
||||
|
||||
@ -3,8 +3,8 @@ import { PeopleSelectedSortType, reduceSortsToOrderBy } from './select';
|
||||
describe('reduceSortsToOrderBy', () => {
|
||||
it('should return an array of objects with the id as key and the order as value', () => {
|
||||
const sorts = [
|
||||
{ id: 'firstname', label: 'firstname', order: 'asc' },
|
||||
{ id: 'lastname', label: 'lastname', order: 'desc' },
|
||||
{ key: 'firstname', label: 'firstname', order: 'asc' },
|
||||
{ key: 'lastname', label: 'lastname', order: 'desc' },
|
||||
] satisfies PeopleSelectedSortType[];
|
||||
const result = reduceSortsToOrderBy(sorts);
|
||||
expect(result).toEqual([{ firstname: 'asc', lastname: 'desc' }]);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { QueryResult, gql, useQuery } from '@apollo/client';
|
||||
import { GraphqlQueryPerson } from '../../interfaces/person.interface';
|
||||
import { SelectedSortType } from '../../components/table/table-header/SortAndFilterBar';
|
||||
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';
|
||||
|
||||
@ -15,7 +15,7 @@ export const reduceSortsToOrderBy = (
|
||||
sorts: Array<PeopleSelectedSortType>,
|
||||
): People_Order_By[] => {
|
||||
const mappedSorts = sorts.reduce((acc, sort) => {
|
||||
const id = sort.id;
|
||||
const id = sort.key;
|
||||
const order = mapOrder(sort.order);
|
||||
if (id === 'fullname') {
|
||||
acc['firstname'] = order;
|
||||
|
||||
Reference in New Issue
Block a user