Reorganized components in advanced filter dropdown (#11089)

This PR essentially focuses on a refactor of the component hierarchy and
naming in advanced filter dropdown, to make it more readable and easy to
maintain.

This refactor was required because this area of the code is recursive,
so it's better to see the same abstract components in the recursion,
instead of trying to guess whether we have the same components than the
level above or not.

Also keep in mind that this refactor is meant to separate the advanced
filter code path from the view bar simple filter code path, while
reusing what's reusable, so here we have a first attempt at finding the
sweet spot, that we'll be able to duplicate on other filter dropdown use
cases.

- We now use AdvancedFilterRecordFilterGroupRow and
AdvancedFilterRecordFilterRow to make it clearer in the advanced filter
dropdown recursion where we are.
- Children component of AdvancedFilterRecordFilterRow have been
abstracted at the same level to make reading easier
- The field selection dropdown is now in a self-explanatory component
that follows the same naming pattern as other dropdowns in the app :
AdvancedFilterFieldSelectDrodownButton, together with
AdvancedFilterFieldSelectDrodownContent.
- The field selection search in the filter dropdown is now a standalone
component : AdvancedFilterFieldSelectSearchInput
- The UI container of a row has been abstracted in a new
AdvancedFilterDropdownRow

Miscellaneous : 
- Renamed a bunch of view filter old naming to record filter naming.
This commit is contained in:
Lucas Bordeau
2025-03-21 16:19:19 +01:00
committed by GitHub
parent d7dabe5826
commit c961d3a60d
15 changed files with 339 additions and 243 deletions

View File

@ -0,0 +1,9 @@
import styled from '@emotion/styled';
const StyledRow = styled.div`
display: flex;
width: 100%;
gap: ${({ theme }) => theme.spacing(2)};
`;
export const AdvancedFilterDropdownRow = StyledRow;

View File

@ -0,0 +1,53 @@
import { AdvancedFilterFieldSelectDrodownContent } from '@/object-record/advanced-filter/components/AdvancedFilterFieldSelectDrodownContent';
import { useAdvancedFilterDropdown } from '@/object-record/advanced-filter/hooks/useAdvancedFilterDropdown';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { SelectControl } from '@/ui/input/components/SelectControl';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import styled from '@emotion/styled';
const StyledContainer = styled.div`
flex: 2;
`;
type AdvancedFilterFieldSelectDrodownButtonProps = {
recordFilterId: string;
};
export const AdvancedFilterFieldSelectDrodownButton = ({
recordFilterId,
}: AdvancedFilterFieldSelectDrodownButtonProps) => {
const { advancedFilterDropdownId } =
useAdvancedFilterDropdown(recordFilterId);
const currentRecordFilters = useRecoilComponentValueV2(
currentRecordFiltersComponentState,
);
const recordFilter = currentRecordFilters.find(
(recordFilter) => recordFilter.id === recordFilterId,
);
const selectedFieldLabel = recordFilter?.label ?? '';
return (
<StyledContainer>
<Dropdown
dropdownId={advancedFilterDropdownId}
clickableComponent={
<SelectControl
selectedOption={{
label: selectedFieldLabel,
value: null,
}}
/>
}
dropdownComponents={<AdvancedFilterFieldSelectDrodownContent />}
dropdownHotkeyScope={{ scope: advancedFilterDropdownId }}
dropdownOffset={{ y: 8, x: 0 }}
dropdownPlacement="bottom-start"
/>
</StyledContainer>
);
};

View File

@ -0,0 +1,20 @@
import { ObjectFilterDropdownFilterSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelect';
import { ObjectFilterDropdownFilterSelectCompositeFieldSubMenu } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectCompositeFieldSubMenu';
import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
export const AdvancedFilterFieldSelectDrodownContent = () => {
const [objectFilterDropdownIsSelectingCompositeField] =
useRecoilComponentStateV2(
objectFilterDropdownIsSelectingCompositeFieldComponentState,
);
const shouldShowCompositeSelectionSubMenu =
objectFilterDropdownIsSelectingCompositeField;
return shouldShowCompositeSelectionSubMenu ? (
<ObjectFilterDropdownFilterSelectCompositeFieldSubMenu />
) : (
<ObjectFilterDropdownFilterSelect />
);
};

View File

@ -0,0 +1,46 @@
import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
export const StyledInput = styled.input`
background: transparent;
border: none;
border-top: none;
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
border-radius: 0;
border-top-left-radius: ${({ theme }) => theme.border.radius.md};
border-top-right-radius: ${({ theme }) => theme.border.radius.md};
color: ${({ theme }) => theme.font.color.primary};
margin: 0;
outline: none;
padding: ${({ theme }) => theme.spacing(2)};
min-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};
}
`;
export const AdvancedFilterFieldSelectSearchInput = () => {
const [objectFilterDropdownSearchInput, setObjectFilterDropdownSearchInput] =
useRecoilComponentStateV2(objectFilterDropdownSearchInputComponentState);
return (
<StyledInput
value={objectFilterDropdownSearchInput}
autoFocus
placeholder={t`Search fields`}
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
setObjectFilterDropdownSearchInput(event.target.value)
}
/>
);
};

View File

@ -1,27 +0,0 @@
import { AdvancedFilterRecordFilterGroupOptionsDropdown } from '@/object-record/advanced-filter/components/AdvancedFilterRecordFilterGroupOptionsDropdown';
import { AdvancedFilterRecordFilterOptionsDropdown } from '@/object-record/advanced-filter/components/AdvancedFilterRecordFilterOptionsDropdown';
import { isRecordFilterGroupChildARecordFilter } from '@/object-record/advanced-filter/utils/isRecordFilterGroupChildARecordFilter';
import { RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup';
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
type AdvancedFilterRecordFilterGroupChildOptionsDropdownProps = {
recordFilterGroupChild: RecordFilter | RecordFilterGroup;
};
export const AdvancedFilterRecordFilterGroupChildOptionsDropdown = ({
recordFilterGroupChild,
}: AdvancedFilterRecordFilterGroupChildOptionsDropdownProps) => {
const isRecordFilter = isRecordFilterGroupChildARecordFilter(
recordFilterGroupChild,
);
return isRecordFilter ? (
<AdvancedFilterRecordFilterOptionsDropdown
recordFilterId={recordFilterGroupChild.id}
/>
) : (
<AdvancedFilterRecordFilterGroupOptionsDropdown
recordFilterGroupId={recordFilterGroupChild.id}
/>
);
};

View File

@ -1,18 +1,10 @@
import { AdvancedFilterAddFilterRuleSelect } from '@/object-record/advanced-filter/components/AdvancedFilterAddFilterRuleSelect';
import { AdvancedFilterLogicalOperatorCell } from '@/object-record/advanced-filter/components/AdvancedFilterLogicalOperatorCell';
import { AdvancedFilterRecordFilterGroupChildOptionsDropdown } from '@/object-record/advanced-filter/components/AdvancedFilterRecordFilterGroupChildOptionsDropdown';
import { AdvancedFilterRecordFilterRow } from '@/object-record/advanced-filter/components/AdvancedFilterRecordFilterRow';
import { AdvancedFilterViewFilter } from '@/object-record/advanced-filter/components/AdvancedFilterViewFilter';
import { useChildRecordFiltersAndRecordFilterGroups } from '@/object-record/advanced-filter/hooks/useChildRecordFiltersAndRecordFilterGroups';
import styled from '@emotion/styled';
import { isDefined } from 'twenty-shared';
const StyledRow = styled.div`
display: flex;
width: 100%;
gap: ${({ theme }) => theme.spacing(2)};
`;
const StyledContainer = styled.div<{ isGrayBackground?: boolean }>`
align-items: start;
background-color: ${({ theme, isGrayBackground }) =>
@ -27,14 +19,14 @@ const StyledContainer = styled.div<{ isGrayBackground?: boolean }>`
overflow: hidden;
`;
type AdvancedFilterRecordFilterGroupProps = {
type AdvancedFilterRecordFilterGroupChildrenProps = {
recordFilterGroupId: string;
};
export const AdvancedFilterRecordFilterGroup = ({
export const AdvancedFilterRecordFilterGroupChildren = ({
recordFilterGroupId,
}: AdvancedFilterRecordFilterGroupProps) => {
const { currentRecordFilterGroup, childRecordFiltersAndRecordFilterGroups } =
}: AdvancedFilterRecordFilterGroupChildrenProps) => {
const { currentRecordFilterGroup, childRecordFilters } =
useChildRecordFiltersAndRecordFilterGroups({
recordFilterGroupId,
});
@ -49,17 +41,13 @@ export const AdvancedFilterRecordFilterGroup = ({
return (
<StyledContainer isGrayBackground={hasParentRecordFilterGroup}>
{childRecordFiltersAndRecordFilterGroups.map((child, i) => (
<StyledRow key={child.id}>
<AdvancedFilterLogicalOperatorCell
index={i}
recordFilterGroup={currentRecordFilterGroup}
/>
<AdvancedFilterViewFilter viewFilterId={child.id} />
<AdvancedFilterRecordFilterGroupChildOptionsDropdown
recordFilterGroupChild={child}
/>
</StyledRow>
{childRecordFilters.map((childRecordFilter, childRecordFilterIndex) => (
<AdvancedFilterRecordFilterRow
key={childRecordFilter.id}
recordFilter={childRecordFilter}
recordFilterIndex={childRecordFilterIndex}
recordFilterGroup={currentRecordFilterGroup}
/>
))}
<AdvancedFilterAddFilterRuleSelect
recordFilterGroup={currentRecordFilterGroup}

View File

@ -0,0 +1,30 @@
import { AdvancedFilterDropdownRow } from '@/object-record/advanced-filter/components/AdvancedFilterDropdownRow';
import { AdvancedFilterLogicalOperatorCell } from '@/object-record/advanced-filter/components/AdvancedFilterLogicalOperatorCell';
import { AdvancedFilterRecordFilterGroupChildren } from '@/object-record/advanced-filter/components/AdvancedFilterRecordFilterGroupChildren';
import { AdvancedFilterRecordFilterGroupOptionsDropdown } from '@/object-record/advanced-filter/components/AdvancedFilterRecordFilterGroupOptionsDropdown';
import { RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup';
export const AdvancedFilterRecordFilterGroupRow = ({
parentRecordFilterGroup,
recordFilterGroup,
recordFilterGroupIndex,
}: {
parentRecordFilterGroup: RecordFilterGroup;
recordFilterGroup: RecordFilterGroup;
recordFilterGroupIndex: number;
}) => {
return (
<AdvancedFilterDropdownRow>
<AdvancedFilterLogicalOperatorCell
index={recordFilterGroupIndex}
recordFilterGroup={parentRecordFilterGroup}
/>
<AdvancedFilterRecordFilterGroupChildren
recordFilterGroupId={recordFilterGroup.id}
/>
<AdvancedFilterRecordFilterGroupOptionsDropdown
recordFilterGroupId={recordFilterGroup.id}
/>
</AdvancedFilterDropdownRow>
);
};

View File

@ -20,21 +20,21 @@ const StyledContainer = styled.div`
flex: 1;
`;
type AdvancedFilterViewFilterOperandSelectProps = {
viewFilterId: string;
type AdvancedFilterRecordFilterOperandSelectProps = {
recordFilterId: string;
};
export const AdvancedFilterViewFilterOperandSelect = ({
viewFilterId,
}: AdvancedFilterViewFilterOperandSelectProps) => {
const dropdownId = `advanced-filter-view-filter-operand-${viewFilterId}`;
export const AdvancedFilterRecordFilterOperandSelect = ({
recordFilterId,
}: AdvancedFilterRecordFilterOperandSelectProps) => {
const dropdownId = `advanced-filter-view-filter-operand-${recordFilterId}`;
const currentRecordFilters = useRecoilComponentValueV2(
currentRecordFiltersComponentState,
);
const filter = currentRecordFilters.find(
(recordFilter) => recordFilter.id === viewFilterId,
(recordFilter) => recordFilter.id === recordFilterId,
);
const { getFieldMetadataItemById } = useGetFieldMetadataItemById();

View File

@ -0,0 +1,44 @@
import { AdvancedFilterDropdownRow } from '@/object-record/advanced-filter/components/AdvancedFilterDropdownRow';
import { AdvancedFilterFieldSelectDrodownButton } from '@/object-record/advanced-filter/components/AdvancedFilterFieldSelectDrodownButton';
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 { 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';
export const AdvancedFilterRecordFilterRow = ({
recordFilterGroup,
recordFilter,
recordFilterIndex,
}: {
recordFilterGroup: RecordFilterGroup;
recordFilter: RecordFilter;
recordFilterIndex: number;
}) => {
return (
<ObjectFilterDropdownComponentInstanceContext.Provider
value={{ instanceId: `advanced-filter-${recordFilter.id}` }}
>
<AdvancedFilterDropdownRow>
<AdvancedFilterLogicalOperatorCell
index={recordFilterIndex}
recordFilterGroup={recordFilterGroup}
/>
<AdvancedFilterFieldSelectDrodownButton
recordFilterId={recordFilter.id}
/>
<AdvancedFilterRecordFilterOperandSelect
recordFilterId={recordFilter.id}
/>
<AdvancedFilterValueInputDropdownButton
recordFilterId={recordFilter.id}
/>
<AdvancedFilterRecordFilterOptionsDropdown
recordFilterId={recordFilter.id}
/>
</AdvancedFilterDropdownRow>
</ObjectFilterDropdownComponentInstanceContext.Provider>
);
};

View File

@ -1,85 +0,0 @@
import { AdvancedFilterAddFilterRuleSelect } from '@/object-record/advanced-filter/components/AdvancedFilterAddFilterRuleSelect';
import { AdvancedFilterLogicalOperatorCell } from '@/object-record/advanced-filter/components/AdvancedFilterLogicalOperatorCell';
import { AdvancedFilterRecordFilterGroupChildOptionsDropdown } from '@/object-record/advanced-filter/components/AdvancedFilterRecordFilterGroupChildOptionsDropdown';
import { AdvancedFilterRecordFilterGroup } from '@/object-record/advanced-filter/components/AdvancedFilterRecordFilterGroup';
import { AdvancedFilterViewFilter } from '@/object-record/advanced-filter/components/AdvancedFilterViewFilter';
import { useChildRecordFiltersAndRecordFilterGroups } from '@/object-record/advanced-filter/hooks/useChildRecordFiltersAndRecordFilterGroups';
import { rootLevelRecordFilterGroupComponentSelector } from '@/object-record/advanced-filter/states/rootLevelRecordFilterGroupComponentSelector';
import { isRecordFilterGroupChildARecordFilterGroup } from '@/object-record/advanced-filter/utils/isRecordFilterGroupChildARecordFilterGroup';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import styled from '@emotion/styled';
import { isDefined } from 'twenty-shared';
const StyledRow = styled.div`
display: flex;
width: 100%;
gap: ${({ theme }) => theme.spacing(2)};
`;
const StyledContainer = styled.div<{ isGrayBackground?: boolean }>`
align-items: start;
background-color: ${({ theme, isGrayBackground }) =>
isGrayBackground ? theme.background.transparent.lighter : 'transparent'};
border: ${({ theme }) => `1px solid ${theme.border.color.medium}`};
border-radius: ${({ theme }) => theme.border.radius.md};
display: flex;
flex: 1;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(2)};
padding: ${({ theme }) => theme.spacing(2)};
overflow: hidden;
`;
export const AdvancedFilterRootLevelViewFilterGroup = () => {
const rootLevelRecordFilterGroup = useRecoilComponentValueV2(
rootLevelRecordFilterGroupComponentSelector,
);
const { childRecordFiltersAndRecordFilterGroups } =
useChildRecordFiltersAndRecordFilterGroups({
recordFilterGroupId: rootLevelRecordFilterGroup?.id,
});
if (!isDefined(rootLevelRecordFilterGroup)) {
return null;
}
return (
<StyledContainer>
{childRecordFiltersAndRecordFilterGroups.map(
(recordFilterGroupChild, recordFilterGroupChildIndex) =>
isRecordFilterGroupChildARecordFilterGroup(recordFilterGroupChild) ? (
<StyledRow key={recordFilterGroupChild.id}>
<AdvancedFilterLogicalOperatorCell
index={recordFilterGroupChildIndex}
recordFilterGroup={rootLevelRecordFilterGroup}
/>
<AdvancedFilterRecordFilterGroup
recordFilterGroupId={recordFilterGroupChild.id}
/>
<AdvancedFilterRecordFilterGroupChildOptionsDropdown
recordFilterGroupChild={recordFilterGroupChild}
/>
</StyledRow>
) : (
<StyledRow key={recordFilterGroupChild.id}>
<AdvancedFilterLogicalOperatorCell
index={recordFilterGroupChildIndex}
recordFilterGroup={rootLevelRecordFilterGroup}
/>
<AdvancedFilterViewFilter
viewFilterId={recordFilterGroupChild.id}
/>
<AdvancedFilterRecordFilterGroupChildOptionsDropdown
recordFilterGroupChild={recordFilterGroupChild}
/>
</StyledRow>
),
)}
<AdvancedFilterAddFilterRuleSelect
recordFilterGroup={rootLevelRecordFilterGroup}
/>
</StyledContainer>
);
};

View File

@ -0,0 +1,65 @@
import { AdvancedFilterAddFilterRuleSelect } from '@/object-record/advanced-filter/components/AdvancedFilterAddFilterRuleSelect';
import { AdvancedFilterRecordFilterGroupRow } from '@/object-record/advanced-filter/components/AdvancedFilterRecordFilterGroupRow';
import { AdvancedFilterRecordFilterRow } from '@/object-record/advanced-filter/components/AdvancedFilterRecordFilterRow';
import { useChildRecordFiltersAndRecordFilterGroups } from '@/object-record/advanced-filter/hooks/useChildRecordFiltersAndRecordFilterGroups';
import { rootLevelRecordFilterGroupComponentSelector } from '@/object-record/advanced-filter/states/rootLevelRecordFilterGroupComponentSelector';
import { isRecordFilterGroupChildARecordFilterGroup } from '@/object-record/advanced-filter/utils/isRecordFilterGroupChildARecordFilterGroup';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import styled from '@emotion/styled';
import { isDefined } from 'twenty-shared';
const StyledContainer = styled.div<{ isGrayBackground?: boolean }>`
align-items: start;
background-color: ${({ theme, isGrayBackground }) =>
isGrayBackground ? theme.background.transparent.lighter : 'transparent'};
border: ${({ theme }) => `1px solid ${theme.border.color.medium}`};
border-radius: ${({ theme }) => theme.border.radius.md};
display: flex;
flex: 1;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(2)};
padding: ${({ theme }) => theme.spacing(2)};
overflow: hidden;
`;
export const AdvancedFilterRootRecordFilterGroup = () => {
const rootRecordFilterGroup = useRecoilComponentValueV2(
rootLevelRecordFilterGroupComponentSelector,
);
const { childRecordFiltersAndRecordFilterGroups } =
useChildRecordFiltersAndRecordFilterGroups({
recordFilterGroupId: rootRecordFilterGroup?.id,
});
if (!isDefined(rootRecordFilterGroup)) {
return null;
}
return (
<StyledContainer>
{childRecordFiltersAndRecordFilterGroups.map(
(recordFilterGroupChild, recordFilterGroupChildIndex) =>
isRecordFilterGroupChildARecordFilterGroup(recordFilterGroupChild) ? (
<AdvancedFilterRecordFilterGroupRow
key={recordFilterGroupChild.id}
parentRecordFilterGroup={rootRecordFilterGroup}
recordFilterGroup={recordFilterGroupChild}
recordFilterGroupIndex={recordFilterGroupChildIndex}
/>
) : (
<AdvancedFilterRecordFilterRow
key={recordFilterGroupChild.id}
recordFilterGroup={rootRecordFilterGroup}
recordFilter={recordFilterGroupChild}
recordFilterIndex={recordFilterGroupChildIndex}
/>
),
)}
<AdvancedFilterAddFilterRuleSelect
recordFilterGroup={rootRecordFilterGroup}
/>
</StyledContainer>
);
};

View File

@ -2,6 +2,7 @@ import { ObjectFilterDropdownFilterInput } from '@/object-record/object-filter-d
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 { configurableViewFilterOperands } from '@/object-record/object-filter-dropdown/utils/configurableViewFilterOperands';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { SelectControl } from '@/ui/input/components/SelectControl';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
@ -9,21 +10,27 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
type AdvancedFilterViewFilterValueInputProps = {
viewFilterId: string;
import styled from '@emotion/styled';
const StyledValueDropdownContainer = styled.div`
flex: 3;
`;
type AdvancedFilterValueInputDropdownButtonProps = {
recordFilterId: string;
};
export const AdvancedFilterViewFilterValueInput = ({
viewFilterId,
}: AdvancedFilterViewFilterValueInputProps) => {
const dropdownId = `advanced-filter-view-filter-value-input-${viewFilterId}`;
export const AdvancedFilterValueInputDropdownButton = ({
recordFilterId,
}: AdvancedFilterValueInputDropdownButtonProps) => {
const dropdownId = `advanced-filter-view-filter-value-input-${recordFilterId}`;
const currentRecordFilters = useRecoilComponentValueV2(
currentRecordFiltersComponentState,
);
const filter = currentRecordFilters.find(
(recordFilter) => recordFilter.id === viewFilterId,
(recordFilter) => recordFilter.id === recordFilterId,
);
const isDisabled = !filter?.fieldMetadataId || !filter.operand;
@ -40,43 +47,48 @@ export const AdvancedFilterViewFilterValueInput = ({
selectedFilterComponentState,
);
if (isDisabled) {
return (
<SelectControl
isDisabled
selectedOption={{
label: filter?.displayValue ?? '',
value: null,
}}
/>
);
}
const operandHasNoInput =
filter && !configurableViewFilterOperands.has(filter.operand);
return (
<Dropdown
dropdownId={dropdownId}
clickableComponent={
<StyledValueDropdownContainer>
{operandHasNoInput ? (
<></>
) : isDisabled ? (
<SelectControl
isDisabled
selectedOption={{
label: filter?.displayValue ?? '',
value: null,
}}
/>
}
onOpen={() => {
setFieldMetadataItemIdUsedInDropdown(filter.fieldMetadataId);
setSelectedOperandInDropdown(filter.operand);
setSelectedFilter(filter);
}}
dropdownComponents={
<DropdownMenuItemsContainer>
<ObjectFilterDropdownFilterInput />
</DropdownMenuItemsContainer>
}
dropdownHotkeyScope={{ scope: dropdownId }}
dropdownOffset={{ y: 8, x: 0 }}
dropdownPlacement="bottom-start"
dropdownMenuWidth={280}
/>
) : (
<Dropdown
dropdownId={dropdownId}
clickableComponent={
<SelectControl
selectedOption={{
label: filter?.displayValue ?? '',
value: null,
}}
/>
}
onOpen={() => {
setFieldMetadataItemIdUsedInDropdown(filter.fieldMetadataId);
setSelectedOperandInDropdown(filter.operand);
setSelectedFilter(filter);
}}
dropdownComponents={
<DropdownMenuItemsContainer>
<ObjectFilterDropdownFilterInput />
</DropdownMenuItemsContainer>
}
dropdownHotkeyScope={{ scope: dropdownId }}
dropdownOffset={{ y: 8, x: 0 }}
dropdownPlacement="bottom-start"
dropdownMenuWidth={280}
/>
)}
</StyledValueDropdownContainer>
);
};

View File

@ -1,60 +0,0 @@
import { AdvancedFilterViewFilterFieldSelect } from '@/object-record/advanced-filter/components/AdvancedFilterViewFilterFieldSelect';
import { AdvancedFilterViewFilterOperandSelect } from '@/object-record/advanced-filter/components/AdvancedFilterViewFilterOperandSelect';
import { AdvancedFilterViewFilterValueInput } from '@/object-record/advanced-filter/components/AdvancedFilterViewFilterValueInput';
import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext';
import { configurableViewFilterOperands } from '@/object-record/object-filter-dropdown/utils/configurableViewFilterOperands';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import styled from '@emotion/styled';
import { isDefined } from 'twenty-shared';
const StyledValueDropdownContainer = styled.div`
flex: 3;
`;
const StyledRow = styled.div`
flex: 1;
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
white-space: nowrap;
overflow: hidden;
`;
type AdvancedFilterViewFilterProps = {
viewFilterId: string;
};
export const AdvancedFilterViewFilter = ({
viewFilterId,
}: AdvancedFilterViewFilterProps) => {
const currentRecordFilters = useRecoilComponentValueV2(
currentRecordFiltersComponentState,
);
const recordFilter = currentRecordFilters.find(
(recordFilter) => recordFilter.id === viewFilterId,
);
if (!isDefined(recordFilter)) {
return null;
}
return (
<ObjectFilterDropdownComponentInstanceContext.Provider
value={{ instanceId: recordFilter.id }}
>
<StyledRow>
<AdvancedFilterViewFilterFieldSelect viewFilterId={recordFilter.id} />
<AdvancedFilterViewFilterOperandSelect viewFilterId={recordFilter.id} />
<StyledValueDropdownContainer>
{configurableViewFilterOperands.has(recordFilter.operand) && (
<AdvancedFilterViewFilterValueInput
viewFilterId={recordFilter.id}
/>
)}
</StyledValueDropdownContainer>
</StyledRow>
</ObjectFilterDropdownComponentInstanceContext.Provider>
);
};

View File

@ -1,6 +1,7 @@
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
import { useAdvancedFilterDropdown } from '@/object-record/advanced-filter/hooks/useAdvancedFilterDropdown';
import { advancedFilterViewFilterGroupIdComponentState } from '@/object-record/object-filter-dropdown/states/advancedFilterViewFilterGroupIdComponentState';
import { advancedFilterViewFilterIdComponentState } from '@/object-record/object-filter-dropdown/states/advancedFilterViewFilterIdComponentState';
import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState';
@ -21,6 +22,7 @@ import { findDuplicateRecordFilterInNonAdvancedRecordFilters } from '@/object-re
import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands';
import { SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsCompositeFieldTypeConfigs';
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
@ -28,7 +30,6 @@ import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-sta
import { useState } from 'react';
import { isDefined } from 'twenty-shared';
import { IconApps, IconChevronLeft, MenuItem, useIcons } from 'twenty-ui';
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
export const ObjectFilterDropdownFilterSelectCompositeFieldSubMenu = () => {
const [searchText] = useState('');

View File

@ -1,6 +1,6 @@
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { AdvancedFilterRootLevelViewFilterGroup } from '@/object-record/advanced-filter/components/AdvancedFilterRootLevelViewFilterGroup';
import { AdvancedFilterRootRecordFilterGroup } from '@/object-record/advanced-filter/components/AdvancedFilterRootRecordFilterGroup';
import { rootLevelRecordFilterGroupComponentSelector } from '@/object-record/advanced-filter/states/rootLevelRecordFilterGroupComponentSelector';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { AdvancedFilterChip } from '@/views/components/AdvancedFilterChip';
@ -20,7 +20,7 @@ export const AdvancedFilterDropdownButton = () => {
<Dropdown
dropdownId={ADVANCED_FILTER_DROPDOWN_ID}
clickableComponent={<AdvancedFilterChip />}
dropdownComponents={<AdvancedFilterRootLevelViewFilterGroup />}
dropdownComponents={<AdvancedFilterRootRecordFilterGroup />}
dropdownHotkeyScope={{ scope: ADVANCED_FILTER_DROPDOWN_ID }}
dropdownOffset={{ y: 8, x: 0 }}
dropdownPlacement="bottom-start"