diff --git a/front/src/components/editable-cell/EditableDate.tsx b/front/src/components/editable-cell/EditableDate.tsx index bb65a66f9..38db1156e 100644 --- a/front/src/components/editable-cell/EditableDate.tsx +++ b/front/src/components/editable-cell/EditableDate.tsx @@ -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` 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 ( - - -
{children}
-
-
- ); + const DatePickerContainer = ({ children }: DatePickerContainerProps) => { + return {children}; }; return ( @@ -86,7 +75,7 @@ function EditableDate({ setInputValue(date); }} customInput={} - customContainer={DatePickerContainer} + customCalendarContainer={DatePickerContainer} /> } diff --git a/front/src/components/form/DatePicker.tsx b/front/src/components/form/DatePicker.tsx index f58f1d648..06d20d25c 100644 --- a/front/src/components/form/DatePicker.tsx +++ b/front/src/components/form/DatePicker.tsx @@ -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 : } - calendarContainer={customContainer ? customContainer : undefined} + calendarContainer={ + customCalendarContainer ? customCalendarContainer : undefined + } /> ); diff --git a/front/src/components/table/table-header/DropdownButton.tsx b/front/src/components/table/table-header/DropdownButton.tsx index 0b8e42606..fa232e62f 100644 --- a/front/src/components/table/table-header/DropdownButton.tsx +++ b/front/src/components/table/table-header/DropdownButton.tsx @@ -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}; diff --git a/front/src/components/table/table-header/FilterDropdownButton.tsx b/front/src/components/table/table-header/FilterDropdownButton.tsx index f276cbd93..cdea885f5 100644 --- a/front/src/components/table/table-header/FilterDropdownButton.tsx +++ b/front/src/components/table/table-header/FilterDropdownButton.tsx @@ -10,6 +10,8 @@ import { SearchResultsType, useSearch, } from '../../../services/api/search/search'; +import DatePicker from '../../form/DatePicker'; +import styled from '@emotion/styled'; type OwnProps = { isFilterSelected: boolean; @@ -26,7 +28,8 @@ export const FilterDropdownButton = ({ }: OwnProps) => { const [isUnfolded, setIsUnfolded] = useState(false); - const [isOptionUnfolded, setIsOptionUnfolded] = useState(false); + const [isOperandSelectionUnfolded, setIsOperandSelectionUnfolded] = + useState(false); const [selectedFilter, setSelectedFilter] = useState< FilterConfigType | undefined @@ -39,19 +42,19 @@ export const FilterDropdownButton = ({ 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) => ( { setSelectedFilterOperand(filterOperand); - setIsOptionUnfolded(false); + setIsOperandSelectionUnfolded(false); }} > {filterOperand.label} @@ -59,6 +62,21 @@ export const FilterDropdownButton = ({ ), ); + const renderFilterSelection = availableFilters.map((filter, index) => ( + { + setSelectedFilter(filter); + setSelectedFilterOperand(filter.operands[0]); + filter.searchConfig && setFilterSearch(filter.searchConfig); + setSearchInput(''); + }} + > + {filter.icon} + {filter.label} + + )); + const renderSearchResults = ( filterSearchResults: SearchResultsType, selectedFilter: FilterConfigType, @@ -93,22 +111,7 @@ export const FilterDropdownButton = ({ )); }; - const renderSelectFilterITems = availableFilters.map((filter, index) => ( - { - setSelectedFilter(filter); - setSelectedFilterOperand(filter.operands[0]); - filter.searchConfig && setFilterSearch(filter.searchConfig); - setSearchInput(''); - }} - > - {filter.icon} - {filter.label} - - )); - - function renderFilterDropdown( + function renderValueSelection( selectedFilter: FilterConfigType, selectedFilterOperand: FilterOperandType, ) { @@ -116,38 +119,65 @@ export const FilterDropdownButton = ({ <> setIsOptionUnfolded(true)} + onClick={() => setIsOperandSelectionUnfolded(true)} > {selectedFilterOperand.label} - ) => { - 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) && ( + ) => { + 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' && ( + { + onFilterSelect({ + key: selectedFilter.key, + label: selectedFilter.label, + value: date.toISOString(), + displayValue: date.toLocaleDateString(), + icon: selectedFilter.icon, + operand: selectedFilterOperand, + }); + }} + customInput={<>} + customCalendarContainer={styled.div` + top: -10px; + `} + /> + )} - {filterSearchResults && + {selectedFilter.type === 'relation' && + filterSearchResults && renderSearchResults( filterSearchResults, selectedFilter, @@ -165,11 +195,11 @@ export const FilterDropdownButton = ({ setIsUnfolded={setIsUnfolded} resetState={resetState} > - {selectedFilter && selectedFilterOperand - ? isOptionUnfolded - ? renderSelectOptionItems - : renderFilterDropdown(selectedFilter, selectedFilterOperand) - : renderSelectFilterITems} + {selectedFilter + ? isOperandSelectionUnfolded + ? renderOperandSelection + : renderValueSelection(selectedFilter, selectedFilterOperand) + : renderFilterSelection} ); }; diff --git a/front/src/interfaces/filters/interface.ts b/front/src/interfaces/filters/interface.ts index 5baa5d623..dc178dd41 100644 --- a/front/src/interfaces/filters/interface.ts +++ b/front/src/interfaces/filters/interface.ts @@ -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[]; } & (WhereType extends UnknownType ? { searchConfig?: SearchConfigType } diff --git a/front/src/pages/companies/companies-filters.tsx b/front/src/pages/companies/companies-filters.tsx index 96ef306c3..25d9c5c73 100644 --- a/front/src/pages/companies/companies-filters.tsx +++ b/front/src/pages/companies/companies-filters.tsx @@ -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: , + type: 'text', operands: [ { label: 'Contains', @@ -28,6 +35,7 @@ export const urlFilter = { key: 'company_domain_name', label: 'Url', icon: , + type: 'text', operands: [ { label: 'Contains', @@ -50,6 +58,7 @@ export const addressFilter = { key: 'company_address', label: 'Address', icon: , + type: 'text', operands: [ { label: 'Contains', @@ -72,6 +81,7 @@ export const employeesFilter = { key: 'company_employees', label: 'Employees', icon: , + type: 'text', operands: [ { label: 'Greater than', @@ -94,9 +104,37 @@ export const employeesFilter = { ], } satisfies FilterConfigType; +export const creationDateFilter = { + key: 'company_created_at', + label: 'Created At', + icon: , + 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; + export const availableFilters = [ nameFilter, urlFilter, addressFilter, employeesFilter, + creationDateFilter, ]; diff --git a/front/src/pages/people/people-filters.tsx b/front/src/pages/people/people-filters.tsx index 8daa149dd..78f3e0f95 100644 --- a/front/src/pages/people/people-filters.tsx +++ b/front/src/pages/people/people-filters.tsx @@ -11,6 +11,7 @@ export const fullnameFilter = { key: 'fullname', label: 'People', icon: , + type: 'text', operands: [ { label: 'Contains', @@ -41,6 +42,7 @@ export const companyFilter = { key: 'company_name', label: 'Company', icon: , + type: 'relation', searchConfig: { query: SEARCH_COMPANY_QUERY, template: (searchString: string) => ({ @@ -74,6 +76,7 @@ export const emailFilter = { key: 'email', label: 'Email', icon: , + type: 'text', operands: [ { label: 'Contains', @@ -96,6 +99,7 @@ export const cityFilter = { key: 'city', label: 'City', icon: , + type: 'text', operands: [ { label: 'Contains',