diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterAddFilterRuleSelect.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterAddFilterRuleSelect.tsx index da22e0d22..9bdedea1d 100644 --- a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterAddFilterRuleSelect.tsx +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterAddFilterRuleSelect.tsx @@ -1,21 +1,23 @@ import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; import { useChildRecordFiltersAndRecordFilterGroups } from '@/object-record/advanced-filter/hooks/useChildRecordFiltersAndRecordFilterGroups'; import { useDefaultFieldMetadataItemForFilter } from '@/object-record/advanced-filter/hooks/useDefaultFieldMetadataItemForFilter'; +import { useSetRecordFilterUsedInAdvancedFilterDropdownRow } from '@/object-record/advanced-filter/hooks/useSetRecordFilterUsedInAdvancedFilterDropdownRow'; import { getAdvancedFilterAddFilterRuleSelectDropdownId } from '@/object-record/advanced-filter/utils/getAdvancedFilterAddFilterRuleSelectDropdownId'; import { useUpsertRecordFilterGroup } from '@/object-record/record-filter-group/hooks/useUpsertRecordFilterGroup'; import { RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup'; import { RecordFilterGroupLogicalOperator } from '@/object-record/record-filter-group/types/RecordFilterGroupLogicalOperator'; import { useUpsertRecordFilter } from '@/object-record/record-filter/hooks/useUpsertRecordFilter'; +import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly'; -import { v4 } from 'uuid'; import { isDefined } from 'twenty-shared/utils'; import { IconLibraryPlus, IconPlus } from 'twenty-ui/display'; import { LightButton } from 'twenty-ui/input'; import { MenuItem } from 'twenty-ui/navigation'; +import { v4 } from 'uuid'; type AdvancedFilterAddFilterRuleSelectProps = { recordFilterGroup: RecordFilterGroup; @@ -45,6 +47,9 @@ export const AdvancedFilterAddFilterRuleSelect = ({ const { defaultFieldMetadataItemForFilter } = useDefaultFieldMetadataItemForFilter(); + const { setRecordFilterUsedInAdvancedFilterDropdownRow } = + useSetRecordFilterUsedInAdvancedFilterDropdownRow(); + const handleAddFilter = () => { if (!isDefined(defaultFieldMetadataItemForFilter)) { throw new Error('Missing default field metadata item for filter'); @@ -56,7 +61,7 @@ export const AdvancedFilterAddFilterRuleSelect = ({ defaultFieldMetadataItemForFilter.type, ); - upsertRecordFilter({ + const newRecordFilter: RecordFilter = { id: v4(), fieldMetadataId: defaultFieldMetadataItemForFilter.id, type: filterType, @@ -68,7 +73,11 @@ export const AdvancedFilterAddFilterRuleSelect = ({ recordFilterGroupId: recordFilterGroup.id, positionInRecordFilterGroup: newPositionInRecordFilterGroup, label: defaultFieldMetadataItemForFilter.label, - }); + }; + + upsertRecordFilter(newRecordFilter); + + setRecordFilterUsedInAdvancedFilterDropdownRow(newRecordFilter); }; const handleAddFilterGroup = () => { @@ -97,7 +106,7 @@ export const AdvancedFilterAddFilterRuleSelect = ({ defaultFieldMetadataItemForFilter.type, ); - upsertRecordFilter({ + const newRecordFilter: RecordFilter = { id: v4(), fieldMetadataId: defaultFieldMetadataItemForFilter.id, type: filterType, @@ -109,7 +118,11 @@ export const AdvancedFilterAddFilterRuleSelect = ({ recordFilterGroupId: newRecordFilterGroupId, positionInRecordFilterGroup: 1, label: defaultFieldMetadataItemForFilter.label, - }); + }; + + upsertRecordFilter(newRecordFilter); + + setRecordFilterUsedInAdvancedFilterDropdownRow(newRecordFilter); }; const isFilterRuleGroupOptionVisible = !isDefined( diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterDropdownDateInput.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterDropdownDateInput.tsx new file mode 100644 index 000000000..1a3f4005f --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterDropdownDateInput.tsx @@ -0,0 +1,122 @@ +import { v4 } from 'uuid'; + +import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; +import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector'; +import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState'; +import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState'; +import { getRelativeDateDisplayValue } from '@/object-record/object-filter-dropdown/utils/getRelativeDateDisplayValue'; +import { useApplyRecordFilter } from '@/object-record/record-filter/hooks/useApplyRecordFilter'; +import { DateTimePicker } from '@/ui/input/components/internal/date/components/InternalDatePicker'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import { computeVariableDateViewFilterValue } from '@/views/view-filter-value/utils/computeVariableDateViewFilterValue'; +import { + resolveDateViewFilterValue, + VariableDateViewFilterValueDirection, + VariableDateViewFilterValueUnit, +} from '@/views/view-filter-value/utils/resolveDateViewFilterValue'; +import { useState } from 'react'; +import { isDefined } from 'twenty-shared/utils'; +import { FieldMetadataType } from '~/generated-metadata/graphql'; + +export const AdvancedFilterDropdownDateInput = () => { + const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2( + fieldMetadataItemUsedInDropdownComponentSelector, + ); + + const selectedOperandInDropdown = useRecoilComponentValueV2( + selectedOperandInDropdownComponentState, + ); + + const selectedFilter = useRecoilComponentValueV2( + selectedFilterComponentState, + ); + + const { applyRecordFilter } = useApplyRecordFilter(); + + const initialFilterValue = selectedFilter + ? resolveDateViewFilterValue(selectedFilter) + : null; + const [internalDate, setInternalDate] = useState( + initialFilterValue instanceof Date ? initialFilterValue : null, + ); + + const isDateTimeInput = + fieldMetadataItemUsedInDropdown?.type === FieldMetadataType.DATE_TIME; + + const handleAbsoluteDateChange = (newDate: Date | null) => { + setInternalDate(newDate); + + if (!fieldMetadataItemUsedInDropdown || !selectedOperandInDropdown) return; + + const newDisplayValue = isDefined(newDate) + ? newDate.toLocaleDateString() + : ''; + + applyRecordFilter({ + id: selectedFilter?.id ? selectedFilter.id : v4(), + fieldMetadataId: fieldMetadataItemUsedInDropdown.id, + value: newDate?.toISOString() ?? '', + operand: selectedOperandInDropdown, + displayValue: newDisplayValue, + recordFilterGroupId: selectedFilter?.recordFilterGroupId, + positionInRecordFilterGroup: selectedFilter?.positionInRecordFilterGroup, + type: getFilterTypeFromFieldType(fieldMetadataItemUsedInDropdown.type), + label: fieldMetadataItemUsedInDropdown.label, + }); + }; + + const handleRelativeDateChange = ( + relativeDate: { + direction: VariableDateViewFilterValueDirection; + amount?: number; + unit: VariableDateViewFilterValueUnit; + } | null, + ) => { + if (!fieldMetadataItemUsedInDropdown || !selectedOperandInDropdown) return; + + const value = relativeDate + ? computeVariableDateViewFilterValue( + relativeDate.direction, + relativeDate.amount, + relativeDate.unit, + ) + : ''; + + applyRecordFilter({ + id: selectedFilter?.id ? selectedFilter.id : v4(), + fieldMetadataId: fieldMetadataItemUsedInDropdown.id, + value, + operand: selectedOperandInDropdown, + displayValue: getRelativeDateDisplayValue(relativeDate), + recordFilterGroupId: selectedFilter?.recordFilterGroupId, + positionInRecordFilterGroup: selectedFilter?.positionInRecordFilterGroup, + type: getFilterTypeFromFieldType(fieldMetadataItemUsedInDropdown.type), + label: fieldMetadataItemUsedInDropdown.label, + }); + }; + + const isRelativeOperand = + selectedOperandInDropdown === ViewFilterOperand.IsRelative; + + const resolvedValue = selectedFilter + ? resolveDateViewFilterValue(selectedFilter) + : null; + + const relativeDate = + resolvedValue && !(resolvedValue instanceof Date) + ? resolvedValue + : undefined; + + return ( + + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterDropdownFilterInput.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterDropdownFilterInput.tsx new file mode 100644 index 000000000..d94cea8e6 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterDropdownFilterInput.tsx @@ -0,0 +1,83 @@ +import { ObjectFilterDropdownOptionSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownOptionSelect'; +import { ObjectFilterDropdownRatingInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRatingInput'; +import { ObjectFilterDropdownRecordSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect'; +import { ObjectFilterDropdownSearchInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownSearchInput'; +import { ObjectFilterDropdownSourceSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownSourceSelect'; +import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; + +import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; +import { AdvancedFilterDropdownDateInput } from '@/object-record/advanced-filter/components/AdvancedFilterDropdownDateInput'; +import { ObjectFilterDropdownBooleanSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownBooleanSelect'; +import { ObjectFilterDropdownTextInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownTextInput'; +import { DATE_FILTER_TYPES } from '@/object-record/object-filter-dropdown/constants/DateFilterTypes'; +import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector'; +import { subFieldNameUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState'; +import { isFilterOnActorSourceSubField } from '@/object-record/object-filter-dropdown/utils/isFilterOnActorSourceSubField'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { isDefined } from 'twenty-shared/utils'; + +type AdvancedFilterDropdownFilterInputProps = { + filterDropdownId?: string; + recordFilterId?: string; +}; + +export const AdvancedFilterDropdownFilterInput = ({ + filterDropdownId, + recordFilterId, +}: AdvancedFilterDropdownFilterInputProps) => { + const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2( + fieldMetadataItemUsedInDropdownComponentSelector, + filterDropdownId, + ); + + const subFieldNameUsedInDropdown = useRecoilComponentValueV2( + subFieldNameUsedInDropdownComponentState, + filterDropdownId, + ); + + if (!isDefined(fieldMetadataItemUsedInDropdown)) { + return null; + } + + const filterType = getFilterTypeFromFieldType( + fieldMetadataItemUsedInDropdown.type, + ); + + const isActorSourceCompositeFilter = isFilterOnActorSourceSubField( + subFieldNameUsedInDropdown, + ); + + return ( + <> + {filterType === 'RATING' && } + {DATE_FILTER_TYPES.includes(filterType) && ( + + )} + {filterType === 'RELATION' && ( + <> + + + + + )} + {filterType === 'ACTOR' && + (isActorSourceCompositeFilter ? ( + <> + + + ) : ( + <> + + + ))} + {['SELECT', 'MULTI_SELECT'].includes(filterType) && ( + <> + + + + + )} + {filterType === 'BOOLEAN' && } + + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterDropdownNumberInput.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterDropdownNumberInput.tsx new file mode 100644 index 000000000..e8cf61cae --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterDropdownNumberInput.tsx @@ -0,0 +1,64 @@ +import { useState } from 'react'; + +import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; +import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector'; +import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState'; +import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState'; +import { useApplyRecordFilter } from '@/object-record/record-filter/hooks/useApplyRecordFilter'; +import { TextInputV2 } from '@/ui/input/components/TextInputV2'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { v4 } from 'uuid'; + +export const AdvancedFilterDropdownNumberInput = () => { + const selectedOperandInDropdown = useRecoilComponentValueV2( + selectedOperandInDropdownComponentState, + ); + + const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2( + fieldMetadataItemUsedInDropdownComponentSelector, + ); + + const selectedFilter = useRecoilComponentValueV2( + selectedFilterComponentState, + ); + + const { applyRecordFilter } = useApplyRecordFilter(); + + const [inputValue, setInputValue] = useState( + () => selectedFilter?.value || '', + ); + + const handleChange = (newValue: string) => { + if (!fieldMetadataItemUsedInDropdown || !selectedOperandInDropdown) { + return; + } + + setInputValue(newValue); + + applyRecordFilter({ + id: selectedFilter?.id ? selectedFilter.id : v4(), + fieldMetadataId: fieldMetadataItemUsedInDropdown?.id ?? '', + value: newValue, + operand: selectedOperandInDropdown, + displayValue: newValue, + type: getFilterTypeFromFieldType(fieldMetadataItemUsedInDropdown.type), + label: fieldMetadataItemUsedInDropdown.label, + recordFilterGroupId: selectedFilter?.recordFilterGroupId, + positionInRecordFilterGroup: selectedFilter?.positionInRecordFilterGroup, + }); + }; + + if (!selectedOperandInDropdown || !fieldMetadataItemUsedInDropdown) { + return null; + } + + return ( + + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterDropdownTextInput.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterDropdownTextInput.tsx new file mode 100644 index 000000000..37d03684e --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterDropdownTextInput.tsx @@ -0,0 +1,63 @@ +import { useState } from 'react'; + +import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; +import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector'; +import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState'; +import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState'; +import { useApplyRecordFilter } from '@/object-record/record-filter/hooks/useApplyRecordFilter'; +import { TextInputV2 } from '@/ui/input/components/TextInputV2'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { v4 } from 'uuid'; + +export const AdvancedFilterDropdownTextInput = () => { + const selectedOperandInDropdown = useRecoilComponentValueV2( + selectedOperandInDropdownComponentState, + ); + + const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2( + fieldMetadataItemUsedInDropdownComponentSelector, + ); + + const selectedFilter = useRecoilComponentValueV2( + selectedFilterComponentState, + ); + + const { applyRecordFilter } = useApplyRecordFilter(); + + const [inputValue, setInputValue] = useState( + () => selectedFilter?.value || '', + ); + + const handleChange = (newValue: string) => { + if (!fieldMetadataItemUsedInDropdown || !selectedOperandInDropdown) { + return; + } + + setInputValue(newValue); + + applyRecordFilter({ + id: selectedFilter?.id ? selectedFilter.id : v4(), + fieldMetadataId: fieldMetadataItemUsedInDropdown?.id ?? '', + value: newValue, + operand: selectedOperandInDropdown, + displayValue: newValue, + type: getFilterTypeFromFieldType(fieldMetadataItemUsedInDropdown.type), + label: fieldMetadataItemUsedInDropdown.label, + recordFilterGroupId: selectedFilter?.recordFilterGroupId, + positionInRecordFilterGroup: selectedFilter?.positionInRecordFilterGroup, + }); + }; + + if (!selectedOperandInDropdown || !fieldMetadataItemUsedInDropdown) { + return null; + } + + return ( + + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRecordFilterRow.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRecordFilterRow.tsx index 7c57f6c5d..7fab53937 100644 --- a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRecordFilterRow.tsx +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRecordFilterRow.tsx @@ -3,7 +3,8 @@ import { AdvancedFilterFieldSelectDropdownButton } from '@/object-record/advance import { AdvancedFilterLogicalOperatorCell } from '@/object-record/advanced-filter/components/AdvancedFilterLogicalOperatorCell'; import { AdvancedFilterRecordFilterOperandSelect } from '@/object-record/advanced-filter/components/AdvancedFilterRecordFilterOperandSelect'; import { AdvancedFilterRecordFilterOptionsDropdown } from '@/object-record/advanced-filter/components/AdvancedFilterRecordFilterOptionsDropdown'; -import { AdvancedFilterValueInputDropdownButton } from '@/object-record/advanced-filter/components/AdvancedFilterValueInputDropdownButton'; +import { AdvancedFilterValueInput } from '@/object-record/advanced-filter/components/AdvancedFilterValueInput'; +import { getAdvancedFilterObjectFilterDropdownComponentInstanceId } from '@/object-record/advanced-filter/utils/getAdvancedFilterObjectFilterDropdownComponentInstanceId'; import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext'; import { RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup'; import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; @@ -19,7 +20,11 @@ export const AdvancedFilterRecordFilterRow = ({ }) => { return ( - + diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterSubFieldSelectMenu.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterSubFieldSelectMenu.tsx index 5b4353c56..0db3aef9f 100644 --- a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterSubFieldSelectMenu.tsx +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterSubFieldSelectMenu.tsx @@ -1,18 +1,14 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; -import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { isDefined } from 'twenty-shared/utils'; import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { useAdvancedFilterFieldSelectDropdown } from '@/object-record/advanced-filter/hooks/useAdvancedFilterFieldSelectDropdown'; import { useSelectFieldUsedInAdvancedFilterDropdown } from '@/object-record/advanced-filter/hooks/useSelectFieldUsedInAdvancedFilterDropdown'; -import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState'; import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector'; -import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState'; import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState'; import { objectFilterDropdownSubMenuFieldTypeComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSubMenuFieldTypeComponentState'; -import { subFieldNameUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState'; import { getCompositeSubFieldLabel } from '@/object-record/object-filter-dropdown/utils/getCompositeSubFieldLabel'; import { getFilterableFieldTypeLabel } from '@/object-record/object-filter-dropdown/utils/getFilterableFieldTypeLabel'; import { SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsCompositeFieldTypeConfigs'; @@ -35,14 +31,6 @@ export const AdvancedFilterSubFieldSelectMenu = ({ fieldMetadataItemUsedInDropdownComponentSelector, ); - const [, setObjectFilterDropdownFilterIsSelected] = useRecoilComponentStateV2( - objectFilterDropdownFilterIsSelectedComponentState, - ); - - const [, setSubFieldNameUsedInDropdown] = useRecoilComponentStateV2( - subFieldNameUsedInDropdownComponentState, - ); - const [, setObjectFilterDropdownIsSelectingCompositeField] = useRecoilComponentStateV2( objectFilterDropdownIsSelectingCompositeFieldComponentState, @@ -55,10 +43,6 @@ export const AdvancedFilterSubFieldSelectMenu = ({ objectFilterDropdownSubMenuFieldTypeComponentState, ); - const setFieldMetadataItemIdUsedInDropdown = useSetRecoilComponentStateV2( - fieldMetadataItemIdUsedInDropdownComponentState, - ); - const { closeAdvancedFilterFieldSelectDropdown } = useAdvancedFilterFieldSelectDropdown(recordFilterId); @@ -83,11 +67,8 @@ export const AdvancedFilterSubFieldSelectMenu = ({ }; const handleSubMenuBack = () => { - setFieldMetadataItemIdUsedInDropdown(null); setObjectFilterDropdownSubMenuFieldType(null); setObjectFilterDropdownIsSelectingCompositeField(false); - setObjectFilterDropdownFilterIsSelected(false); - setSubFieldNameUsedInDropdown(null); }; if (!isDefined(objectFilterDropdownSubMenuFieldType)) { diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterValueInputDropdownButton.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterValueInput.tsx similarity index 60% rename from packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterValueInputDropdownButton.tsx rename to packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterValueInput.tsx index 224b720c3..b804e2055 100644 --- a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterValueInputDropdownButton.tsx +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterValueInput.tsx @@ -1,64 +1,61 @@ +import { AdvancedFilterDropdownFilterInput } from '@/object-record/advanced-filter/components/AdvancedFilterDropdownFilterInput'; +import { AdvancedFilterDropdownTextInput } from '@/object-record/advanced-filter/components/AdvancedFilterDropdownTextInput'; import { AdvancedFilterValueInputDropdownButtonClickableSelect } from '@/object-record/advanced-filter/components/AdvancedFilterValueInputDropdownButtonClickableSelect'; import { DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET } from '@/object-record/advanced-filter/constants/DefaultAdvancedFilterDropdownOffset'; -import { ObjectFilterDropdownFilterInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput'; -import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState'; +import { NUMBER_FILTER_TYPES } from '@/object-record/object-filter-dropdown/constants/NumberFilterTypes'; +import { TEXT_FILTER_TYPES } from '@/object-record/object-filter-dropdown/constants/TextFilterTypes'; import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState'; -import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState'; -import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState'; import { configurableViewFilterOperands } from '@/object-record/object-filter-dropdown/utils/configurableViewFilterOperands'; import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; +import { DropdownOffset } from '@/ui/layout/dropdown/types/DropdownOffset'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import styled from '@emotion/styled'; +import { isDefined } from 'twenty-shared/utils'; const StyledValueDropdownContainer = styled.div` flex: 3; `; -type AdvancedFilterValueInputDropdownButtonProps = { +type AdvancedFilterValueInputProps = { recordFilterId: string; }; -export const AdvancedFilterValueInputDropdownButton = ({ +export const AdvancedFilterValueInput = ({ recordFilterId, -}: AdvancedFilterValueInputDropdownButtonProps) => { +}: AdvancedFilterValueInputProps) => { const dropdownId = `advanced-filter-view-filter-value-input-${recordFilterId}`; const currentRecordFilters = useRecoilComponentValueV2( currentRecordFiltersComponentState, ); - const filter = currentRecordFilters.find( + const recordFilter = currentRecordFilters.find( (recordFilter) => recordFilter.id === recordFilterId, ); - const isDisabled = !filter?.fieldMetadataId || !filter.operand; + const isDisabled = !recordFilter?.fieldMetadataId || !recordFilter.operand; const setObjectFilterDropdownSearchInput = useSetRecoilComponentStateV2( objectFilterDropdownSearchInputComponentState, ); - const setFieldMetadataItemIdUsedInDropdown = useSetRecoilComponentStateV2( - fieldMetadataItemIdUsedInDropdownComponentState, - ); - - const setSelectedOperandInDropdown = useSetRecoilComponentStateV2( - selectedOperandInDropdownComponentState, - ); - - const setSelectedFilter = useSetRecoilComponentStateV2( - selectedFilterComponentState, - ); - const operandHasNoInput = - filter && !configurableViewFilterOperands.has(filter.operand); + recordFilter && !configurableViewFilterOperands.has(recordFilter.operand); const handleFilterValueDropdownClose = () => { setObjectFilterDropdownSearchInput(''); }; + const filterType = recordFilter?.type; + + const dropdownContentOffset = + filterType === 'DATE' || filterType === 'DATE_TIME' + ? ({ y: -33, x: 0 } satisfies DropdownOffset) + : DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET; + return ( {operandHasNoInput ? ( @@ -67,6 +64,10 @@ export const AdvancedFilterValueInputDropdownButton = ({ + ) : isDefined(filterType) && + (TEXT_FILTER_TYPES.includes(filterType) || + NUMBER_FILTER_TYPES.includes(filterType)) ? ( + ) : ( } - onOpen={() => { - setFieldMetadataItemIdUsedInDropdown(filter.fieldMetadataId); - setSelectedOperandInDropdown(filter.operand); - setSelectedFilter(filter); - }} dropdownComponents={ - + } dropdownHotkeyScope={{ scope: dropdownId }} - dropdownOffset={DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET} + dropdownOffset={dropdownContentOffset} dropdownPlacement="bottom-start" dropdownMenuWidth={280} onClose={handleFilterValueDropdownClose} diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterValueInputDropdownButtonClickableSelect.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterValueInputDropdownButtonClickableSelect.tsx index 62648bb2f..18d1871d4 100644 --- a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterValueInputDropdownButtonClickableSelect.tsx +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterValueInputDropdownButtonClickableSelect.tsx @@ -3,10 +3,30 @@ import { getAdvancedFilterInputPlaceholderText } from '@/object-record/advanced- import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; import { SelectControl } from '@/ui/input/components/SelectControl'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; + import { isNonEmptyString } from '@sniptt/guards'; +import styled from '@emotion/styled'; + import { isDefined } from 'twenty-shared/utils'; +// TODO: factorize this with https://github.com/twentyhq/core-team-issues/issues/752 +const StyledControlContainer = styled.div` + display: flex; + align-items: center; + gap: ${({ theme }) => theme.spacing(1)}; + box-sizing: border-box; + height: ${({ theme }) => theme.spacing(8)}; + max-width: 100%; + padding: 0 ${({ theme }) => theme.spacing(2)}; + background-color: ${({ theme }) => theme.background.transparent.lighter}; + border: 1px solid ${({ theme }) => theme.border.color.medium}; + border-radius: ${({ theme }) => theme.border.radius.sm}; + color: ${({ theme }) => theme.font.color.primary}; + cursor: pointer; + text-align: left; +`; + type AdvancedFilterValueInputDropdownButtonClickableSelectProps = { recordFilterId: string; }; @@ -42,7 +62,12 @@ export const AdvancedFilterValueInputDropdownButtonClickableSelect = ({ ? placeholderText : (recordFilter?.displayValue ?? ''); - return ( + const isDateTimeType = + recordFilter?.type === 'DATE' || recordFilter?.type === 'DATE_TIME'; + + return isDateTimeType ? ( + {advancedFilterInputText} + ) : ( ); }; diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useSetRecordFilterUsedInAdvancedFilterDropdownRow.ts b/packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useSetRecordFilterUsedInAdvancedFilterDropdownRow.ts new file mode 100644 index 000000000..f9a00b8fd --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useSetRecordFilterUsedInAdvancedFilterDropdownRow.ts @@ -0,0 +1,47 @@ +import { getAdvancedFilterObjectFilterDropdownComponentInstanceId } from '@/object-record/advanced-filter/utils/getAdvancedFilterObjectFilterDropdownComponentInstanceId'; +import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState'; +import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState'; +import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState'; +import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; +import { useRecoilCallback } from 'recoil'; + +export const useSetRecordFilterUsedInAdvancedFilterDropdownRow = () => { + const setRecordFilterUsedInAdvancedFilterDropdownRow = useRecoilCallback( + ({ set }) => + (recordFilter: RecordFilter) => { + const advancedFilterRowObjectFilterDropdownComponentInstanceId = + getAdvancedFilterObjectFilterDropdownComponentInstanceId( + recordFilter.id, + ); + + set( + fieldMetadataItemIdUsedInDropdownComponentState.atomFamily({ + instanceId: + advancedFilterRowObjectFilterDropdownComponentInstanceId, + }), + recordFilter.fieldMetadataId, + ); + + set( + selectedOperandInDropdownComponentState.atomFamily({ + instanceId: + advancedFilterRowObjectFilterDropdownComponentInstanceId, + }), + recordFilter.operand, + ); + + set( + selectedFilterComponentState.atomFamily({ + instanceId: + advancedFilterRowObjectFilterDropdownComponentInstanceId, + }), + recordFilter, + ); + }, + [], + ); + + return { + setRecordFilterUsedInAdvancedFilterDropdownRow, + }; +}; diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/utils/getAdvancedFilterObjectFilterDropdownComponentInstanceId.ts b/packages/twenty-front/src/modules/object-record/advanced-filter/utils/getAdvancedFilterObjectFilterDropdownComponentInstanceId.ts new file mode 100644 index 000000000..f42c2e4ae --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/utils/getAdvancedFilterObjectFilterDropdownComponentInstanceId.ts @@ -0,0 +1,5 @@ +export const getAdvancedFilterObjectFilterDropdownComponentInstanceId = ( + recordFilterId: string, +) => { + return `advanced-filter-${recordFilterId}`; +}; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/AdvancedFilterButton.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/AdvancedFilterButton.tsx index db9a50cd2..4fde4314e 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/AdvancedFilterButton.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/AdvancedFilterButton.tsx @@ -8,6 +8,8 @@ import { RecordFilterGroupLogicalOperator } from '@/object-record/record-filter- import { useUpsertRecordFilter } from '@/object-record/record-filter/hooks/useUpsertRecordFilter'; import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands'; +import { useSetRecordFilterUsedInAdvancedFilterDropdownRow } from '@/object-record/advanced-filter/hooks/useSetRecordFilterUsedInAdvancedFilterDropdownRow'; +import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { ADVANCED_FILTER_DROPDOWN_ID } from '@/views/constants/AdvancedFilterDropdownId'; @@ -16,11 +18,11 @@ import { ViewFilterGroupLogicalOperator } from '@/views/types/ViewFilterGroupLog import styled from '@emotion/styled'; import { useLingui } from '@lingui/react/macro'; import { useRecoilValue } from 'recoil'; -import { v4 } from 'uuid'; import { isDefined } from 'twenty-shared/utils'; +import { Pill } from 'twenty-ui/components'; import { IconFilter } from 'twenty-ui/display'; import { MenuItemLeftContent, StyledMenuItemBase } from 'twenty-ui/navigation'; -import { Pill } from 'twenty-ui/components'; +import { v4 } from 'uuid'; export const StyledContainer = styled.div` align-items: center; @@ -80,6 +82,9 @@ export const AdvancedFilterButton = () => { currentRecordFilterGroupsComponentState, ); + const { setRecordFilterUsedInAdvancedFilterDropdownRow } = + useSetRecordFilterUsedInAdvancedFilterDropdownRow(); + const handleClick = () => { if (!isDefined(currentView)) { throw new Error('Missing current view id'); @@ -118,7 +123,7 @@ export const AdvancedFilterButton = () => { filterType, })[0]; - upsertRecordFilter({ + const newRecordFilter: RecordFilter = { id: v4(), fieldMetadataId: defaultFieldMetadataItem.id, operand: firstOperand, @@ -128,7 +133,11 @@ export const AdvancedFilterButton = () => { type: getFilterTypeFromFieldType(defaultFieldMetadataItem.type), label: defaultFieldMetadataItem.label, positionInRecordFilterGroup: 1, - }); + }; + + upsertRecordFilter(newRecordFilter); + + setRecordFilterUsedInAdvancedFilterDropdownRow(newRecordFilter); } closeObjectFilterDropdown(); diff --git a/packages/twenty-front/src/modules/ui/input/components/SelectControl.tsx b/packages/twenty-front/src/modules/ui/input/components/SelectControl.tsx index 4eb3a23f0..d5078f666 100644 --- a/packages/twenty-front/src/modules/ui/input/components/SelectControl.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/SelectControl.tsx @@ -7,6 +7,7 @@ import { SelectOption } from 'twenty-ui/input'; export type SelectControlTextAccent = 'default' | 'placeholder'; +// TODO: factorize this with https://github.com/twentyhq/core-team-issues/issues/752 const StyledControlContainer = styled.div<{ disabled?: boolean; hasIcon: boolean;