diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownContentWrapper.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownContentWrapper.tsx new file mode 100644 index 000000000..74d77a3a3 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownContentWrapper.tsx @@ -0,0 +1,38 @@ +import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; +import { DATE_FILTER_TYPES } from '@/object-record/object-filter-dropdown/constants/DateFilterTypes'; +import { DATE_PICKER_DROPDOWN_CONTENT_WIDTH } from '@/object-record/object-filter-dropdown/constants/DatePickerDropdownContentWidth'; +import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector'; +import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; +import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { isDefined } from 'twenty-shared/utils'; + +export const ObjectFilterDropdownContentWrapper = ({ + children, +}: React.PropsWithChildren) => { + const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2( + fieldMetadataItemUsedInDropdownComponentSelector, + ); + + if (!isDefined(fieldMetadataItemUsedInDropdown)) { + return null; + } + + const filterType = getFilterTypeFromFieldType( + fieldMetadataItemUsedInDropdown.type, + ); + + const isDateFilter = DATE_FILTER_TYPES.includes(filterType); + + return ( + + {children} + + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput.tsx index 39253f821..4ca9a2244 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput.tsx @@ -5,22 +5,19 @@ import { ObjectFilterDropdownRatingInput } from '@/object-record/object-filter-d import { ObjectFilterDropdownRecordSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect'; import { ObjectFilterDropdownSearchInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownSearchInput'; import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; -import { ViewBarFilterDropdownVectorSearchInput } from '@/views/components/ViewBarFilterDropdownVectorSearchInput'; + import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand'; import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; import { ObjectFilterDropdownBooleanSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownBooleanSelect'; -import { ObjectFilterDropdownFilterInputHeader } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInputHeader'; import { ObjectFilterDropdownInnerSelectOperandDropdown } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownInnerSelectOperandDropdown'; import { ObjectFilterDropdownTextInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownTextInput'; +import { ObjectFilterDropdownVectorSearchInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownVectorSearchInput'; import { DATE_FILTER_TYPES } from '@/object-record/object-filter-dropdown/constants/DateFilterTypes'; -import { DATE_PICKER_DROPDOWN_CONTENT_WIDTH } from '@/object-record/object-filter-dropdown/constants/DatePickerDropdownContentWidth'; 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 { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector'; import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState'; -import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; -import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { isDefined } from 'twenty-shared/utils'; @@ -62,11 +59,7 @@ export const ObjectFilterDropdownFilterInput = ({ selectedOperandInDropdown === ViewFilterOperand.VectorSearch; if (isVectorSearchFilter && isDefined(filterDropdownId)) { - return ( - - ); + return ; } if (!isDefined(fieldMetadataItemUsedInDropdown)) { @@ -82,24 +75,21 @@ export const ObjectFilterDropdownFilterInput = ({ if (isOnlyOperand) { return ( - - + <> - + ); } else if (isDateFilter) { return ( - - + <> - + ); } else { return ( - - + <> {TEXT_FILTER_TYPES.includes(filterType) && ( @@ -130,7 +120,7 @@ export const ObjectFilterDropdownFilterInput = ({ )} {filterType === 'BOOLEAN' && } - + ); } }; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInputHeader.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInputHeader.tsx index 0d9aa2edd..44e52b08e 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInputHeader.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInputHeader.tsx @@ -1,16 +1,56 @@ -import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader'; - import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector'; +import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState'; +import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader'; +import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent'; +import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponentInstanceContext'; +import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { ViewBarFilterDropdownFilterInputMenuHeader } from '@/views/components/ViewBarFilterDropdownFilterInputMenuHeader'; +import { VIEW_BAR_FILTER_DROPDOWN_ID } from '@/views/constants/ViewBarFilterDropdownId'; +import { useLingui } from '@lingui/react/macro'; +import { useContext } from 'react'; +import { ViewFilterOperand } from 'twenty-shared/types'; +import { IconX } from 'twenty-ui/display'; export const ObjectFilterDropdownFilterInputHeader = () => { + const { t } = useLingui(); + const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2( fieldMetadataItemUsedInDropdownComponentSelector, ); - return ( - - {fieldMetadataItemUsedInDropdown?.label} - + const selectedOperandInDropdown = useRecoilComponentValueV2( + selectedOperandInDropdownComponentState, ); + + const { closeDropdown } = useCloseDropdown(); + + const dropdownInstanceId = useContext( + DropdownComponentInstanceContext, + )?.instanceId; + + const isInViewBarFilterDropdown = + dropdownInstanceId === VIEW_BAR_FILTER_DROPDOWN_ID; + + const isVectorSearchFilter = + selectedOperandInDropdown === ViewFilterOperand.VectorSearch; + + if (isInViewBarFilterDropdown) { + return ; + } else { + return ( + closeDropdown(dropdownInstanceId)} + Icon={IconX} + /> + } + > + {isVectorSearchFilter + ? t`Search` + : fieldMetadataItemUsedInDropdown?.label} + + ); + } }; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownInnerSelectOperandDropdown.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownInnerSelectOperandDropdown.tsx index bb126ae2e..ae9984749 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownInnerSelectOperandDropdown.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownInnerSelectOperandDropdown.tsx @@ -1,4 +1,6 @@ import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; +import { DATE_FILTER_TYPES } from '@/object-record/object-filter-dropdown/constants/DateFilterTypes'; +import { DATE_PICKER_DROPDOWN_CONTENT_WIDTH } from '@/object-record/object-filter-dropdown/constants/DatePickerDropdownContentWidth'; import { useApplyObjectFilterDropdownOperand } from '@/object-record/object-filter-dropdown/hooks/useApplyObjectFilterDropdownOperand'; import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector'; import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState'; @@ -7,6 +9,7 @@ import { getOperandLabel } from '@/object-record/object-filter-dropdown/utils/ge import { RecordFilterOperand } from '@/object-record/record-filter/types/RecordFilterOperand'; import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands'; import { DropdownMenuInnerSelect } from '@/ui/layout/dropdown/components/DropdownMenuInnerSelect'; +import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { isDefined } from 'twenty-shared/utils'; import { SelectOption } from 'twenty-ui/input'; @@ -54,16 +57,30 @@ export const ObjectFilterDropdownInnerSelectOperandDropdown = () => { ); }; - if (!isDefined(selectedOperandInDropdown)) { + if ( + !isDefined(selectedOperandInDropdown) || + !isDefined(fieldMetadataItemUsedInDropdown) + ) { return null; } + const filterType = getFilterTypeFromFieldType( + fieldMetadataItemUsedInDropdown.type, + ); + + const isDateFilter = DATE_FILTER_TYPES.includes(filterType); + + const widthInPixels = isDateFilter + ? DATE_PICKER_DROPDOWN_CONTENT_WIDTH + : GenericDropdownContentWidth.ExtraLarge; + return ( ); }; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownVectorSearchInput.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownVectorSearchInput.tsx new file mode 100644 index 000000000..4f80772da --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownVectorSearchInput.tsx @@ -0,0 +1,38 @@ +import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput'; +import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2'; +import { useVectorSearchFilterActions } from '@/views/hooks/useVectorSearchFilterActions'; +import { vectorSearchInputComponentState } from '@/views/states/vectorSearchInputComponentState'; +import { useLingui } from '@lingui/react/macro'; +import { useDebouncedCallback } from 'use-debounce'; + +export const ObjectFilterDropdownVectorSearchInput = () => { + const { t } = useLingui(); + + const [vectorSearchInputValue, setVectorSearchInputValue] = + useRecoilComponentStateV2(vectorSearchInputComponentState); + + const { applyVectorSearchFilter } = useVectorSearchFilterActions(); + + const debouncedApplyVectorSearchFilter = useDebouncedCallback( + (value: string) => { + applyVectorSearchFilter(value); + }, + 500, + ); + + const handleSearchChange = (e: React.ChangeEvent) => { + const inputValue = e.target.value; + setVectorSearchInputValue(inputValue); + debouncedApplyVectorSearchFilter(inputValue); + }; + + return ( + + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx b/packages/twenty-front/src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx index 10f388514..4f5573d21 100644 --- a/packages/twenty-front/src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx +++ b/packages/twenty-front/src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx @@ -1,5 +1,3 @@ -import styled from '@emotion/styled'; - import { availableFieldMetadataItemsForSortFamilySelector } from '@/object-metadata/states/availableFieldMetadataItemsForSortFamilySelector'; import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { OBJECT_SORT_DROPDOWN_ID } from '@/object-record/object-sort-dropdown/constants/ObjectSortDropdownId'; @@ -20,7 +18,10 @@ import { visibleTableColumnsComponentSelector } from '@/object-record/record-tab import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader'; +import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent'; +import { DropdownMenuInnerSelect } from '@/ui/layout/dropdown/components/DropdownMenuInnerSelect'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; +import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput'; import { DropdownMenuSectionLabel } from '@/ui/layout/dropdown/components/DropdownMenuSectionLabel'; import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/StyledHeaderDropdownButton'; @@ -32,54 +33,12 @@ import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; -import { useTheme } from '@emotion/react'; import { Trans, useLingui } from '@lingui/react/macro'; import { useRecoilValue } from 'recoil'; -import { IconChevronDown, useIcons } from 'twenty-ui/display'; +import { IconX, useIcons } from 'twenty-ui/display'; import { MenuItem } from 'twenty-ui/navigation'; import { v4 } from 'uuid'; -export const StyledInput = styled.input` - background: transparent; - border: none; - border-top: none; - border-bottom: 1px solid ${({ theme }) => theme.border.color.light}; - border-radius: 0; - color: ${({ theme }) => theme.font.color.primary}; - margin: 0; - outline: none; - padding: ${({ theme }) => theme.spacing(2)}; - height: 19px; - font-family: inherit; - font-size: ${({ theme }) => theme.font.size.sm}; - - font-weight: inherit; - max-width: 100%; - overflow: hidden; - text-decoration: none; - - &::placeholder { - color: ${({ theme }) => theme.font.color.light}; - } -`; - -const StyledSelectedSortDirectionContainer = styled.div` - background: ${({ theme }) => theme.background.secondary}; - box-shadow: ${({ theme }) => theme.boxShadow.light}; - border-radius: ${({ theme }) => theme.border.radius.md}; - - position: absolute; - top: 32px; - width: 100%; - z-index: 1000; -`; - -const StyledDropdownMenuHeaderEndComponent = styled.div` - padding: ${({ theme }) => theme.spacing(1)}; - display: flex; - align-items: center; -`; - export const ObjectSortDropdownButton = () => { const { resetRecordSortDropdownSearchInput } = useResetRecordSortDropdownSearchInput(); @@ -88,10 +47,6 @@ export const ObjectSortDropdownButton = () => { objectSortDropdownSearchInputComponentState, ); - const isRecordSortDirectionMenuUnfolded = useRecoilComponentValueV2( - isRecordSortDirectionDropdownMenuUnfoldedComponentState, - ); - const { resetSortDropdown } = useResetSortDropdown(); const { recordIndexId, objectMetadataItem } = useRecordIndexContextOrThrow(); @@ -195,8 +150,6 @@ export const ObjectSortDropdownButton = () => { const { t } = useLingui(); - const theme = useTheme(); - const selectableItemIdArray = [ ...visibleFieldMetadataItems.map((item) => item.id), ...hiddenFieldMetadataItems.map((item) => item.id), @@ -227,51 +180,50 @@ export const ObjectSortDropdownButton = () => { } dropdownComponents={ + closeSortDropdown()} + Icon={IconX} + /> + } + > + {t`Sort`} + + ({ + value: sortDirection, + label: sortDirection === 'asc' ? t`Ascending` : t`Descending`, + }))} + selectedOption={{ + value: selectedRecordSortDirection, + label: + selectedRecordSortDirection === 'asc' + ? t`Ascending` + : t`Descending`, + }} + onChange={(sortDirection) => + handleSortDirectionClick( + sortDirection.value as RecordSortDirection, + ) + } + widthInPixels={GenericDropdownContentWidth.ExtraLarge} + /> + + + setObjectSortDropdownSearchInput(event.target.value) + } + /> - {isRecordSortDirectionMenuUnfolded && ( - - - {RECORD_SORT_DIRECTIONS.map((sortDirection, index) => ( - handleSortDirectionClick(sortDirection)} - text={ - sortDirection === 'asc' ? t`Ascending` : t`Descending` - } - /> - ))} - - - )} - - setIsRecordSortDirectionMenuUnfolded( - !isRecordSortDirectionMenuUnfolded, - ) - } - EndComponent={ - - - - } - > - {selectedRecordSortDirection === 'asc' - ? t`Ascending` - : t`Descending`} - - - setObjectSortDropdownSearchInput(event.target.value) - } - /> {shouldShowVisibleFields && ( <> diff --git a/packages/twenty-front/src/modules/ui/layout/dropdown/components/DropdownMenuInnerSelect.tsx b/packages/twenty-front/src/modules/ui/layout/dropdown/components/DropdownMenuInnerSelect.tsx index ff708c87d..0205c1ab8 100644 --- a/packages/twenty-front/src/modules/ui/layout/dropdown/components/DropdownMenuInnerSelect.tsx +++ b/packages/twenty-front/src/modules/ui/layout/dropdown/components/DropdownMenuInnerSelect.tsx @@ -33,6 +33,7 @@ export type DropdownMenuInnerSelectProps = { onChange: (value: SelectOption) => void; options: SelectOption[]; dropdownId: string; + widthInPixels?: number; }; export const DropdownMenuInnerSelect = ({ @@ -40,6 +41,7 @@ export const DropdownMenuInnerSelect = ({ onChange, options, dropdownId, + widthInPixels, }: DropdownMenuInnerSelectProps) => { const theme = useTheme(); @@ -54,7 +56,7 @@ export const DropdownMenuInnerSelect = ({ } dropdownComponents={ - + {options.map((selectOption) => ( { + return ( + + + + + ); +}; diff --git a/packages/twenty-front/src/modules/views/components/EditableFilterChipDropdownMenuHeader.tsx b/packages/twenty-front/src/modules/views/components/EditableFilterChipDropdownMenuHeader.tsx new file mode 100644 index 000000000..384f9c2c5 --- /dev/null +++ b/packages/twenty-front/src/modules/views/components/EditableFilterChipDropdownMenuHeader.tsx @@ -0,0 +1,45 @@ +import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector'; +import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState'; +import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader'; +import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent'; +import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { useLingui } from '@lingui/react/macro'; +import { ViewFilterOperand } from 'twenty-shared/types'; +import { IconX } from 'twenty-ui/display'; + +export const EditableFilterChipDropdownMenuHeader = () => { + const { t } = useLingui(); + + const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2( + fieldMetadataItemUsedInDropdownComponentSelector, + ); + + const selectedOperandInDropdown = useRecoilComponentValueV2( + selectedOperandInDropdownComponentState, + ); + + const isVectorSearchFilter = + selectedOperandInDropdown === ViewFilterOperand.VectorSearch; + + const { closeDropdown } = useCloseDropdown(); + + const handleBackButtonClick = () => { + closeDropdown(); + }; + + return ( + + } + > + {isVectorSearchFilter + ? t`Search` + : fieldMetadataItemUsedInDropdown?.label} + + ); +}; diff --git a/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx b/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx index 4f2279091..34d820bd9 100644 --- a/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx +++ b/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx @@ -4,10 +4,11 @@ import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { EditableFilterChip } from '@/views/components/EditableFilterChip'; -import { ObjectFilterDropdownFilterInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput'; import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRemoveRecordFilter'; import { isRecordFilterConsideredEmpty } from '@/object-record/record-filter/utils/isRecordFilterConsideredEmpty'; import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown'; +import { EditableFilterChipDropdownContent } from '@/views/components/EditableFilterChipDropdownContent'; +import { useClearVectorSearchInput } from '@/views/hooks/useClearVectorSearchInput'; import { useSetEditableFilterChipDropdownStates } from '@/views/hooks/useSetEditableFilterChipDropdownStates'; type EditableFilterDropdownButtonProps = { @@ -29,13 +30,17 @@ export const EditableFilterDropdownButton = ({ removeRecordFilter({ recordFilterId: recordFilter.id }); }; + const { clearVectorSearchInput } = useClearVectorSearchInput(); + const onFilterDropdownClose = useCallback(() => { const recordFilterIsEmpty = isRecordFilterConsideredEmpty(recordFilter); if (recordFilterIsEmpty) { removeRecordFilter({ recordFilterId: recordFilter.id }); } - }, [recordFilter, removeRecordFilter]); + + clearVectorSearchInput(); + }, [recordFilter, removeRecordFilter, clearVectorSearchInput]); const { setEditableFilterChipDropdownStates } = useSetEditableFilterChipDropdownStates(); @@ -56,7 +61,7 @@ export const EditableFilterDropdownButton = ({ /> } dropdownComponents={ - + } dropdownOffset={{ y: 8, x: 0 }} dropdownPlacement="bottom-start" diff --git a/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdown.tsx b/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdown.tsx index 8f3101729..37512bddf 100644 --- a/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdown.tsx +++ b/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdown.tsx @@ -8,6 +8,7 @@ import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRe import { isRecordFilterConsideredEmpty } from '@/object-record/record-filter/utils/isRecordFilterConsideredEmpty'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { ViewBarFilterDropdownContent } from '@/views/components/ViewBarFilterDropdownContent'; +import { useClearVectorSearchInput } from '@/views/hooks/useClearVectorSearchInput'; import { isDefined } from 'twenty-shared/utils'; import { ViewBarFilterButton } from './ViewBarFilterButton'; @@ -20,6 +21,8 @@ export const ViewBarFilterDropdown = () => { objectFilterDropdownCurrentRecordFilterComponentState, ); + const { clearVectorSearchInput } = useClearVectorSearchInput(); + const handleDropdownClickOutside = () => { const recordFilterIsEmpty = isDefined(objectFilterDropdownCurrentRecordFilter) && @@ -37,6 +40,7 @@ export const ViewBarFilterDropdown = () => { const handleDropdownClose = () => { resetFilterDropdown(); removeEmptyVectorSearchFilter(); + clearVectorSearchInput(); }; const handleDropdownOpen = () => { diff --git a/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownContent.tsx b/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownContent.tsx index 32dbf9003..a2a054d47 100644 --- a/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownContent.tsx +++ b/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownContent.tsx @@ -1,8 +1,12 @@ -import { ObjectFilterDropdownFilterInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput'; import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState'; +import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState'; import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { ViewBarFilterDropdownFieldSelectMenu } from '@/views/components/ViewBarFilterDropdownFieldSelectMenu'; +import { ViewBarFilterDropdownFilterInput } from '@/views/components/ViewBarFilterDropdownFilterInput'; +import { ViewBarFilterDropdownVectorSearchInput } from '@/views/components/ViewBarFilterDropdownVectorSearchInput'; import { VIEW_BAR_FILTER_DROPDOWN_ID } from '@/views/constants/ViewBarFilterDropdownId'; +import { ViewFilterOperand } from 'twenty-shared/types'; export const ViewBarFilterDropdownContent = () => { const [objectFilterDropdownFilterIsSelected] = useRecoilComponentStateV2( @@ -10,14 +14,23 @@ export const ViewBarFilterDropdownContent = () => { VIEW_BAR_FILTER_DROPDOWN_ID, ); + const selectedOperandInDropdown = useRecoilComponentValueV2( + selectedOperandInDropdownComponentState, + ); + + const isVectorSearchFilter = + selectedOperandInDropdown === ViewFilterOperand.VectorSearch; + + if (isVectorSearchFilter) { + return ; + } + const shouldShowFilterInput = objectFilterDropdownFilterIsSelected; return ( <> {shouldShowFilterInput ? ( - + ) : ( )} diff --git a/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownFieldSelectMenu.tsx b/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownFieldSelectMenu.tsx index 9c82c9b95..6a54b03b6 100644 --- a/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownFieldSelectMenu.tsx +++ b/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownFieldSelectMenu.tsx @@ -16,10 +16,14 @@ import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/ import { ViewBarFilterDropdownBottomMenu } from '@/views/components/ViewBarFilterDropdownBottomMenu'; import { ViewBarFilterDropdownFieldSelectMenuItem } from '@/views/components/ViewBarFilterDropdownFieldSelectMenuItem'; +import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader'; +import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent'; import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth'; +import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown'; import { VIEW_BAR_FILTER_BOTTOM_MENU_ITEM_IDS } from '@/views/constants/ViewBarFilterBottomMenuItemIds'; import { VIEW_BAR_FILTER_DROPDOWN_ID } from '@/views/constants/ViewBarFilterDropdownId'; import { useLingui } from '@lingui/react/macro'; +import { IconX } from 'twenty-ui/display'; export const StyledInput = styled.input` background: transparent; @@ -56,6 +60,8 @@ export const ViewBarFilterDropdownFieldSelectMenu = () => { selectableVisibleFieldMetadataItems, } = useFilterDropdownSelectableFieldMetadataItems(); + const { closeDropdown } = useCloseDropdown(); + const selectableFieldMetadataItemIds = [ ...selectableVisibleFieldMetadataItems.map( (fieldMetadataItem) => fieldMetadataItem.id, @@ -83,6 +89,16 @@ export const ViewBarFilterDropdownFieldSelectMenu = () => { return ( + closeDropdown()} + Icon={IconX} + /> + } + > + {t`Filter`} + { + return ( + + + + + ); +}; diff --git a/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownFilterInputMenuHeader.tsx b/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownFilterInputMenuHeader.tsx new file mode 100644 index 000000000..0c4a179be --- /dev/null +++ b/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownFilterInputMenuHeader.tsx @@ -0,0 +1,49 @@ +import { useResetFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useResetFilterDropdown'; +import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector'; +import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState'; +import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader'; +import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { useClearVectorSearchInput } from '@/views/hooks/useClearVectorSearchInput'; +import { useLingui } from '@lingui/react/macro'; +import { ViewFilterOperand } from 'twenty-shared/types'; +import { IconChevronLeft } from 'twenty-ui/display'; + +export const ViewBarFilterDropdownFilterInputMenuHeader = () => { + const { t } = useLingui(); + + const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2( + fieldMetadataItemUsedInDropdownComponentSelector, + ); + + const selectedOperandInDropdown = useRecoilComponentValueV2( + selectedOperandInDropdownComponentState, + ); + + const isVectorSearchFilter = + selectedOperandInDropdown === ViewFilterOperand.VectorSearch; + + const { clearVectorSearchInput } = useClearVectorSearchInput(); + + const { resetFilterDropdown } = useResetFilterDropdown(); + + const handleBackButtonClick = () => { + resetFilterDropdown(); + clearVectorSearchInput(); + }; + + return ( + + } + > + {isVectorSearchFilter + ? t`Search` + : fieldMetadataItemUsedInDropdown?.label} + + ); +}; diff --git a/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownVectorSearchButton.tsx b/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownVectorSearchButton.tsx index a9c4d826e..fa8dd5336 100644 --- a/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownVectorSearchButton.tsx +++ b/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownVectorSearchButton.tsx @@ -9,7 +9,6 @@ import { IconSearch } from 'twenty-ui/display'; import { MenuItem } from 'twenty-ui/navigation'; import { VIEW_BAR_FILTER_BOTTOM_MENU_ITEM_IDS } from '@/views/constants/ViewBarFilterBottomMenuItemIds'; -import { VIEW_BAR_FILTER_DROPDOWN_ID } from '@/views/constants/ViewBarFilterDropdownId'; import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState'; import { useOpenVectorSearchFilter } from '@/views/hooks/useOpenVectorSearchFilter'; @@ -26,20 +25,16 @@ export const ViewBarFilterDropdownVectorSearchButton = () => { const { t } = useLingui(); const [, setVectorSearchInputValue] = useRecoilComponentStateV2( vectorSearchInputComponentState, - VIEW_BAR_FILTER_DROPDOWN_ID, ); const { setVectorSearchInputValueFromExistingFilter } = - useSetVectorSearchInputValueFromExistingFilter(VIEW_BAR_FILTER_DROPDOWN_ID); + useSetVectorSearchInputValueFromExistingFilter(); - const fieldSearchInputValue = useRecoilComponentValueV2( + const objectFilterDropdownSearchInput = useRecoilComponentValueV2( objectFilterDropdownSearchInputComponentState, - VIEW_BAR_FILTER_DROPDOWN_ID, ); const { applyVectorSearchFilter } = useVectorSearchFilterActions(); - const { openVectorSearchFilter } = useOpenVectorSearchFilter( - VIEW_BAR_FILTER_DROPDOWN_ID, - ); + const { openVectorSearchFilter } = useOpenVectorSearchFilter(); const isSelected = useRecoilComponentFamilyValueV2( isSelectedItemIdComponentFamilySelector, @@ -49,9 +44,9 @@ export const ViewBarFilterDropdownVectorSearchButton = () => { const handleSearchClick = () => { openVectorSearchFilter(); - if (fieldSearchInputValue.length > 0) { - setVectorSearchInputValue(fieldSearchInputValue); - applyVectorSearchFilter(fieldSearchInputValue); + if (objectFilterDropdownSearchInput.length > 0) { + setVectorSearchInputValue(objectFilterDropdownSearchInput); + applyVectorSearchFilter(objectFilterDropdownSearchInput); } else { setVectorSearchInputValueFromExistingFilter(); } @@ -69,8 +64,8 @@ export const ViewBarFilterDropdownVectorSearchButton = () => { text={ <> {t`Search`} - {fieldSearchInputValue && ( - {t`· ${fieldSearchInputValue}`} + {objectFilterDropdownSearchInput && ( + {t`· ${objectFilterDropdownSearchInput}`} )} } diff --git a/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownVectorSearchInput.tsx b/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownVectorSearchInput.tsx index 2a80c0a3e..f41aae84d 100644 --- a/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownVectorSearchInput.tsx +++ b/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownVectorSearchInput.tsx @@ -1,47 +1,13 @@ +import { ObjectFilterDropdownVectorSearchInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownVectorSearchInput'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; -import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput'; import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth'; -import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2'; -import { useVectorSearchFilterActions } from '@/views/hooks/useVectorSearchFilterActions'; -import { vectorSearchInputComponentState } from '@/views/states/vectorSearchInputComponentState'; -import { useLingui } from '@lingui/react/macro'; -import { useDebouncedCallback } from 'use-debounce'; - -export const ViewBarFilterDropdownVectorSearchInput = ({ - filterDropdownId, -}: { - filterDropdownId: string; -}) => { - const { t } = useLingui(); - const [vectorSearchInputValue, setVectorSearchInputValue] = - useRecoilComponentStateV2( - vectorSearchInputComponentState, - filterDropdownId, - ); - const { applyVectorSearchFilter } = useVectorSearchFilterActions(); - - const debouncedApplyVectorSearchFilter = useDebouncedCallback( - (value: string) => { - applyVectorSearchFilter(value); - }, - 500, - ); - - const handleSearchChange = (e: React.ChangeEvent) => { - const inputValue = e.target.value; - setVectorSearchInputValue(inputValue); - debouncedApplyVectorSearchFilter(inputValue); - }; +import { ViewBarFilterDropdownFilterInputMenuHeader } from '@/views/components/ViewBarFilterDropdownFilterInputMenuHeader'; +export const ViewBarFilterDropdownVectorSearchInput = () => { return ( - + + ); }; diff --git a/packages/twenty-front/src/modules/views/hooks/useClearVectorSearchInput.ts b/packages/twenty-front/src/modules/views/hooks/useClearVectorSearchInput.ts new file mode 100644 index 000000000..a1c4a66f4 --- /dev/null +++ b/packages/twenty-front/src/modules/views/hooks/useClearVectorSearchInput.ts @@ -0,0 +1,16 @@ +import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; +import { vectorSearchInputComponentState } from '@/views/states/vectorSearchInputComponentState'; + +export const useClearVectorSearchInput = () => { + const setVectorSearchInputValue = useSetRecoilComponentStateV2( + vectorSearchInputComponentState, + ); + + const clearVectorSearchInput = () => { + setVectorSearchInputValue(''); + }; + + return { + clearVectorSearchInput, + }; +}; diff --git a/packages/twenty-front/src/modules/views/hooks/useSetEditableFilterChipDropdownStates.ts b/packages/twenty-front/src/modules/views/hooks/useSetEditableFilterChipDropdownStates.ts index 6dd452c4b..86b5de3bb 100644 --- a/packages/twenty-front/src/modules/views/hooks/useSetEditableFilterChipDropdownStates.ts +++ b/packages/twenty-front/src/modules/views/hooks/useSetEditableFilterChipDropdownStates.ts @@ -24,15 +24,6 @@ export const useSetEditableFilterChipDropdownStates = () => { ? filterableFieldMetadataItems.concat(vectorSearchField) : filterableFieldMetadataItems; - const fieldMetadataItem = filterableFieldsWithVector.find( - (fieldMetadataItem) => - fieldMetadataItem.id === recordFilter.fieldMetadataId, - ); - - if (!isDefined(fieldMetadataItem)) { - return; - } - if (isVectorSearchFilter(recordFilter)) { set( vectorSearchInputComponentState.atomFamily({ @@ -42,13 +33,20 @@ export const useSetEditableFilterChipDropdownStates = () => { ); } - set( - fieldMetadataItemIdUsedInDropdownComponentState.atomFamily({ - instanceId: recordFilter.id, - }), - fieldMetadataItem.id, + const fieldMetadataItem = filterableFieldsWithVector.find( + (fieldMetadataItem) => + fieldMetadataItem.id === recordFilter.fieldMetadataId, ); + if (isDefined(fieldMetadataItem)) { + set( + fieldMetadataItemIdUsedInDropdownComponentState.atomFamily({ + instanceId: recordFilter.id, + }), + fieldMetadataItem.id, + ); + } + set( selectedOperandInDropdownComponentState.atomFamily({ instanceId: recordFilter.id, diff --git a/packages/twenty-front/src/modules/views/hooks/useSetVectorSearchInputValueFromExistingFilter.ts b/packages/twenty-front/src/modules/views/hooks/useSetVectorSearchInputValueFromExistingFilter.ts index 3b01c0414..e1c985e48 100644 --- a/packages/twenty-front/src/modules/views/hooks/useSetVectorSearchInputValueFromExistingFilter.ts +++ b/packages/twenty-front/src/modules/views/hooks/useSetVectorSearchInputValueFromExistingFilter.ts @@ -3,17 +3,15 @@ import { vectorSearchInputComponentState } from '@/views/states/vectorSearchInpu import { isDefined } from 'twenty-shared/utils'; import { useVectorSearchFilterState } from './useVectorSearchFilterState'; -export const useSetVectorSearchInputValueFromExistingFilter = ( - filterDropdownId: string, -) => { +export const useSetVectorSearchInputValueFromExistingFilter = () => { const [, setVectorSearchInputValue] = useRecoilComponentStateV2( vectorSearchInputComponentState, - filterDropdownId, ); const { getExistingVectorSearchFilter } = useVectorSearchFilterState(); const setVectorSearchInputValueFromExistingFilter = () => { const existingVectorSearchFilter = getExistingVectorSearchFilter(); + if (isDefined(existingVectorSearchFilter)) { setVectorSearchInputValue(existingVectorSearchFilter.value); } diff --git a/packages/twenty-front/src/modules/views/states/vectorSearchInputComponentState.ts b/packages/twenty-front/src/modules/views/states/vectorSearchInputComponentState.ts index 29c83f674..7b9de59bc 100644 --- a/packages/twenty-front/src/modules/views/states/vectorSearchInputComponentState.ts +++ b/packages/twenty-front/src/modules/views/states/vectorSearchInputComponentState.ts @@ -1,8 +1,8 @@ +import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext'; import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; -import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext'; export const vectorSearchInputComponentState = createComponentStateV2({ key: 'vectorSearchInputComponentState', defaultValue: '', - componentInstanceContext: ViewComponentInstanceContext, + componentInstanceContext: ObjectFilterDropdownComponentInstanceContext, });