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:
@ -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;
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
@ -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 />
|
||||
);
|
||||
};
|
||||
@ -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)
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -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}
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
@ -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();
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
@ -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('');
|
||||
|
||||
@ -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"
|
||||
|
||||
Reference in New Issue
Block a user