Add search any field front logic with its feature flag (#13278)
This PR adds the frontend logic to handle the user input of a search any field value. It also adds the associated feature flag, that can be modified from the admin panel. This PR does not add the filtering part nor the saving on view logic, which will come in their separate PRs. https://github.com/user-attachments/assets/6a52c090-b957-46aa-bff7-a90b51109789
This commit is contained in:
@ -732,6 +732,7 @@ export type FeatureFlagDto = {
|
||||
export enum FeatureFlagKey {
|
||||
IS_AIRTABLE_INTEGRATION_ENABLED = 'IS_AIRTABLE_INTEGRATION_ENABLED',
|
||||
IS_AI_ENABLED = 'IS_AI_ENABLED',
|
||||
IS_ANY_FIELD_SEARCH_ENABLED = 'IS_ANY_FIELD_SEARCH_ENABLED',
|
||||
IS_FIELDS_PERMISSIONS_ENABLED = 'IS_FIELDS_PERMISSIONS_ENABLED',
|
||||
IS_IMAP_SMTP_CALDAV_ENABLED = 'IS_IMAP_SMTP_CALDAV_ENABLED',
|
||||
IS_JSON_FILTER_ENABLED = 'IS_JSON_FILTER_ENABLED',
|
||||
|
||||
@ -696,6 +696,7 @@ export type FeatureFlagDto = {
|
||||
export enum FeatureFlagKey {
|
||||
IS_AIRTABLE_INTEGRATION_ENABLED = 'IS_AIRTABLE_INTEGRATION_ENABLED',
|
||||
IS_AI_ENABLED = 'IS_AI_ENABLED',
|
||||
IS_ANY_FIELD_SEARCH_ENABLED = 'IS_ANY_FIELD_SEARCH_ENABLED',
|
||||
IS_FIELDS_PERMISSIONS_ENABLED = 'IS_FIELDS_PERMISSIONS_ENABLED',
|
||||
IS_IMAP_SMTP_CALDAV_ENABLED = 'IS_IMAP_SMTP_CALDAV_ENABLED',
|
||||
IS_JSON_FILTER_ENABLED = 'IS_JSON_FILTER_ENABLED',
|
||||
|
||||
@ -0,0 +1,27 @@
|
||||
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||
import { viewAnyFieldSearchValueComponentState } from '@/views/states/viewAnyFieldSearchValueComponentState';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
|
||||
export const ObjectFilterDropdownAnyFieldSearchInput = () => {
|
||||
const { t } = useLingui();
|
||||
|
||||
const [viewAnyFieldSearchValue, setViewAnyFieldSearchValue] =
|
||||
useRecoilComponentStateV2(viewAnyFieldSearchValueComponentState);
|
||||
|
||||
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const inputValue = event.target.value;
|
||||
|
||||
setViewAnyFieldSearchValue(inputValue);
|
||||
};
|
||||
|
||||
return (
|
||||
<DropdownMenuSearchInput
|
||||
autoFocus
|
||||
type="text"
|
||||
value={viewAnyFieldSearchValue}
|
||||
placeholder={t`Search any field`}
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -42,7 +42,7 @@ export const ObjectFilterDropdownTextInput = () => {
|
||||
<DropdownMenuInput
|
||||
instanceId="object-filter-dropdown-text-input"
|
||||
ref={handleInputRef}
|
||||
value={objectFilterDropdownFilterValue}
|
||||
value={objectFilterDropdownFilterValue ?? ''}
|
||||
autoFocus
|
||||
type="text"
|
||||
placeholder={fieldMetadataItemUsedInDropdown?.label}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState';
|
||||
import { objectFilterDropdownAnyFieldSearchIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownAnyFieldSearchIsSelectedComponentState';
|
||||
import { objectFilterDropdownCurrentRecordFilterComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownCurrentRecordFilterComponentState';
|
||||
import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState';
|
||||
import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState';
|
||||
@ -32,6 +33,12 @@ export const useResetFilterDropdown = (componentInstanceId?: string) => {
|
||||
componentInstanceId,
|
||||
);
|
||||
|
||||
const objectFilterDropdownAnyFieldSearchIsSelectedCallbackState =
|
||||
useRecoilComponentCallbackStateV2(
|
||||
objectFilterDropdownAnyFieldSearchIsSelectedComponentState,
|
||||
componentInstanceId,
|
||||
);
|
||||
|
||||
const objectFilterDropdownIsSelectingCompositeFieldCallbackState =
|
||||
useRecoilComponentCallbackStateV2(
|
||||
objectFilterDropdownIsSelectingCompositeFieldComponentState,
|
||||
@ -53,6 +60,7 @@ export const useResetFilterDropdown = (componentInstanceId?: string) => {
|
||||
set(objectFilterDropdownIsSelectingCompositeFieldCallbackState, false);
|
||||
set(fieldMetadataItemIdUsedInDropdownCallbackState, null);
|
||||
set(objectFilterDropdownCurrentRecordFilterCallbackState, null);
|
||||
set(objectFilterDropdownAnyFieldSearchIsSelectedCallbackState, false);
|
||||
},
|
||||
[
|
||||
objectFilterDropdownSearchInputCallbackState,
|
||||
@ -61,6 +69,7 @@ export const useResetFilterDropdown = (componentInstanceId?: string) => {
|
||||
objectFilterDropdownIsSelectingCompositeFieldCallbackState,
|
||||
fieldMetadataItemIdUsedInDropdownCallbackState,
|
||||
objectFilterDropdownCurrentRecordFilterCallbackState,
|
||||
objectFilterDropdownAnyFieldSearchIsSelectedCallbackState,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext';
|
||||
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||
|
||||
export const objectFilterDropdownAnyFieldSearchIsSelectedComponentState =
|
||||
createComponentStateV2<boolean>({
|
||||
key: 'objectFilterDropdownAnyFieldSearchIsSelectedComponentState',
|
||||
defaultValue: false,
|
||||
componentInstanceContext: ObjectFilterDropdownComponentInstanceContext,
|
||||
});
|
||||
@ -0,0 +1,29 @@
|
||||
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||
import { SortOrFilterChip } from '@/views/components/SortOrFilterChip';
|
||||
import { ADVANCED_FILTER_DROPDOWN_ID } from '@/views/constants/AdvancedFilterDropdownId';
|
||||
import { viewAnyFieldSearchValueComponentState } from '@/views/states/viewAnyFieldSearchValueComponentState';
|
||||
import { IconFilter } from 'twenty-ui/display';
|
||||
|
||||
export const AnyFieldSearchChip = () => {
|
||||
const { closeDropdown } = useCloseDropdown();
|
||||
|
||||
const [viewAnyFieldSearchValue, setViewAnyFieldSearchValue] =
|
||||
useRecoilComponentStateV2(viewAnyFieldSearchValueComponentState);
|
||||
|
||||
const handleRemoveClick = () => {
|
||||
closeDropdown();
|
||||
setViewAnyFieldSearchValue('');
|
||||
};
|
||||
|
||||
return (
|
||||
<SortOrFilterChip
|
||||
testId={ADVANCED_FILTER_DROPDOWN_ID}
|
||||
labelKey={'Any field :'}
|
||||
labelValue={viewAnyFieldSearchValue}
|
||||
Icon={IconFilter}
|
||||
onRemove={handleRemoveClick}
|
||||
type="filter"
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,28 @@
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
|
||||
import { DROPDOWN_OFFSET_Y } from '@/ui/layout/dropdown/constants/DropdownOffsetY';
|
||||
import { useOpenDropdown } from '@/ui/layout/dropdown/hooks/useOpenDropdown';
|
||||
import { AnyFieldSearchChip } from '@/views/components/AnyFieldSearchChip';
|
||||
import { AnyFieldSearchDropdownContent } from '@/views/components/AnyFieldSearchDropdownContent';
|
||||
import { ANY_FIELD_SEARCH_DROPDOWN_ID } from '@/views/constants/AnyFieldSearchDropdownId';
|
||||
|
||||
export const AnyFieldSearchDropdownButton = () => {
|
||||
const { openDropdown } = useOpenDropdown();
|
||||
|
||||
const handleOpenAnyFieldSearchDropdown = () => {
|
||||
openDropdown({
|
||||
dropdownComponentInstanceIdFromProps: ANY_FIELD_SEARCH_DROPDOWN_ID,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
dropdownId={ANY_FIELD_SEARCH_DROPDOWN_ID}
|
||||
clickableComponent={<AnyFieldSearchChip />}
|
||||
dropdownComponents={<AnyFieldSearchDropdownContent />}
|
||||
dropdownOffset={{ y: DROPDOWN_OFFSET_Y, x: 0 }}
|
||||
dropdownPlacement="bottom-start"
|
||||
onOpen={handleOpenAnyFieldSearchDropdown}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,13 @@
|
||||
import { ObjectFilterDropdownAnyFieldSearchInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownAnyFieldSearchInput';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
|
||||
import { AnyFieldSearchDropdownContentMenuHeader } from '@/views/components/AnyFieldSearchDropdownContentMenuHeader';
|
||||
|
||||
export const AnyFieldSearchDropdownContent = () => {
|
||||
return (
|
||||
<DropdownContent widthInPixels={GenericDropdownContentWidth.ExtraLarge}>
|
||||
<AnyFieldSearchDropdownContentMenuHeader />
|
||||
<ObjectFilterDropdownAnyFieldSearchInput />
|
||||
</DropdownContent>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,28 @@
|
||||
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 { useLingui } from '@lingui/react/macro';
|
||||
import { IconX } from 'twenty-ui/display';
|
||||
|
||||
export const AnyFieldSearchDropdownContentMenuHeader = () => {
|
||||
const { t } = useLingui();
|
||||
|
||||
const { closeDropdown } = useCloseDropdown();
|
||||
|
||||
const handleBackButtonClick = () => {
|
||||
closeDropdown();
|
||||
};
|
||||
|
||||
return (
|
||||
<DropdownMenuHeader
|
||||
StartComponent={
|
||||
<DropdownMenuHeaderLeftComponent
|
||||
onClick={handleBackButtonClick}
|
||||
Icon={IconX}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{t`Search any field`}
|
||||
</DropdownMenuHeader>
|
||||
);
|
||||
};
|
||||
@ -22,12 +22,16 @@ import { useAreViewFiltersDifferentFromRecordFilters } from '@/views/hooks/useAr
|
||||
import { useAreViewSortsDifferentFromRecordSorts } from '@/views/hooks/useAreViewSortsDifferentFromRecordSorts';
|
||||
|
||||
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
|
||||
import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
|
||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||
import { AnyFieldSearchDropdownButton } from '@/views/components/AnyFieldSearchDropdownButton';
|
||||
import { ANY_FIELD_SEARCH_DROPDOWN_ID } from '@/views/constants/AnyFieldSearchDropdownId';
|
||||
import { useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups } from '@/views/hooks/useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups';
|
||||
import { useAreViewFilterGroupsDifferentFromRecordFilterGroups } from '@/views/hooks/useAreViewFilterGroupsDifferentFromRecordFilterGroups';
|
||||
import { isViewBarExpandedComponentState } from '@/views/states/isViewBarExpandedComponentState';
|
||||
import { viewAnyFieldSearchValueComponentState } from '@/views/states/viewAnyFieldSearchValueComponentState';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isNonEmptyArray } from '@sniptt/guards';
|
||||
import { isNonEmptyArray, isNonEmptyString } from '@sniptt/guards';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { LightButton } from 'twenty-ui/input';
|
||||
|
||||
@ -119,6 +123,10 @@ export const ViewBarDetails = ({
|
||||
currentRecordSortsComponentState,
|
||||
);
|
||||
|
||||
const viewAnyFieldSearchValue = useRecoilComponentValueV2(
|
||||
viewAnyFieldSearchValueComponentState,
|
||||
);
|
||||
|
||||
const { objectNameSingular } = useObjectNameSingularFromPlural({
|
||||
objectNamePlural: objectNamePlural,
|
||||
});
|
||||
@ -172,7 +180,19 @@ export const ViewBarDetails = ({
|
||||
toggleSoftDeleteFilterState(false);
|
||||
};
|
||||
|
||||
const shouldShowAdvancedFilterDropdownButton =
|
||||
currentRecordFilterGroups.length > 0;
|
||||
|
||||
const isAnyFieldSearchDropdownOpen = useRecoilComponentValueV2(
|
||||
isDropdownOpenComponentState,
|
||||
ANY_FIELD_SEARCH_DROPDOWN_ID,
|
||||
);
|
||||
|
||||
const shouldShowAnyFieldSearchChip =
|
||||
isNonEmptyString(viewAnyFieldSearchValue) || isAnyFieldSearchDropdownOpen;
|
||||
|
||||
const shouldExpandViewBar =
|
||||
shouldShowAnyFieldSearchChip ||
|
||||
viewFiltersAreDifferentFromRecordFilters ||
|
||||
viewSortsAreDifferentFromRecordSorts ||
|
||||
viewFilterGroupsAreDifferentFromRecordFilterGroups ||
|
||||
@ -185,9 +205,6 @@ export const ViewBarDetails = ({
|
||||
return null;
|
||||
}
|
||||
|
||||
const shouldShowAdvancedFilterDropdownButton =
|
||||
currentRecordFilterGroups.length > 0;
|
||||
|
||||
return (
|
||||
<StyledBar>
|
||||
<StyledFilterContainer>
|
||||
@ -220,6 +237,7 @@ export const ViewBarDetails = ({
|
||||
<StyledSeperator />
|
||||
</StyledSeperatorContainer>
|
||||
)}
|
||||
{shouldShowAnyFieldSearchChip && <AnyFieldSearchDropdownButton />}
|
||||
{shouldShowAdvancedFilterDropdownButton && (
|
||||
<AdvancedFilterDropdownButton />
|
||||
)}
|
||||
|
||||
@ -0,0 +1,17 @@
|
||||
import { ViewBarFilterDropdownAnyFieldSearchButtonMenuItem } from '@/views/components/ViewBarFilterDropdownAnyFieldSearchButtonMenuItem';
|
||||
import { useOpenAnyFieldSearchFilterFromViewBar } from '@/views/hooks/useOpenAnyFieldSearchFilterFromViewBar';
|
||||
|
||||
export const ViewBarFilterDropdownAnyFieldSearchButton = () => {
|
||||
const { openAnyFieldSearchFilterFromViewBar } =
|
||||
useOpenAnyFieldSearchFilterFromViewBar();
|
||||
|
||||
const handleSearchClick = () => {
|
||||
openAnyFieldSearchFilterFromViewBar();
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewBarFilterDropdownAnyFieldSearchButtonMenuItem
|
||||
onClick={handleSearchClick}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,57 @@
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { isSelectedItemIdComponentFamilySelector } from '@/ui/layout/selectable-list/states/selectors/isSelectedItemIdComponentFamilySelector';
|
||||
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import styled from '@emotion/styled';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
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 { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState';
|
||||
|
||||
const StyledSearchText = styled.span`
|
||||
color: ${({ theme }) => theme.font.color.light};
|
||||
margin-left: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
type ViewBarFilterDropdownAnyFieldSearchButtonMenuItemProps = {
|
||||
onClick?: () => void;
|
||||
};
|
||||
|
||||
export const ViewBarFilterDropdownAnyFieldSearchButtonMenuItem = ({
|
||||
onClick,
|
||||
}: ViewBarFilterDropdownAnyFieldSearchButtonMenuItemProps) => {
|
||||
const { t } = useLingui();
|
||||
|
||||
const objectFilterDropdownSearchInput = useRecoilComponentValueV2(
|
||||
objectFilterDropdownSearchInputComponentState,
|
||||
);
|
||||
|
||||
const isSelected = useRecoilComponentFamilyValueV2(
|
||||
isSelectedItemIdComponentFamilySelector,
|
||||
VIEW_BAR_FILTER_BOTTOM_MENU_ITEM_IDS.SEARCH,
|
||||
);
|
||||
|
||||
return (
|
||||
<SelectableListItem
|
||||
itemId={VIEW_BAR_FILTER_BOTTOM_MENU_ITEM_IDS.SEARCH}
|
||||
onEnter={onClick}
|
||||
>
|
||||
<MenuItem
|
||||
focused={isSelected}
|
||||
onClick={onClick}
|
||||
LeftIcon={IconSearch}
|
||||
text={
|
||||
<>
|
||||
{t`Search any field`}
|
||||
{objectFilterDropdownSearchInput && (
|
||||
<StyledSearchText>{t`· ${objectFilterDropdownSearchInput}`}</StyledSearchText>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,13 @@
|
||||
import { ObjectFilterDropdownAnyFieldSearchInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownAnyFieldSearchInput';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
|
||||
import { ViewBarFilterDropdownAnyFieldSearchInputDropdownHeader } from '@/views/components/ViewBarFilterDropdownAnyFieldSearchInputDropdownHeader';
|
||||
|
||||
export const ViewBarFilterDropdownAnyFieldSearchInput = () => {
|
||||
return (
|
||||
<DropdownContent widthInPixels={GenericDropdownContentWidth.ExtraLarge}>
|
||||
<ViewBarFilterDropdownAnyFieldSearchInputDropdownHeader />
|
||||
<ObjectFilterDropdownAnyFieldSearchInput />
|
||||
</DropdownContent>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,28 @@
|
||||
import { useResetFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useResetFilterDropdown';
|
||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
|
||||
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { IconChevronLeft } from 'twenty-ui/display';
|
||||
|
||||
export const ViewBarFilterDropdownAnyFieldSearchInputDropdownHeader = () => {
|
||||
const { t } = useLingui();
|
||||
|
||||
const { resetFilterDropdown } = useResetFilterDropdown();
|
||||
|
||||
const handleBackButtonClick = () => {
|
||||
resetFilterDropdown();
|
||||
};
|
||||
|
||||
return (
|
||||
<DropdownMenuHeader
|
||||
StartComponent={
|
||||
<DropdownMenuHeaderLeftComponent
|
||||
onClick={handleBackButtonClick}
|
||||
Icon={IconChevronLeft}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{t`Search any field`}
|
||||
</DropdownMenuHeader>
|
||||
);
|
||||
};
|
||||
@ -1,6 +1,9 @@
|
||||
import { ViewBarFilterDropdownAdvancedFilterButton } from '@/views/components/ViewBarFilterDropdownAdvancedFilterButton';
|
||||
import { ViewBarFilterDropdownAnyFieldSearchButton } from '@/views/components/ViewBarFilterDropdownAnyFieldSearchButton';
|
||||
import { ViewBarFilterDropdownVectorSearchButton } from '@/views/components/ViewBarFilterDropdownVectorSearchButton';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import styled from '@emotion/styled';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
@ -11,9 +14,17 @@ const StyledContainer = styled.div`
|
||||
`;
|
||||
|
||||
export const ViewBarFilterDropdownBottomMenu = () => {
|
||||
const isAnyFieldSearchEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_ANY_FIELD_SEARCH_ENABLED,
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<ViewBarFilterDropdownVectorSearchButton />
|
||||
{isAnyFieldSearchEnabled ? (
|
||||
<ViewBarFilterDropdownAnyFieldSearchButton />
|
||||
) : (
|
||||
<ViewBarFilterDropdownVectorSearchButton />
|
||||
)}
|
||||
<ViewBarFilterDropdownAdvancedFilterButton />
|
||||
</StyledContainer>
|
||||
);
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import { objectFilterDropdownAnyFieldSearchIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownAnyFieldSearchIsSelectedComponentState';
|
||||
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 { ViewBarFilterDropdownAnyFieldSearchInput } from '@/views/components/ViewBarFilterDropdownAnyFieldSearchInput';
|
||||
import { ViewBarFilterDropdownFieldSelectMenu } from '@/views/components/ViewBarFilterDropdownFieldSelectMenu';
|
||||
import { ViewBarFilterDropdownFilterInput } from '@/views/components/ViewBarFilterDropdownFilterInput';
|
||||
import { ViewBarFilterDropdownVectorSearchInput } from '@/views/components/ViewBarFilterDropdownVectorSearchInput';
|
||||
@ -14,6 +16,11 @@ export const ViewBarFilterDropdownContent = () => {
|
||||
VIEW_BAR_FILTER_DROPDOWN_ID,
|
||||
);
|
||||
|
||||
const objectFilterDropdownAnyFieldSearchIsSelected =
|
||||
useRecoilComponentValueV2(
|
||||
objectFilterDropdownAnyFieldSearchIsSelectedComponentState,
|
||||
);
|
||||
|
||||
const selectedOperandInDropdown = useRecoilComponentValueV2(
|
||||
selectedOperandInDropdownComponentState,
|
||||
);
|
||||
@ -21,6 +28,12 @@ export const ViewBarFilterDropdownContent = () => {
|
||||
const isVectorSearchFilter =
|
||||
selectedOperandInDropdown === ViewFilterOperand.VectorSearch;
|
||||
|
||||
const isAnyFieldSearchFilter = objectFilterDropdownAnyFieldSearchIsSelected;
|
||||
|
||||
if (isAnyFieldSearchFilter) {
|
||||
return <ViewBarFilterDropdownAnyFieldSearchInput />;
|
||||
}
|
||||
|
||||
if (isVectorSearchFilter) {
|
||||
return <ViewBarFilterDropdownVectorSearchInput />;
|
||||
}
|
||||
|
||||
@ -0,0 +1 @@
|
||||
export const ANY_FIELD_SEARCH_DROPDOWN_ID = 'any-field-search-dropdown';
|
||||
@ -0,0 +1,38 @@
|
||||
import { objectFilterDropdownAnyFieldSearchIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownAnyFieldSearchIsSelectedComponentState';
|
||||
import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { viewAnyFieldSearchValueComponentState } from '@/views/states/viewAnyFieldSearchValueComponentState';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
export const useOpenAnyFieldSearchFilterFromViewBar = () => {
|
||||
const setViewAnyFieldSearchValueComponentState = useSetRecoilComponentStateV2(
|
||||
viewAnyFieldSearchValueComponentState,
|
||||
);
|
||||
|
||||
const setObjectFilterDropdownAnyFieldSearchIsSelectedComponentState =
|
||||
useSetRecoilComponentStateV2(
|
||||
objectFilterDropdownAnyFieldSearchIsSelectedComponentState,
|
||||
);
|
||||
|
||||
const objectFilterDropdownSearchInput = useRecoilComponentValueV2(
|
||||
objectFilterDropdownSearchInputComponentState,
|
||||
);
|
||||
|
||||
const openAnyFieldSearchFilterFromViewBar = () => {
|
||||
const userHasAlreadyEnteredSearchInputForObjectDropdownSearch =
|
||||
isNonEmptyString(objectFilterDropdownSearchInput);
|
||||
|
||||
if (userHasAlreadyEnteredSearchInputForObjectDropdownSearch) {
|
||||
const filterValue = objectFilterDropdownSearchInput;
|
||||
|
||||
setViewAnyFieldSearchValueComponentState(filterValue);
|
||||
}
|
||||
|
||||
setObjectFilterDropdownAnyFieldSearchIsSelectedComponentState(true);
|
||||
};
|
||||
|
||||
return {
|
||||
openAnyFieldSearchFilterFromViewBar,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,9 @@
|
||||
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
||||
|
||||
export const viewAnyFieldSearchValueComponentState =
|
||||
createComponentStateV2<string>({
|
||||
key: 'viewAnyFieldSearchValueComponentState',
|
||||
defaultValue: '',
|
||||
componentInstanceContext: ViewComponentInstanceContext,
|
||||
});
|
||||
@ -11,4 +11,5 @@ export enum FeatureFlagKey {
|
||||
IS_RELATION_CONNECT_ENABLED = 'IS_RELATION_CONNECT_ENABLED',
|
||||
IS_WORKSPACE_API_KEY_WEBHOOK_GRAPHQL_ENABLED = 'IS_WORKSPACE_API_KEY_WEBHOOK_GRAPHQL_ENABLED',
|
||||
IS_FIELDS_PERMISSIONS_ENABLED = 'IS_FIELDS_PERMISSIONS_ENABLED',
|
||||
IS_ANY_FIELD_SEARCH_ENABLED = 'IS_ANY_FIELD_SEARCH_ENABLED',
|
||||
}
|
||||
|
||||
@ -107,6 +107,7 @@ describe('WorkspaceEntityManager', () => {
|
||||
IS_RELATION_CONNECT_ENABLED: false,
|
||||
IS_WORKSPACE_API_KEY_WEBHOOK_GRAPHQL_ENABLED: false,
|
||||
IS_FIELDS_PERMISSIONS_ENABLED: false,
|
||||
IS_ANY_FIELD_SEARCH_ENABLED: false,
|
||||
},
|
||||
eventEmitterService: {
|
||||
emitMutationEvent: jest.fn(),
|
||||
|
||||
@ -55,6 +55,11 @@ export const seedFeatureFlags = async (
|
||||
workspaceId: workspaceId,
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
key: FeatureFlagKey.IS_ANY_FIELD_SEARCH_ENABLED,
|
||||
workspaceId: workspaceId,
|
||||
value: true,
|
||||
},
|
||||
])
|
||||
.execute();
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user