Enable filtering by creation date with datepicker (#131)
Enable to filter by date with datepicker
This commit is contained in:
@ -2,7 +2,6 @@ import styled from '@emotion/styled';
|
||||
import { forwardRef, useState } from 'react';
|
||||
import EditableCellWrapper from './EditableCellWrapper';
|
||||
import DatePicker from '../form/DatePicker';
|
||||
import { CalendarContainer } from 'react-datepicker';
|
||||
import { modalBackground } from '../../layout/styles/themes';
|
||||
|
||||
export type EditableDateProps = {
|
||||
@ -24,10 +23,10 @@ const StyledCalendarContainer = styled.div<StyledCalendarContainerProps>`
|
||||
position: absolute;
|
||||
border: 1px solid ${(props) => props.theme.primaryBorder};
|
||||
border-radius: 8px;
|
||||
width: 280px;
|
||||
box-shadow: 0px 3px 12px rgba(0, 0, 0, 0.09);
|
||||
z-index: 1;
|
||||
left: -10px;
|
||||
top: 10px;
|
||||
${modalBackground};
|
||||
`;
|
||||
function EditableDate({
|
||||
@ -54,21 +53,11 @@ function EditableDate({
|
||||
);
|
||||
|
||||
interface DatePickerContainerProps {
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const DatePickerContainer = ({
|
||||
className,
|
||||
children,
|
||||
}: DatePickerContainerProps) => {
|
||||
return (
|
||||
<StyledCalendarContainer>
|
||||
<CalendarContainer className={className}>
|
||||
<div style={{ position: 'relative' }}>{children}</div>
|
||||
</CalendarContainer>
|
||||
</StyledCalendarContainer>
|
||||
);
|
||||
const DatePickerContainer = ({ children }: DatePickerContainerProps) => {
|
||||
return <StyledCalendarContainer>{children}</StyledCalendarContainer>;
|
||||
};
|
||||
|
||||
return (
|
||||
@ -86,7 +75,7 @@ function EditableDate({
|
||||
setInputValue(date);
|
||||
}}
|
||||
customInput={<DateDisplay />}
|
||||
customContainer={DatePickerContainer}
|
||||
customCalendarContainer={DatePickerContainer}
|
||||
/>
|
||||
</StyledContainer>
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@ export type DatePickerProps = {
|
||||
date: Date;
|
||||
onChangeHandler: (date: Date) => void;
|
||||
customInput?: ReactElement;
|
||||
customContainer?(props: CalendarContainerProps): React.ReactNode;
|
||||
customCalendarContainer?(props: CalendarContainerProps): React.ReactNode;
|
||||
};
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
@ -22,6 +22,13 @@ const StyledContainer = styled.div`
|
||||
display: block;
|
||||
}
|
||||
|
||||
& .react-datepicker-popper {
|
||||
position: relative !important;
|
||||
inset: auto !important;
|
||||
transform: none !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
& .react-datepicker__triangle::after {
|
||||
display: none;
|
||||
}
|
||||
@ -125,6 +132,10 @@ const StyledContainer = styled.div`
|
||||
line-height: 40px;
|
||||
}
|
||||
|
||||
& .react-datepicker__month-container {
|
||||
float: none;
|
||||
}
|
||||
|
||||
// Days
|
||||
|
||||
& .react-datepicker__month {
|
||||
@ -172,7 +183,7 @@ function DatePicker({
|
||||
date,
|
||||
onChangeHandler,
|
||||
customInput,
|
||||
customContainer,
|
||||
customCalendarContainer,
|
||||
}: DatePickerProps) {
|
||||
const [startDate, setStartDate] = useState(date);
|
||||
|
||||
@ -203,7 +214,9 @@ function DatePicker({
|
||||
onChangeHandler(date);
|
||||
}}
|
||||
customInput={customInput ? customInput : <DefaultDateDisplay />}
|
||||
calendarContainer={customContainer ? customContainer : undefined}
|
||||
calendarContainer={
|
||||
customCalendarContainer ? customCalendarContainer : undefined
|
||||
}
|
||||
/>
|
||||
</StyledContainer>
|
||||
);
|
||||
|
||||
@ -76,7 +76,6 @@ const StyledDropdownItem = styled.li`
|
||||
padding: ${(props) => props.theme.spacing(2)}
|
||||
calc(${(props) => props.theme.spacing(2)} - 2px);
|
||||
margin: 2px;
|
||||
background: rgba(0, 0, 0, 0);
|
||||
cursor: pointer;
|
||||
color: ${(props) => props.theme.text60};
|
||||
|
||||
@ -91,7 +90,6 @@ const StyledDropdownTopOption = styled.li`
|
||||
justify-content: space-between;
|
||||
padding: calc(${(props) => props.theme.spacing(2)} + 2px)
|
||||
calc(${(props) => props.theme.spacing(2)});
|
||||
background: rgba(0, 0, 0, 0);
|
||||
cursor: pointer;
|
||||
color: ${(props) => props.theme.text60};
|
||||
font-weight: ${(props) => props.theme.fontWeightBold};
|
||||
@ -115,7 +113,6 @@ const StyledSearchField = styled.li`
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
cursor: pointer;
|
||||
color: ${(props) => props.theme.text60};
|
||||
font-weight: ${(props) => props.theme.fontWeightBold};
|
||||
|
||||
@ -10,6 +10,8 @@ import {
|
||||
SearchResultsType,
|
||||
useSearch,
|
||||
} from '../../../services/api/search/search';
|
||||
import DatePicker from '../../form/DatePicker';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
type OwnProps<TData extends FilterableFieldsType> = {
|
||||
isFilterSelected: boolean;
|
||||
@ -26,7 +28,8 @@ export const FilterDropdownButton = <TData extends FilterableFieldsType>({
|
||||
}: OwnProps<TData>) => {
|
||||
const [isUnfolded, setIsUnfolded] = useState(false);
|
||||
|
||||
const [isOptionUnfolded, setIsOptionUnfolded] = useState(false);
|
||||
const [isOperandSelectionUnfolded, setIsOperandSelectionUnfolded] =
|
||||
useState(false);
|
||||
|
||||
const [selectedFilter, setSelectedFilter] = useState<
|
||||
FilterConfigType<TData> | undefined
|
||||
@ -39,19 +42,19 @@ export const FilterDropdownButton = <TData extends FilterableFieldsType>({
|
||||
const [filterSearchResults, setSearchInput, setFilterSearch] = useSearch();
|
||||
|
||||
const resetState = useCallback(() => {
|
||||
setIsOptionUnfolded(false);
|
||||
setIsOperandSelectionUnfolded(false);
|
||||
setSelectedFilter(undefined);
|
||||
setSelectedFilterOperand(undefined);
|
||||
setFilterSearch(null);
|
||||
}, [setFilterSearch]);
|
||||
|
||||
const renderSelectOptionItems = selectedFilter?.operands.map(
|
||||
const renderOperandSelection = selectedFilter?.operands.map(
|
||||
(filterOperand, index) => (
|
||||
<DropdownButton.StyledDropdownItem
|
||||
key={`select-filter-operand-${index}`}
|
||||
onClick={() => {
|
||||
setSelectedFilterOperand(filterOperand);
|
||||
setIsOptionUnfolded(false);
|
||||
setIsOperandSelectionUnfolded(false);
|
||||
}}
|
||||
>
|
||||
{filterOperand.label}
|
||||
@ -59,6 +62,21 @@ export const FilterDropdownButton = <TData extends FilterableFieldsType>({
|
||||
),
|
||||
);
|
||||
|
||||
const renderFilterSelection = availableFilters.map((filter, index) => (
|
||||
<DropdownButton.StyledDropdownItem
|
||||
key={`select-filter-${index}`}
|
||||
onClick={() => {
|
||||
setSelectedFilter(filter);
|
||||
setSelectedFilterOperand(filter.operands[0]);
|
||||
filter.searchConfig && setFilterSearch(filter.searchConfig);
|
||||
setSearchInput('');
|
||||
}}
|
||||
>
|
||||
<DropdownButton.StyledIcon>{filter.icon}</DropdownButton.StyledIcon>
|
||||
{filter.label}
|
||||
</DropdownButton.StyledDropdownItem>
|
||||
));
|
||||
|
||||
const renderSearchResults = (
|
||||
filterSearchResults: SearchResultsType,
|
||||
selectedFilter: FilterConfigType<TData>,
|
||||
@ -93,22 +111,7 @@ export const FilterDropdownButton = <TData extends FilterableFieldsType>({
|
||||
));
|
||||
};
|
||||
|
||||
const renderSelectFilterITems = availableFilters.map((filter, index) => (
|
||||
<DropdownButton.StyledDropdownItem
|
||||
key={`select-filter-${index}`}
|
||||
onClick={() => {
|
||||
setSelectedFilter(filter);
|
||||
setSelectedFilterOperand(filter.operands[0]);
|
||||
filter.searchConfig && setFilterSearch(filter.searchConfig);
|
||||
setSearchInput('');
|
||||
}}
|
||||
>
|
||||
<DropdownButton.StyledIcon>{filter.icon}</DropdownButton.StyledIcon>
|
||||
{filter.label}
|
||||
</DropdownButton.StyledDropdownItem>
|
||||
));
|
||||
|
||||
function renderFilterDropdown(
|
||||
function renderValueSelection(
|
||||
selectedFilter: FilterConfigType<TData>,
|
||||
selectedFilterOperand: FilterOperandType<TData>,
|
||||
) {
|
||||
@ -116,38 +119,65 @@ export const FilterDropdownButton = <TData extends FilterableFieldsType>({
|
||||
<>
|
||||
<DropdownButton.StyledDropdownTopOption
|
||||
key={'selected-filter-operand'}
|
||||
onClick={() => setIsOptionUnfolded(true)}
|
||||
onClick={() => setIsOperandSelectionUnfolded(true)}
|
||||
>
|
||||
{selectedFilterOperand.label}
|
||||
|
||||
<DropdownButton.StyledDropdownTopOptionAngleDown />
|
||||
</DropdownButton.StyledDropdownTopOption>
|
||||
<DropdownButton.StyledSearchField key={'search-filter'}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder={selectedFilter.label}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (selectedFilter.searchConfig) {
|
||||
setFilterSearch(selectedFilter.searchConfig);
|
||||
setSearchInput(event.target.value);
|
||||
} else {
|
||||
if (event.target.value === '') {
|
||||
onFilterRemove(selectedFilter.key);
|
||||
} else {
|
||||
onFilterSelect({
|
||||
key: selectedFilter.key,
|
||||
label: selectedFilter.label,
|
||||
value: event.target.value,
|
||||
displayValue: event.target.value,
|
||||
icon: selectedFilter.icon,
|
||||
operand: selectedFilterOperand,
|
||||
});
|
||||
{['text', 'relation'].includes(selectedFilter.type) && (
|
||||
<input
|
||||
type="text"
|
||||
placeholder={selectedFilter.label}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (
|
||||
selectedFilter.type === 'relation' &&
|
||||
selectedFilter.searchConfig
|
||||
) {
|
||||
setFilterSearch(selectedFilter.searchConfig);
|
||||
setSearchInput(event.target.value);
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
if (selectedFilter.type === 'text') {
|
||||
if (event.target.value === '') {
|
||||
onFilterRemove(selectedFilter.key);
|
||||
} else {
|
||||
onFilterSelect({
|
||||
key: selectedFilter.key,
|
||||
label: selectedFilter.label,
|
||||
value: event.target.value,
|
||||
displayValue: event.target.value,
|
||||
icon: selectedFilter.icon,
|
||||
operand: selectedFilterOperand,
|
||||
});
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{selectedFilter.type === 'date' && (
|
||||
<DatePicker
|
||||
date={new Date()}
|
||||
onChangeHandler={(date) => {
|
||||
onFilterSelect({
|
||||
key: selectedFilter.key,
|
||||
label: selectedFilter.label,
|
||||
value: date.toISOString(),
|
||||
displayValue: date.toLocaleDateString(),
|
||||
icon: selectedFilter.icon,
|
||||
operand: selectedFilterOperand,
|
||||
});
|
||||
}}
|
||||
customInput={<></>}
|
||||
customCalendarContainer={styled.div`
|
||||
top: -10px;
|
||||
`}
|
||||
/>
|
||||
)}
|
||||
</DropdownButton.StyledSearchField>
|
||||
{filterSearchResults &&
|
||||
{selectedFilter.type === 'relation' &&
|
||||
filterSearchResults &&
|
||||
renderSearchResults(
|
||||
filterSearchResults,
|
||||
selectedFilter,
|
||||
@ -165,11 +195,11 @@ export const FilterDropdownButton = <TData extends FilterableFieldsType>({
|
||||
setIsUnfolded={setIsUnfolded}
|
||||
resetState={resetState}
|
||||
>
|
||||
{selectedFilter && selectedFilterOperand
|
||||
? isOptionUnfolded
|
||||
? renderSelectOptionItems
|
||||
: renderFilterDropdown(selectedFilter, selectedFilterOperand)
|
||||
: renderSelectFilterITems}
|
||||
{selectedFilter
|
||||
? isOperandSelectionUnfolded
|
||||
? renderOperandSelection
|
||||
: renderValueSelection(selectedFilter, selectedFilterOperand)
|
||||
: renderFilterSelection}
|
||||
</DropdownButton>
|
||||
);
|
||||
};
|
||||
|
||||
@ -14,6 +14,13 @@ export type FilterConfigType<
|
||||
key: string;
|
||||
label: string;
|
||||
icon: ReactNode;
|
||||
type: WhereType extends UnknownType
|
||||
? 'relation' | 'text' | 'date'
|
||||
: WhereType extends AnyEntity
|
||||
? 'relation'
|
||||
: WhereType extends string
|
||||
? 'text' | 'date'
|
||||
: never;
|
||||
operands: FilterOperandType<FilteredType, WhereType>[];
|
||||
} & (WhereType extends UnknownType
|
||||
? { searchConfig?: SearchConfigType<UnknownType> }
|
||||
|
||||
@ -1,11 +1,18 @@
|
||||
import { Company } from '../../interfaces/entities/company.interface';
|
||||
import { FaLink, FaBuilding, FaMapPin, FaUsers } from 'react-icons/fa';
|
||||
import {
|
||||
FaLink,
|
||||
FaBuilding,
|
||||
FaMapPin,
|
||||
FaUsers,
|
||||
FaCalendar,
|
||||
} from 'react-icons/fa';
|
||||
import { FilterConfigType } from '../../interfaces/filters/interface';
|
||||
|
||||
export const nameFilter = {
|
||||
key: 'company_name',
|
||||
label: 'Company',
|
||||
icon: <FaBuilding />,
|
||||
type: 'text',
|
||||
operands: [
|
||||
{
|
||||
label: 'Contains',
|
||||
@ -28,6 +35,7 @@ export const urlFilter = {
|
||||
key: 'company_domain_name',
|
||||
label: 'Url',
|
||||
icon: <FaLink />,
|
||||
type: 'text',
|
||||
operands: [
|
||||
{
|
||||
label: 'Contains',
|
||||
@ -50,6 +58,7 @@ export const addressFilter = {
|
||||
key: 'company_address',
|
||||
label: 'Address',
|
||||
icon: <FaMapPin />,
|
||||
type: 'text',
|
||||
operands: [
|
||||
{
|
||||
label: 'Contains',
|
||||
@ -72,6 +81,7 @@ export const employeesFilter = {
|
||||
key: 'company_employees',
|
||||
label: 'Employees',
|
||||
icon: <FaUsers />,
|
||||
type: 'text',
|
||||
operands: [
|
||||
{
|
||||
label: 'Greater than',
|
||||
@ -94,9 +104,37 @@ export const employeesFilter = {
|
||||
],
|
||||
} satisfies FilterConfigType<Company, string>;
|
||||
|
||||
export const creationDateFilter = {
|
||||
key: 'company_created_at',
|
||||
label: 'Created At',
|
||||
icon: <FaCalendar />,
|
||||
type: 'date',
|
||||
operands: [
|
||||
{
|
||||
label: 'Greater than',
|
||||
id: 'greater_than',
|
||||
whereTemplate: (searchString) => ({
|
||||
created_at: {
|
||||
_gte: searchString,
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: 'Less than',
|
||||
id: 'less_than',
|
||||
whereTemplate: (searchString) => ({
|
||||
created_at: {
|
||||
_lte: searchString,
|
||||
},
|
||||
}),
|
||||
},
|
||||
],
|
||||
} satisfies FilterConfigType<Company, string>;
|
||||
|
||||
export const availableFilters = [
|
||||
nameFilter,
|
||||
urlFilter,
|
||||
addressFilter,
|
||||
employeesFilter,
|
||||
creationDateFilter,
|
||||
];
|
||||
|
||||
@ -11,6 +11,7 @@ export const fullnameFilter = {
|
||||
key: 'fullname',
|
||||
label: 'People',
|
||||
icon: <FaUser />,
|
||||
type: 'text',
|
||||
operands: [
|
||||
{
|
||||
label: 'Contains',
|
||||
@ -41,6 +42,7 @@ export const companyFilter = {
|
||||
key: 'company_name',
|
||||
label: 'Company',
|
||||
icon: <FaBuilding />,
|
||||
type: 'relation',
|
||||
searchConfig: {
|
||||
query: SEARCH_COMPANY_QUERY,
|
||||
template: (searchString: string) => ({
|
||||
@ -74,6 +76,7 @@ export const emailFilter = {
|
||||
key: 'email',
|
||||
label: 'Email',
|
||||
icon: <FaEnvelope />,
|
||||
type: 'text',
|
||||
operands: [
|
||||
{
|
||||
label: 'Contains',
|
||||
@ -96,6 +99,7 @@ export const cityFilter = {
|
||||
key: 'city',
|
||||
label: 'City',
|
||||
icon: <FaMapPin />,
|
||||
type: 'text',
|
||||
operands: [
|
||||
{
|
||||
label: 'Contains',
|
||||
|
||||
Reference in New Issue
Block a user