Added dashboard chart advanced filter components (#15095)

This PR adds the components for advanced filters on chart settings.

It also refactors what's in common between this new command menu page
and the workflow advanced filters.
This commit is contained in:
Lucas Bordeau
2025-10-15 16:06:15 +02:00
committed by GitHub
parent bd16c77fed
commit 7f8ec35b5d
24 changed files with 445 additions and 190 deletions
@@ -1,10 +1,10 @@
import { AdvancedFilterCommandMenuColumn } from '@/object-record/advanced-filter/command-menu/components/AdvancedFilterCommandMenuColumn';
import { WorkflowStepFilterFieldSelect } from '@/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterFieldSelect';
import { WorkflowStepFilterLogicalOperatorCell } from '@/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterLogicalOperatorCell';
import { WorkflowStepFilterOperandSelect } from '@/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterOperandSelect';
import { WorkflowStepFilterOptionsDropdown } from '@/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterOptionsDropdown';
import { WorkflowStepFilterValueInput } from '@/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterValueInput';
import { WorkflowStepFilterContext } from '@/workflow/workflow-steps/workflow-actions/filter-action/states/context/WorkflowStepFilterContext';
import { WorkflowAdvancedFilterDropdownColumn } from '@/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowAdvancedFilterDropdownColumn';
import styled from '@emotion/styled';
import { useContext } from 'react';
import { type StepFilter, type StepFilterGroup } from 'twenty-shared/types';
@@ -30,7 +30,7 @@ export const WorkflowStepFilterColumn = ({
const { readonly } = useContext(WorkflowStepFilterContext);
return (
<WorkflowAdvancedFilterDropdownColumn>
<AdvancedFilterCommandMenuColumn>
<StyledContainer>
<WorkflowStepFilterLogicalOperatorCell
index={stepFilterIndex}
@@ -43,6 +43,6 @@ export const WorkflowStepFilterColumn = ({
<WorkflowStepFilterFieldSelect stepFilter={stepFilter} />
<WorkflowStepFilterOperandSelect stepFilter={stepFilter} />
<WorkflowStepFilterValueInput stepFilter={stepFilter} />
</WorkflowAdvancedFilterDropdownColumn>
</AdvancedFilterCommandMenuColumn>
);
};
@@ -1,8 +1,8 @@
import { AdvancedFilterCommandMenuColumn } from '@/object-record/advanced-filter/command-menu/components/AdvancedFilterCommandMenuColumn';
import { WorkflowStepFilterGroupChildren } from '@/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterGroupChildren';
import { WorkflowStepFilterGroupOptionsDropdown } from '@/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterGroupOptionsDropdown';
import { WorkflowStepFilterLogicalOperatorCell } from '@/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterLogicalOperatorCell';
import { WorkflowStepFilterContext } from '@/workflow/workflow-steps/workflow-actions/filter-action/states/context/WorkflowStepFilterContext';
import { WorkflowAdvancedFilterDropdownColumn } from '@/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowAdvancedFilterDropdownColumn';
import styled from '@emotion/styled';
import { useContext } from 'react';
import { type StepFilterGroup } from 'twenty-shared/types';
@@ -28,7 +28,7 @@ export const WorkflowStepFilterGroupColumn = ({
const { readonly } = useContext(WorkflowStepFilterContext);
return (
<WorkflowAdvancedFilterDropdownColumn>
<AdvancedFilterCommandMenuColumn>
<StyledContainer>
<WorkflowStepFilterLogicalOperatorCell
index={stepFilterGroupIndex}
@@ -41,6 +41,6 @@ export const WorkflowStepFilterGroupColumn = ({
)}
</StyledContainer>
<WorkflowStepFilterGroupChildren stepFilterGroupId={stepFilterGroup.id} />
</WorkflowAdvancedFilterDropdownColumn>
</AdvancedFilterCommandMenuColumn>
);
};
@@ -1,10 +0,0 @@
import styled from '@emotion/styled';
const StyledColumn = styled.div`
display: flex;
flex-direction: column;
width: 100%;
gap: ${({ theme }) => theme.spacing(1)};
`;
export const WorkflowAdvancedFilterDropdownColumn = StyledColumn;
@@ -1,23 +0,0 @@
import { useRecordFilterField } from '@/object-record/advanced-filter/hooks/useRecordFilterField';
import { SelectControl } from '@/ui/input/components/SelectControl';
type WorkflowAdvancedFilterFieldSelectDisabledProps = {
recordFilterId: string;
};
export const WorkflowAdvancedFilterFieldSelectDisabled = ({
recordFilterId,
}: WorkflowAdvancedFilterFieldSelectDisabledProps) => {
const { label, icon } = useRecordFilterField(recordFilterId);
return (
<SelectControl
selectedOption={{
label,
value: null,
Icon: icon,
}}
isDisabled
/>
);
};
@@ -1,156 +0,0 @@
import { formatFieldMetadataItemAsFieldDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsFieldDefinition';
import { AdvancedFilterContext } from '@/object-record/advanced-filter/states/context/AdvancedFilterContext';
import { shouldShowFilterTextInput } from '@/object-record/advanced-filter/utils/shouldShowFilterTextInput';
import { useApplyObjectFilterDropdownFilterValue } from '@/object-record/object-filter-dropdown/hooks/useApplyObjectFilterDropdownFilterValue';
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
import { subFieldNameUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState';
import { configurableViewFilterOperands } from '@/object-record/object-filter-dropdown/utils/configurableViewFilterOperands';
import { FormFieldInput } from '@/object-record/record-field/ui/components/FormFieldInput';
import { FormMultiSelectFieldInput } from '@/object-record/record-field/ui/form-types/components/FormMultiSelectFieldInput';
import { FormTextFieldInput } from '@/object-record/record-field/ui/form-types/components/FormTextFieldInput';
import {
type FieldMetadata,
type FieldMultiSelectMetadata,
type FieldSelectMetadata,
} from '@/object-record/record-field/ui/types/FieldMetadata';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { WorkflowAdvancedFilterValueFormCompositeFieldInput } from '@/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowAdvancedFilterValueFormCompositeFieldInput';
import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
import { isObject, isString } from '@sniptt/guards';
import { useContext } from 'react';
import { FieldMetadataType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { type JsonValue } from 'type-fest';
export const WorkflowAdvancedFilterValueFormInput = ({
recordFilterId,
}: {
recordFilterId: string;
}) => {
const { readonly } = useContext(AdvancedFilterContext);
const currentRecordFilters = useRecoilComponentValue(
currentRecordFiltersComponentState,
);
const { objectMetadataItem } = useRecordIndexContextOrThrow();
const subFieldNameUsedInDropdown = useRecoilComponentValue(
subFieldNameUsedInDropdownComponentState,
);
const recordFilter = currentRecordFilters.find(
(recordFilter) => recordFilter.id === recordFilterId,
);
const isDisabled = !recordFilter?.fieldMetadataId || !recordFilter.operand;
const operandHasNoInput =
(recordFilter &&
!configurableViewFilterOperands.has(recordFilter.operand)) ??
true;
const { applyObjectFilterDropdownFilterValue } =
useApplyObjectFilterDropdownFilterValue();
const handleChange = (newValue: JsonValue) => {
if (isString(newValue)) {
applyObjectFilterDropdownFilterValue(newValue);
} else if (Array.isArray(newValue) || isObject(newValue)) {
applyObjectFilterDropdownFilterValue(JSON.stringify(newValue));
} else {
applyObjectFilterDropdownFilterValue(String(newValue));
}
};
const fieldMetadataItemUsedInDropdown = useRecoilComponentValue(
fieldMetadataItemUsedInDropdownComponentSelector,
);
const fieldDefinition = fieldMetadataItemUsedInDropdown
? formatFieldMetadataItemAsFieldDefinition({
field: fieldMetadataItemUsedInDropdown,
objectMetadataItem: objectMetadataItem,
})
: null;
if (!isDefined(recordFilter)) {
return null;
}
const isFilterableByTextValue = shouldShowFilterTextInput({
recordFilter,
subFieldNameUsedInDropdown,
});
const isFilterableByMultiSelectValue =
recordFilter.type === FieldMetadataType.MULTI_SELECT ||
recordFilter.type === FieldMetadataType.SELECT;
const isFilterableByDateValue =
recordFilter.type === FieldMetadataType.DATE ||
recordFilter.type === FieldMetadataType.DATE_TIME;
if (isDisabled || operandHasNoInput) {
return null;
}
if (isFilterableByTextValue) {
return (
<FormTextFieldInput
label=""
defaultValue={recordFilter.value}
onChange={handleChange}
readonly={readonly}
VariablePicker={WorkflowVariablePicker}
/>
);
}
if (isDefined(subFieldNameUsedInDropdown)) {
return (
<WorkflowAdvancedFilterValueFormCompositeFieldInput
recordFilter={recordFilter}
onChange={handleChange}
/>
);
}
if (isFilterableByMultiSelectValue) {
const metadata = fieldDefinition?.metadata as
| FieldMultiSelectMetadata
| FieldSelectMetadata
| undefined;
return (
<FormMultiSelectFieldInput
label=""
defaultValue={recordFilter.value}
onChange={handleChange}
readonly={readonly}
VariablePicker={WorkflowVariablePicker}
options={metadata?.options ?? []}
/>
);
}
const field = {
type: recordFilter.type as FieldMetadataType,
label: '',
metadata: fieldDefinition?.metadata as FieldMetadata,
};
return (
<FormFieldInput
field={field}
defaultValue={recordFilter.value}
onChange={handleChange}
readonly={readonly}
// VariablePicker is not supported for date filters yet
VariablePicker={
isFilterableByDateValue ? undefined : WorkflowVariablePicker
}
/>
);
};
@@ -1,64 +0,0 @@
import { AdvancedFilterLogicalOperatorDropdown } from '@/object-record/advanced-filter/components/AdvancedFilterLogicalOperatorDropdown';
import { ADVANCED_FILTER_LOGICAL_OPERATOR_OPTIONS } from '@/object-record/advanced-filter/constants/AdvancedFilterLogicalOperatorOptions';
import { DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET } from '@/object-record/advanced-filter/constants/DefaultAdvancedFilterDropdownOffset';
import { AdvancedFilterContext } from '@/object-record/advanced-filter/states/context/AdvancedFilterContext';
import { type RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup';
import { Select } from '@/ui/input/components/Select';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import styled from '@emotion/styled';
import { useContext } from 'react';
import { capitalize } from 'twenty-shared/utils';
const StyledText = styled.div`
align-items: center;
color: ${({ theme }) => theme.font.color.primary};
display: flex;
height: ${({ theme }) => theme.spacing(8)};
`;
const StyledContainer = styled.div`
align-items: start;
display: flex;
min-width: ${({ theme }) => theme.spacing(20)};
color: ${({ theme }) => theme.font.color.tertiary};
`;
type WorkflowAdvancedFilterLogicalOperatorCellProps = {
index: number;
recordFilterGroup: RecordFilterGroup;
};
export const WorkflowAdvancedFilterLogicalOperatorCell = ({
index,
recordFilterGroup,
}: WorkflowAdvancedFilterLogicalOperatorCellProps) => {
const { readonly } = useContext(AdvancedFilterContext);
return (
<StyledContainer>
{index === 0 ? (
<StyledText>Where</StyledText>
) : index === 1 ? (
readonly ? (
<Select
dropdownWidth={GenericDropdownContentWidth.Narrow}
dropdownId={`advanced-filter-logical-operator-${recordFilterGroup.id}`}
value={recordFilterGroup.logicalOperator}
options={ADVANCED_FILTER_LOGICAL_OPERATOR_OPTIONS}
dropdownOffset={DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET}
disabled
/>
) : (
<AdvancedFilterLogicalOperatorDropdown
recordFilterGroup={recordFilterGroup}
/>
)
) : (
<StyledText>
{capitalize(recordFilterGroup.logicalOperator.toLowerCase())}
</StyledText>
)}
</StyledContainer>
);
};
@@ -1,72 +0,0 @@
import { AdvancedFilterFieldSelectDropdownButton } from '@/object-record/advanced-filter/components/AdvancedFilterFieldSelectDropdownButton';
import { AdvancedFilterRecordFilterOptionsDropdown } from '@/object-record/advanced-filter/components/AdvancedFilterRecordFilterOptionsDropdown';
import { AdvancedFilterContext } from '@/object-record/advanced-filter/states/context/AdvancedFilterContext';
import { getAdvancedFilterObjectFilterDropdownComponentInstanceId } from '@/object-record/advanced-filter/utils/getAdvancedFilterObjectFilterDropdownComponentInstanceId';
import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext';
import { type RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup';
import { type RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
import { WorkflowAdvancedFilterDropdownColumn } from '@/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowAdvancedFilterDropdownColumn';
import { WorkflowAdvancedFilterFieldSelectDisabled } from '@/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowAdvancedFilterFieldSelectDisabled';
import { WorkflowAdvancedFilterValueFormInput } from '@/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowAdvancedFilterFormInput';
import { WorkflowAdvancedFilterLogicalOperatorCell } from '@/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowAdvancedFilterLogicalOperatorCell';
import { WorkflowAdvancedFilterRecordFilterOperandSelect } from '@/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowAdvancedFilterRecordFilterOperandSelect';
import styled from '@emotion/styled';
import { useContext } from 'react';
const StyledContainer = styled.div`
display: flex;
flex-direction: row;
justify-content: space-between;
gap: ${({ theme }) => theme.spacing(1)};
`;
export const WorkflowAdvancedFilterRecordFilterColumn = ({
recordFilterGroup,
recordFilter,
recordFilterIndex,
}: {
recordFilterGroup: RecordFilterGroup;
recordFilter: RecordFilter;
recordFilterIndex: number;
}) => {
const { readonly } = useContext(AdvancedFilterContext);
return (
<ObjectFilterDropdownComponentInstanceContext.Provider
value={{
instanceId: getAdvancedFilterObjectFilterDropdownComponentInstanceId(
recordFilter.id,
),
}}
>
<WorkflowAdvancedFilterDropdownColumn>
<StyledContainer>
<WorkflowAdvancedFilterLogicalOperatorCell
index={recordFilterIndex}
recordFilterGroup={recordFilterGroup}
/>
{!readonly && (
<AdvancedFilterRecordFilterOptionsDropdown
recordFilterId={recordFilter.id}
/>
)}
</StyledContainer>
{readonly ? (
<WorkflowAdvancedFilterFieldSelectDisabled
recordFilterId={recordFilter.id}
/>
) : (
<AdvancedFilterFieldSelectDropdownButton
recordFilterId={recordFilter.id}
/>
)}
<WorkflowAdvancedFilterRecordFilterOperandSelect
recordFilterId={recordFilter.id}
/>
<WorkflowAdvancedFilterValueFormInput
recordFilterId={recordFilter.id}
/>
</WorkflowAdvancedFilterDropdownColumn>
</ObjectFilterDropdownComponentInstanceContext.Provider>
);
};
@@ -1,61 +0,0 @@
import { AdvancedFilterAddFilterRuleSelect } from '@/object-record/advanced-filter/components/AdvancedFilterAddFilterRuleSelect';
import { useChildRecordFiltersAndRecordFilterGroups } from '@/object-record/advanced-filter/hooks/useChildRecordFiltersAndRecordFilterGroups';
import { AdvancedFilterContext } from '@/object-record/advanced-filter/states/context/AdvancedFilterContext';
import { WorkflowAdvancedFilterRecordFilterColumn } from '@/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowAdvancedFilterRecordFilterColumn';
import styled from '@emotion/styled';
import { useContext } from 'react';
import { isDefined } from 'twenty-shared/utils';
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(6)};
padding: ${({ theme }) => theme.spacing(2)};
`;
type WorkflowAdvancedFilterRecordFilterGroupChildrenProps = {
recordFilterGroupId: string;
};
export const WorkflowAdvancedFilterRecordFilterGroupChildren = ({
recordFilterGroupId,
}: WorkflowAdvancedFilterRecordFilterGroupChildrenProps) => {
const { readonly } = useContext(AdvancedFilterContext);
const { currentRecordFilterGroup, childRecordFilters } =
useChildRecordFiltersAndRecordFilterGroups({
recordFilterGroupId,
});
if (!currentRecordFilterGroup) {
return null;
}
const hasParentRecordFilterGroup = isDefined(
currentRecordFilterGroup.parentRecordFilterGroupId,
);
return (
<StyledContainer isGrayBackground={hasParentRecordFilterGroup}>
{childRecordFilters.map((childRecordFilter, childRecordFilterIndex) => (
<WorkflowAdvancedFilterRecordFilterColumn
key={childRecordFilter.id}
recordFilter={childRecordFilter}
recordFilterIndex={childRecordFilterIndex}
recordFilterGroup={currentRecordFilterGroup}
/>
))}
{!readonly && (
<AdvancedFilterAddFilterRuleSelect
recordFilterGroup={currentRecordFilterGroup}
/>
)}
</StyledContainer>
);
};
@@ -1,46 +0,0 @@
import { AdvancedFilterRecordFilterGroupOptionsDropdown } from '@/object-record/advanced-filter/components/AdvancedFilterRecordFilterGroupOptionsDropdown';
import { AdvancedFilterContext } from '@/object-record/advanced-filter/states/context/AdvancedFilterContext';
import { type RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup';
import { WorkflowAdvancedFilterDropdownColumn } from '@/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowAdvancedFilterDropdownColumn';
import { WorkflowAdvancedFilterLogicalOperatorCell } from '@/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowAdvancedFilterLogicalOperatorCell';
import { WorkflowAdvancedFilterRecordFilterGroupChildren } from '@/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowAdvancedFilterRecordFilterGroupChildren';
import styled from '@emotion/styled';
import { useContext } from 'react';
const StyledContainer = styled.div`
display: flex;
flex-direction: row;
justify-content: space-between;
gap: ${({ theme }) => theme.spacing(1)};
`;
export const WorkflowAdvancedFilterRecordFilterGroupColumn = ({
parentRecordFilterGroup,
recordFilterGroup,
recordFilterGroupIndex,
}: {
parentRecordFilterGroup: RecordFilterGroup;
recordFilterGroup: RecordFilterGroup;
recordFilterGroupIndex: number;
}) => {
const { readonly } = useContext(AdvancedFilterContext);
return (
<WorkflowAdvancedFilterDropdownColumn>
<StyledContainer>
<WorkflowAdvancedFilterLogicalOperatorCell
index={recordFilterGroupIndex}
recordFilterGroup={parentRecordFilterGroup}
/>
{!readonly && (
<AdvancedFilterRecordFilterGroupOptionsDropdown
recordFilterGroupId={recordFilterGroup.id}
/>
)}
</StyledContainer>
<WorkflowAdvancedFilterRecordFilterGroupChildren
recordFilterGroupId={recordFilterGroup.id}
/>
</WorkflowAdvancedFilterDropdownColumn>
);
};
@@ -1,60 +0,0 @@
import { AdvancedFilterRecordFilterOperandSelectContent } from '@/object-record/advanced-filter/components/AdvancedFilterRecordFilterOperandSelectContent';
import { AdvancedFilterContext } from '@/object-record/advanced-filter/states/context/AdvancedFilterContext';
import { getOperandLabel } from '@/object-record/object-filter-dropdown/utils/getOperandLabel';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { RecordFilterOperand } from '@/object-record/record-filter/types/RecordFilterOperand';
import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands';
import { SelectControl } from '@/ui/input/components/SelectControl';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useContext } from 'react';
import { isDefined } from 'twenty-shared/utils';
type WorkflowAdvancedFilterRecordFilterOperandSelectProps = {
recordFilterId: string;
};
export const WorkflowAdvancedFilterRecordFilterOperandSelect = ({
recordFilterId,
}: WorkflowAdvancedFilterRecordFilterOperandSelectProps) => {
const { readonly } = useContext(AdvancedFilterContext);
const currentRecordFilters = useRecoilComponentValue(
currentRecordFiltersComponentState,
);
const filter = currentRecordFilters.find(
(recordFilter) => recordFilter.id === recordFilterId,
);
const isDisabled = !filter?.fieldMetadataId || readonly;
const filterType = filter?.type;
const operandsForFilterType = isDefined(filterType)
? getRecordFilterOperands({
filterType,
subFieldName: filter?.subFieldName,
}).filter((operand) => operand !== RecordFilterOperand.IS_RELATIVE)
: [];
if (isDisabled === true) {
return (
<SelectControl
selectedOption={{
label: filter?.operand
? getOperandLabel(filter.operand)
: 'Select operand',
value: null,
}}
isDisabled
/>
);
}
return (
<AdvancedFilterRecordFilterOperandSelectContent
recordFilterId={recordFilterId}
filter={filter}
operandsForFilterType={operandsForFilterType}
/>
);
};
@@ -1,90 +0,0 @@
import { AdvancedFilterContext } from '@/object-record/advanced-filter/states/context/AdvancedFilterContext';
import { subFieldNameUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState';
import { FormCountryMultiSelectInput } from '@/object-record/record-field/ui/form-types/components/FormCountryMultiSelectInput';
import { FormMultiSelectFieldInput } from '@/object-record/record-field/ui/form-types/components/FormMultiSelectFieldInput';
import { FormNumberFieldInput } from '@/object-record/record-field/ui/form-types/components/FormNumberFieldInput';
import { FormTextFieldInput } from '@/object-record/record-field/ui/form-types/components/FormTextFieldInput';
import { type RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
import { CURRENCIES } from '@/settings/data-model/constants/Currencies';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
import { useContext } from 'react';
import { type JsonValue } from 'type-fest';
export const WorkflowAdvancedFilterValueFormCompositeFieldInput = ({
recordFilter,
onChange,
}: {
recordFilter: RecordFilter;
onChange: (newValue: JsonValue) => void;
}) => {
const subFieldNameUsedInDropdown = useRecoilComponentValue(
subFieldNameUsedInDropdownComponentState,
);
const filterType = recordFilter.type;
const { readonly } = useContext(AdvancedFilterContext);
return (
<>
{filterType === 'ADDRESS' ? (
subFieldNameUsedInDropdown === 'addressCountry' ? (
<FormCountryMultiSelectInput
defaultValue={recordFilter.value}
onChange={onChange}
VariablePicker={WorkflowVariablePicker}
readonly={readonly}
/>
) : (
<FormTextFieldInput
defaultValue={recordFilter.value}
onChange={onChange}
VariablePicker={WorkflowVariablePicker}
readonly={readonly}
/>
)
) : filterType === 'CURRENCY' ? (
recordFilter.subFieldName === 'currencyCode' ? (
<FormMultiSelectFieldInput
defaultValue={recordFilter.value}
onChange={onChange}
VariablePicker={WorkflowVariablePicker}
options={CURRENCIES}
readonly={readonly}
/>
) : recordFilter.subFieldName === 'amountMicros' ? (
<FormNumberFieldInput
defaultValue={recordFilter.value}
onChange={onChange}
VariablePicker={WorkflowVariablePicker}
readonly={readonly}
/>
) : null
) : filterType === 'PHONES' ? (
recordFilter.subFieldName === 'primaryPhoneNumber' ? (
<FormNumberFieldInput
defaultValue={recordFilter.value}
onChange={onChange}
VariablePicker={WorkflowVariablePicker}
readonly={readonly}
/>
) : (
<FormTextFieldInput
defaultValue={recordFilter.value}
onChange={onChange}
VariablePicker={WorkflowVariablePicker}
readonly={readonly}
/>
)
) : (
<FormTextFieldInput
defaultValue={recordFilter.value}
onChange={onChange}
VariablePicker={WorkflowVariablePicker}
readonly={readonly}
/>
)}
</>
);
};
@@ -1,78 +0,0 @@
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { AdvancedFilterContext } from '@/object-record/advanced-filter/states/context/AdvancedFilterContext';
import { rootLevelRecordFilterGroupComponentSelector } from '@/object-record/advanced-filter/states/rootLevelRecordFilterGroupComponentSelector';
import { useUpsertRecordFilterGroup } from '@/object-record/record-filter-group/hooks/useUpsertRecordFilterGroup';
import { useCreateEmptyRecordFilterFromFieldMetadataItem } from '@/object-record/record-filter/hooks/useCreateEmptyRecordFilterFromFieldMetadataItem';
import { useUpsertRecordFilter } from '@/object-record/record-filter/hooks/useUpsertRecordFilter';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useSetRecoilComponentFamilyState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentFamilyState';
import { hasInitializedCurrentRecordFiltersComponentFamilyState } from '@/views/states/hasInitializedCurrentRecordFiltersComponentFamilyState';
import { useContext } from 'react';
import { RecordFilterGroupLogicalOperator } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { IconFilter } from 'twenty-ui/display';
import { Button } from 'twenty-ui/input';
import { v4 } from 'uuid';
export const WorkflowFindRecordsAddFilterButton = ({
defaultFieldMetadataItem,
}: {
defaultFieldMetadataItem: FieldMetadataItem;
}) => {
const { readonly } = useContext(AdvancedFilterContext);
const rootRecordFilterGroup = useRecoilComponentValue(
rootLevelRecordFilterGroupComponentSelector,
);
const { upsertRecordFilterGroup } = useUpsertRecordFilterGroup();
const { upsertRecordFilter } = useUpsertRecordFilter();
const { createEmptyRecordFilterFromFieldMetadataItem } =
useCreateEmptyRecordFilterFromFieldMetadataItem();
const setHasInitializedCurrentRecordFilters =
useSetRecoilComponentFamilyState(
hasInitializedCurrentRecordFiltersComponentFamilyState,
{},
);
const addRootRecordFilterGroup = () => {
const alreadyHasAdvancedFilterGroup = isDefined(rootRecordFilterGroup);
if (!alreadyHasAdvancedFilterGroup) {
setHasInitializedCurrentRecordFilters(false);
const newRecordFilterGroup = {
id: v4(),
logicalOperator: RecordFilterGroupLogicalOperator.AND,
};
upsertRecordFilterGroup(newRecordFilterGroup);
if (!isDefined(defaultFieldMetadataItem)) {
throw new Error('Missing default filter definition');
}
const { newRecordFilter } = createEmptyRecordFilterFromFieldMetadataItem(
defaultFieldMetadataItem,
);
newRecordFilter.recordFilterGroupId = newRecordFilterGroup.id;
upsertRecordFilter(newRecordFilter);
}
};
return (
<Button
Icon={IconFilter}
size="small"
variant="secondary"
accent="default"
onClick={addRootRecordFilterGroup}
ariaLabel="Add filter"
title="Add filter"
disabled={readonly}
/>
);
};
@@ -1,39 +1,16 @@
import { availableFieldMetadataItemsForFilterFamilySelector } from '@/object-metadata/states/availableFieldMetadataItemsForFilterFamilySelector';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { AdvancedFilterAddFilterRuleSelect } from '@/object-record/advanced-filter/components/AdvancedFilterAddFilterRuleSelect';
import { useChildRecordFiltersAndRecordFilterGroups } from '@/object-record/advanced-filter/hooks/useChildRecordFiltersAndRecordFilterGroups';
import { AdvancedFilterContext } from '@/object-record/advanced-filter/states/context/AdvancedFilterContext';
import { rootLevelRecordFilterGroupComponentSelector } from '@/object-record/advanced-filter/states/rootLevelRecordFilterGroupComponentSelector';
import { isRecordFilterGroupChildARecordFilterGroup } from '@/object-record/advanced-filter/utils/isRecordFilterGroupChildARecordFilterGroup';
import { AdvancedFilterCommandMenuContainer } from '@/object-record/advanced-filter/command-menu/components/AdvancedFilterCommandMenuContainer';
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
import { WorkflowAdvancedFilterRecordFilterColumn } from '@/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowAdvancedFilterRecordFilterColumn';
import { WorkflowAdvancedFilterRecordFilterGroupColumn } from '@/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowAdvancedFilterRecordFilterGroupColumn';
import { type FindRecordsActionFilter } from '@/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowEditActionFindRecords';
import { WorkflowFindRecordsAddFilterButton } from '@/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowFindRecordsAddFilterButton';
import styled from '@emotion/styled';
import { useRecoilCallback, useRecoilValue } from 'recoil';
import {
computeRecordGqlOperationFilter,
isDefined,
} from 'twenty-shared/utils';
import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
const StyledContainer = styled.div`
align-items: start;
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(2)};
`;
const StyledChildContainer = styled.div`
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(6)};
width: 100%;
`;
import { useRecoilCallback } from 'recoil';
import { computeRecordGqlOperationFilter } from 'twenty-shared/utils';
export const WorkflowFindRecordsFilters = ({
objectMetadataItem,
@@ -44,28 +21,6 @@ export const WorkflowFindRecordsFilters = ({
onChange: (filter: FindRecordsActionFilter) => void;
readonly?: boolean;
}) => {
const rootRecordFilterGroup = useRecoilComponentValue(
rootLevelRecordFilterGroupComponentSelector,
);
const { childRecordFiltersAndRecordFilterGroups } =
useChildRecordFiltersAndRecordFilterGroups({
recordFilterGroupId: rootRecordFilterGroup?.id,
});
const availableFieldMetadataItemsForFilter = useRecoilValue(
availableFieldMetadataItemsForFilterFamilySelector({
objectMetadataItemId: objectMetadataItem.id,
}),
);
const defaultFieldMetadataItem =
availableFieldMetadataItemsForFilter.find(
(fieldMetadataItem) =>
fieldMetadataItem.id ===
objectMetadataItem?.labelIdentifierFieldMetadataId,
) ?? availableFieldMetadataItemsForFilter[0];
const currentRecordFilterGroupsCallbackState =
useRecoilComponentCallbackState(currentRecordFilterGroupsComponentState);
@@ -110,48 +65,12 @@ export const WorkflowFindRecordsFilters = ({
);
return (
<AdvancedFilterContext.Provider
value={{
onUpdate: readonly ? undefined : onUpdate,
isWorkflowFindRecords: true,
readonly,
}}
>
{isDefined(rootRecordFilterGroup) ? (
<StyledContainer>
<StyledChildContainer>
{childRecordFiltersAndRecordFilterGroups.map(
(recordFilterGroupChild, recordFilterGroupChildIndex) =>
isRecordFilterGroupChildARecordFilterGroup(
recordFilterGroupChild,
) ? (
<WorkflowAdvancedFilterRecordFilterGroupColumn
key={recordFilterGroupChild.id}
parentRecordFilterGroup={rootRecordFilterGroup}
recordFilterGroup={recordFilterGroupChild}
recordFilterGroupIndex={recordFilterGroupChildIndex}
/>
) : (
<WorkflowAdvancedFilterRecordFilterColumn
key={recordFilterGroupChild.id}
recordFilterGroup={rootRecordFilterGroup}
recordFilter={recordFilterGroupChild}
recordFilterIndex={recordFilterGroupChildIndex}
/>
),
)}
</StyledChildContainer>
{!readonly && (
<AdvancedFilterAddFilterRuleSelect
recordFilterGroup={rootRecordFilterGroup}
/>
)}
</StyledContainer>
) : (
<WorkflowFindRecordsAddFilterButton
defaultFieldMetadataItem={defaultFieldMetadataItem}
/>
)}
</AdvancedFilterContext.Provider>
<AdvancedFilterCommandMenuContainer
objectMetadataItem={objectMetadataItem}
onUpdate={readonly ? undefined : onUpdate}
VariablePicker={WorkflowVariablePicker}
readonly={readonly}
isWorkflowFindRecords={true}
/>
);
};