Fix advanced filter dropdown input components (#11381)
This PR fixes the filter value input components that are used in advanced dropdown filter, which slightly differ from the classic object filter dropdown in the view bar. We notably needed the same experience as other text inputs in the application, for entering filter values. ## New text and number filter experience : <img width="681" alt="image" src="https://github.com/user-attachments/assets/b373bb6b-dc00-4396-9294-9b866b91fa02" /> ## New date filter experience : <img width="683" alt="image" src="https://github.com/user-attachments/assets/8aea22e2-6f3b-4641-9f3d-6d7ba537bc27" /> To obtain the same experience for date input as in workflow forms, it would require to duplicate or factorize a lot of complex code that manipulates dates and user events with the input, it would be better tackled in another issue related to a larger refactor effort : https://github.com/twentyhq/core-team-issues/issues/736 Fixes https://github.com/twentyhq/core-team-issues/issues/674
This commit is contained in:
@ -1,21 +1,23 @@
|
||||
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import { useChildRecordFiltersAndRecordFilterGroups } from '@/object-record/advanced-filter/hooks/useChildRecordFiltersAndRecordFilterGroups';
|
||||
import { useDefaultFieldMetadataItemForFilter } from '@/object-record/advanced-filter/hooks/useDefaultFieldMetadataItemForFilter';
|
||||
import { useSetRecordFilterUsedInAdvancedFilterDropdownRow } from '@/object-record/advanced-filter/hooks/useSetRecordFilterUsedInAdvancedFilterDropdownRow';
|
||||
import { getAdvancedFilterAddFilterRuleSelectDropdownId } from '@/object-record/advanced-filter/utils/getAdvancedFilterAddFilterRuleSelectDropdownId';
|
||||
import { useUpsertRecordFilterGroup } from '@/object-record/record-filter-group/hooks/useUpsertRecordFilterGroup';
|
||||
import { RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup';
|
||||
import { RecordFilterGroupLogicalOperator } from '@/object-record/record-filter-group/types/RecordFilterGroupLogicalOperator';
|
||||
import { useUpsertRecordFilter } from '@/object-record/record-filter/hooks/useUpsertRecordFilter';
|
||||
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||
import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
|
||||
import { v4 } from 'uuid';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IconLibraryPlus, IconPlus } from 'twenty-ui/display';
|
||||
import { LightButton } from 'twenty-ui/input';
|
||||
import { MenuItem } from 'twenty-ui/navigation';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
type AdvancedFilterAddFilterRuleSelectProps = {
|
||||
recordFilterGroup: RecordFilterGroup;
|
||||
@ -45,6 +47,9 @@ export const AdvancedFilterAddFilterRuleSelect = ({
|
||||
const { defaultFieldMetadataItemForFilter } =
|
||||
useDefaultFieldMetadataItemForFilter();
|
||||
|
||||
const { setRecordFilterUsedInAdvancedFilterDropdownRow } =
|
||||
useSetRecordFilterUsedInAdvancedFilterDropdownRow();
|
||||
|
||||
const handleAddFilter = () => {
|
||||
if (!isDefined(defaultFieldMetadataItemForFilter)) {
|
||||
throw new Error('Missing default field metadata item for filter');
|
||||
@ -56,7 +61,7 @@ export const AdvancedFilterAddFilterRuleSelect = ({
|
||||
defaultFieldMetadataItemForFilter.type,
|
||||
);
|
||||
|
||||
upsertRecordFilter({
|
||||
const newRecordFilter: RecordFilter = {
|
||||
id: v4(),
|
||||
fieldMetadataId: defaultFieldMetadataItemForFilter.id,
|
||||
type: filterType,
|
||||
@ -68,7 +73,11 @@ export const AdvancedFilterAddFilterRuleSelect = ({
|
||||
recordFilterGroupId: recordFilterGroup.id,
|
||||
positionInRecordFilterGroup: newPositionInRecordFilterGroup,
|
||||
label: defaultFieldMetadataItemForFilter.label,
|
||||
});
|
||||
};
|
||||
|
||||
upsertRecordFilter(newRecordFilter);
|
||||
|
||||
setRecordFilterUsedInAdvancedFilterDropdownRow(newRecordFilter);
|
||||
};
|
||||
|
||||
const handleAddFilterGroup = () => {
|
||||
@ -97,7 +106,7 @@ export const AdvancedFilterAddFilterRuleSelect = ({
|
||||
defaultFieldMetadataItemForFilter.type,
|
||||
);
|
||||
|
||||
upsertRecordFilter({
|
||||
const newRecordFilter: RecordFilter = {
|
||||
id: v4(),
|
||||
fieldMetadataId: defaultFieldMetadataItemForFilter.id,
|
||||
type: filterType,
|
||||
@ -109,7 +118,11 @@ export const AdvancedFilterAddFilterRuleSelect = ({
|
||||
recordFilterGroupId: newRecordFilterGroupId,
|
||||
positionInRecordFilterGroup: 1,
|
||||
label: defaultFieldMetadataItemForFilter.label,
|
||||
});
|
||||
};
|
||||
|
||||
upsertRecordFilter(newRecordFilter);
|
||||
|
||||
setRecordFilterUsedInAdvancedFilterDropdownRow(newRecordFilter);
|
||||
};
|
||||
|
||||
const isFilterRuleGroupOptionVisible = !isDefined(
|
||||
|
||||
@ -0,0 +1,122 @@
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
|
||||
import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState';
|
||||
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
|
||||
import { getRelativeDateDisplayValue } from '@/object-record/object-filter-dropdown/utils/getRelativeDateDisplayValue';
|
||||
import { useApplyRecordFilter } from '@/object-record/record-filter/hooks/useApplyRecordFilter';
|
||||
import { DateTimePicker } from '@/ui/input/components/internal/date/components/InternalDatePicker';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||
import { computeVariableDateViewFilterValue } from '@/views/view-filter-value/utils/computeVariableDateViewFilterValue';
|
||||
import {
|
||||
resolveDateViewFilterValue,
|
||||
VariableDateViewFilterValueDirection,
|
||||
VariableDateViewFilterValueUnit,
|
||||
} from '@/views/view-filter-value/utils/resolveDateViewFilterValue';
|
||||
import { useState } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
export const AdvancedFilterDropdownDateInput = () => {
|
||||
const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2(
|
||||
fieldMetadataItemUsedInDropdownComponentSelector,
|
||||
);
|
||||
|
||||
const selectedOperandInDropdown = useRecoilComponentValueV2(
|
||||
selectedOperandInDropdownComponentState,
|
||||
);
|
||||
|
||||
const selectedFilter = useRecoilComponentValueV2(
|
||||
selectedFilterComponentState,
|
||||
);
|
||||
|
||||
const { applyRecordFilter } = useApplyRecordFilter();
|
||||
|
||||
const initialFilterValue = selectedFilter
|
||||
? resolveDateViewFilterValue(selectedFilter)
|
||||
: null;
|
||||
const [internalDate, setInternalDate] = useState<Date | null>(
|
||||
initialFilterValue instanceof Date ? initialFilterValue : null,
|
||||
);
|
||||
|
||||
const isDateTimeInput =
|
||||
fieldMetadataItemUsedInDropdown?.type === FieldMetadataType.DATE_TIME;
|
||||
|
||||
const handleAbsoluteDateChange = (newDate: Date | null) => {
|
||||
setInternalDate(newDate);
|
||||
|
||||
if (!fieldMetadataItemUsedInDropdown || !selectedOperandInDropdown) return;
|
||||
|
||||
const newDisplayValue = isDefined(newDate)
|
||||
? newDate.toLocaleDateString()
|
||||
: '';
|
||||
|
||||
applyRecordFilter({
|
||||
id: selectedFilter?.id ? selectedFilter.id : v4(),
|
||||
fieldMetadataId: fieldMetadataItemUsedInDropdown.id,
|
||||
value: newDate?.toISOString() ?? '',
|
||||
operand: selectedOperandInDropdown,
|
||||
displayValue: newDisplayValue,
|
||||
recordFilterGroupId: selectedFilter?.recordFilterGroupId,
|
||||
positionInRecordFilterGroup: selectedFilter?.positionInRecordFilterGroup,
|
||||
type: getFilterTypeFromFieldType(fieldMetadataItemUsedInDropdown.type),
|
||||
label: fieldMetadataItemUsedInDropdown.label,
|
||||
});
|
||||
};
|
||||
|
||||
const handleRelativeDateChange = (
|
||||
relativeDate: {
|
||||
direction: VariableDateViewFilterValueDirection;
|
||||
amount?: number;
|
||||
unit: VariableDateViewFilterValueUnit;
|
||||
} | null,
|
||||
) => {
|
||||
if (!fieldMetadataItemUsedInDropdown || !selectedOperandInDropdown) return;
|
||||
|
||||
const value = relativeDate
|
||||
? computeVariableDateViewFilterValue(
|
||||
relativeDate.direction,
|
||||
relativeDate.amount,
|
||||
relativeDate.unit,
|
||||
)
|
||||
: '';
|
||||
|
||||
applyRecordFilter({
|
||||
id: selectedFilter?.id ? selectedFilter.id : v4(),
|
||||
fieldMetadataId: fieldMetadataItemUsedInDropdown.id,
|
||||
value,
|
||||
operand: selectedOperandInDropdown,
|
||||
displayValue: getRelativeDateDisplayValue(relativeDate),
|
||||
recordFilterGroupId: selectedFilter?.recordFilterGroupId,
|
||||
positionInRecordFilterGroup: selectedFilter?.positionInRecordFilterGroup,
|
||||
type: getFilterTypeFromFieldType(fieldMetadataItemUsedInDropdown.type),
|
||||
label: fieldMetadataItemUsedInDropdown.label,
|
||||
});
|
||||
};
|
||||
|
||||
const isRelativeOperand =
|
||||
selectedOperandInDropdown === ViewFilterOperand.IsRelative;
|
||||
|
||||
const resolvedValue = selectedFilter
|
||||
? resolveDateViewFilterValue(selectedFilter)
|
||||
: null;
|
||||
|
||||
const relativeDate =
|
||||
resolvedValue && !(resolvedValue instanceof Date)
|
||||
? resolvedValue
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<DateTimePicker
|
||||
relativeDate={relativeDate}
|
||||
highlightedDateRange={relativeDate}
|
||||
isRelative={isRelativeOperand}
|
||||
date={internalDate}
|
||||
onChange={handleAbsoluteDateChange}
|
||||
onRelativeDateChange={handleRelativeDateChange}
|
||||
isDateTimeInput={isDateTimeInput}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,83 @@
|
||||
import { ObjectFilterDropdownOptionSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownOptionSelect';
|
||||
import { ObjectFilterDropdownRatingInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRatingInput';
|
||||
import { ObjectFilterDropdownRecordSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect';
|
||||
import { ObjectFilterDropdownSearchInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownSearchInput';
|
||||
import { ObjectFilterDropdownSourceSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownSourceSelect';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
|
||||
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import { AdvancedFilterDropdownDateInput } from '@/object-record/advanced-filter/components/AdvancedFilterDropdownDateInput';
|
||||
import { ObjectFilterDropdownBooleanSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownBooleanSelect';
|
||||
import { ObjectFilterDropdownTextInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownTextInput';
|
||||
import { DATE_FILTER_TYPES } from '@/object-record/object-filter-dropdown/constants/DateFilterTypes';
|
||||
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
|
||||
import { subFieldNameUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState';
|
||||
import { isFilterOnActorSourceSubField } from '@/object-record/object-filter-dropdown/utils/isFilterOnActorSourceSubField';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
type AdvancedFilterDropdownFilterInputProps = {
|
||||
filterDropdownId?: string;
|
||||
recordFilterId?: string;
|
||||
};
|
||||
|
||||
export const AdvancedFilterDropdownFilterInput = ({
|
||||
filterDropdownId,
|
||||
recordFilterId,
|
||||
}: AdvancedFilterDropdownFilterInputProps) => {
|
||||
const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2(
|
||||
fieldMetadataItemUsedInDropdownComponentSelector,
|
||||
filterDropdownId,
|
||||
);
|
||||
|
||||
const subFieldNameUsedInDropdown = useRecoilComponentValueV2(
|
||||
subFieldNameUsedInDropdownComponentState,
|
||||
filterDropdownId,
|
||||
);
|
||||
|
||||
if (!isDefined(fieldMetadataItemUsedInDropdown)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const filterType = getFilterTypeFromFieldType(
|
||||
fieldMetadataItemUsedInDropdown.type,
|
||||
);
|
||||
|
||||
const isActorSourceCompositeFilter = isFilterOnActorSourceSubField(
|
||||
subFieldNameUsedInDropdown,
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{filterType === 'RATING' && <ObjectFilterDropdownRatingInput />}
|
||||
{DATE_FILTER_TYPES.includes(filterType) && (
|
||||
<AdvancedFilterDropdownDateInput />
|
||||
)}
|
||||
{filterType === 'RELATION' && (
|
||||
<>
|
||||
<ObjectFilterDropdownSearchInput />
|
||||
<DropdownMenuSeparator />
|
||||
<ObjectFilterDropdownRecordSelect recordFilterId={recordFilterId} />
|
||||
</>
|
||||
)}
|
||||
{filterType === 'ACTOR' &&
|
||||
(isActorSourceCompositeFilter ? (
|
||||
<>
|
||||
<ObjectFilterDropdownSourceSelect />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ObjectFilterDropdownTextInput />
|
||||
</>
|
||||
))}
|
||||
{['SELECT', 'MULTI_SELECT'].includes(filterType) && (
|
||||
<>
|
||||
<ObjectFilterDropdownSearchInput />
|
||||
<DropdownMenuSeparator />
|
||||
<ObjectFilterDropdownOptionSelect />
|
||||
</>
|
||||
)}
|
||||
{filterType === 'BOOLEAN' && <ObjectFilterDropdownBooleanSelect />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,64 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
|
||||
import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState';
|
||||
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
|
||||
import { useApplyRecordFilter } from '@/object-record/record-filter/hooks/useApplyRecordFilter';
|
||||
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
export const AdvancedFilterDropdownNumberInput = () => {
|
||||
const selectedOperandInDropdown = useRecoilComponentValueV2(
|
||||
selectedOperandInDropdownComponentState,
|
||||
);
|
||||
|
||||
const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2(
|
||||
fieldMetadataItemUsedInDropdownComponentSelector,
|
||||
);
|
||||
|
||||
const selectedFilter = useRecoilComponentValueV2(
|
||||
selectedFilterComponentState,
|
||||
);
|
||||
|
||||
const { applyRecordFilter } = useApplyRecordFilter();
|
||||
|
||||
const [inputValue, setInputValue] = useState(
|
||||
() => selectedFilter?.value || '',
|
||||
);
|
||||
|
||||
const handleChange = (newValue: string) => {
|
||||
if (!fieldMetadataItemUsedInDropdown || !selectedOperandInDropdown) {
|
||||
return;
|
||||
}
|
||||
|
||||
setInputValue(newValue);
|
||||
|
||||
applyRecordFilter({
|
||||
id: selectedFilter?.id ? selectedFilter.id : v4(),
|
||||
fieldMetadataId: fieldMetadataItemUsedInDropdown?.id ?? '',
|
||||
value: newValue,
|
||||
operand: selectedOperandInDropdown,
|
||||
displayValue: newValue,
|
||||
type: getFilterTypeFromFieldType(fieldMetadataItemUsedInDropdown.type),
|
||||
label: fieldMetadataItemUsedInDropdown.label,
|
||||
recordFilterGroupId: selectedFilter?.recordFilterGroupId,
|
||||
positionInRecordFilterGroup: selectedFilter?.positionInRecordFilterGroup,
|
||||
});
|
||||
};
|
||||
|
||||
if (!selectedOperandInDropdown || !fieldMetadataItemUsedInDropdown) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<TextInputV2
|
||||
value={inputValue}
|
||||
onChange={handleChange}
|
||||
placeholder="Enter value"
|
||||
fullWidth
|
||||
type="number"
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,63 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
|
||||
import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState';
|
||||
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
|
||||
import { useApplyRecordFilter } from '@/object-record/record-filter/hooks/useApplyRecordFilter';
|
||||
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
export const AdvancedFilterDropdownTextInput = () => {
|
||||
const selectedOperandInDropdown = useRecoilComponentValueV2(
|
||||
selectedOperandInDropdownComponentState,
|
||||
);
|
||||
|
||||
const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2(
|
||||
fieldMetadataItemUsedInDropdownComponentSelector,
|
||||
);
|
||||
|
||||
const selectedFilter = useRecoilComponentValueV2(
|
||||
selectedFilterComponentState,
|
||||
);
|
||||
|
||||
const { applyRecordFilter } = useApplyRecordFilter();
|
||||
|
||||
const [inputValue, setInputValue] = useState(
|
||||
() => selectedFilter?.value || '',
|
||||
);
|
||||
|
||||
const handleChange = (newValue: string) => {
|
||||
if (!fieldMetadataItemUsedInDropdown || !selectedOperandInDropdown) {
|
||||
return;
|
||||
}
|
||||
|
||||
setInputValue(newValue);
|
||||
|
||||
applyRecordFilter({
|
||||
id: selectedFilter?.id ? selectedFilter.id : v4(),
|
||||
fieldMetadataId: fieldMetadataItemUsedInDropdown?.id ?? '',
|
||||
value: newValue,
|
||||
operand: selectedOperandInDropdown,
|
||||
displayValue: newValue,
|
||||
type: getFilterTypeFromFieldType(fieldMetadataItemUsedInDropdown.type),
|
||||
label: fieldMetadataItemUsedInDropdown.label,
|
||||
recordFilterGroupId: selectedFilter?.recordFilterGroupId,
|
||||
positionInRecordFilterGroup: selectedFilter?.positionInRecordFilterGroup,
|
||||
});
|
||||
};
|
||||
|
||||
if (!selectedOperandInDropdown || !fieldMetadataItemUsedInDropdown) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<TextInputV2
|
||||
value={inputValue}
|
||||
onChange={handleChange}
|
||||
placeholder="Enter value"
|
||||
fullWidth
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -3,7 +3,8 @@ import { AdvancedFilterFieldSelectDropdownButton } from '@/object-record/advance
|
||||
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 { AdvancedFilterValueInput } from '@/object-record/advanced-filter/components/AdvancedFilterValueInput';
|
||||
import { getAdvancedFilterObjectFilterDropdownComponentInstanceId } from '@/object-record/advanced-filter/utils/getAdvancedFilterObjectFilterDropdownComponentInstanceId';
|
||||
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';
|
||||
@ -19,7 +20,11 @@ export const AdvancedFilterRecordFilterRow = ({
|
||||
}) => {
|
||||
return (
|
||||
<ObjectFilterDropdownComponentInstanceContext.Provider
|
||||
value={{ instanceId: `advanced-filter-${recordFilter.id}` }}
|
||||
value={{
|
||||
instanceId: getAdvancedFilterObjectFilterDropdownComponentInstanceId(
|
||||
recordFilter.id,
|
||||
),
|
||||
}}
|
||||
>
|
||||
<AdvancedFilterDropdownRow>
|
||||
<AdvancedFilterLogicalOperatorCell
|
||||
@ -32,9 +37,7 @@ export const AdvancedFilterRecordFilterRow = ({
|
||||
<AdvancedFilterRecordFilterOperandSelect
|
||||
recordFilterId={recordFilter.id}
|
||||
/>
|
||||
<AdvancedFilterValueInputDropdownButton
|
||||
recordFilterId={recordFilter.id}
|
||||
/>
|
||||
<AdvancedFilterValueInput recordFilterId={recordFilter.id} />
|
||||
<AdvancedFilterRecordFilterOptionsDropdown
|
||||
recordFilterId={recordFilter.id}
|
||||
/>
|
||||
|
||||
@ -1,18 +1,14 @@
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { useAdvancedFilterFieldSelectDropdown } from '@/object-record/advanced-filter/hooks/useAdvancedFilterFieldSelectDropdown';
|
||||
import { useSelectFieldUsedInAdvancedFilterDropdown } from '@/object-record/advanced-filter/hooks/useSelectFieldUsedInAdvancedFilterDropdown';
|
||||
import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState';
|
||||
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
|
||||
import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState';
|
||||
import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState';
|
||||
import { objectFilterDropdownSubMenuFieldTypeComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSubMenuFieldTypeComponentState';
|
||||
import { subFieldNameUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState';
|
||||
import { getCompositeSubFieldLabel } from '@/object-record/object-filter-dropdown/utils/getCompositeSubFieldLabel';
|
||||
import { getFilterableFieldTypeLabel } from '@/object-record/object-filter-dropdown/utils/getFilterableFieldTypeLabel';
|
||||
import { SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsCompositeFieldTypeConfigs';
|
||||
@ -35,14 +31,6 @@ export const AdvancedFilterSubFieldSelectMenu = ({
|
||||
fieldMetadataItemUsedInDropdownComponentSelector,
|
||||
);
|
||||
|
||||
const [, setObjectFilterDropdownFilterIsSelected] = useRecoilComponentStateV2(
|
||||
objectFilterDropdownFilterIsSelectedComponentState,
|
||||
);
|
||||
|
||||
const [, setSubFieldNameUsedInDropdown] = useRecoilComponentStateV2(
|
||||
subFieldNameUsedInDropdownComponentState,
|
||||
);
|
||||
|
||||
const [, setObjectFilterDropdownIsSelectingCompositeField] =
|
||||
useRecoilComponentStateV2(
|
||||
objectFilterDropdownIsSelectingCompositeFieldComponentState,
|
||||
@ -55,10 +43,6 @@ export const AdvancedFilterSubFieldSelectMenu = ({
|
||||
objectFilterDropdownSubMenuFieldTypeComponentState,
|
||||
);
|
||||
|
||||
const setFieldMetadataItemIdUsedInDropdown = useSetRecoilComponentStateV2(
|
||||
fieldMetadataItemIdUsedInDropdownComponentState,
|
||||
);
|
||||
|
||||
const { closeAdvancedFilterFieldSelectDropdown } =
|
||||
useAdvancedFilterFieldSelectDropdown(recordFilterId);
|
||||
|
||||
@ -83,11 +67,8 @@ export const AdvancedFilterSubFieldSelectMenu = ({
|
||||
};
|
||||
|
||||
const handleSubMenuBack = () => {
|
||||
setFieldMetadataItemIdUsedInDropdown(null);
|
||||
setObjectFilterDropdownSubMenuFieldType(null);
|
||||
setObjectFilterDropdownIsSelectingCompositeField(false);
|
||||
setObjectFilterDropdownFilterIsSelected(false);
|
||||
setSubFieldNameUsedInDropdown(null);
|
||||
};
|
||||
|
||||
if (!isDefined(objectFilterDropdownSubMenuFieldType)) {
|
||||
|
||||
@ -1,64 +1,61 @@
|
||||
import { AdvancedFilterDropdownFilterInput } from '@/object-record/advanced-filter/components/AdvancedFilterDropdownFilterInput';
|
||||
import { AdvancedFilterDropdownTextInput } from '@/object-record/advanced-filter/components/AdvancedFilterDropdownTextInput';
|
||||
import { AdvancedFilterValueInputDropdownButtonClickableSelect } from '@/object-record/advanced-filter/components/AdvancedFilterValueInputDropdownButtonClickableSelect';
|
||||
import { DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET } from '@/object-record/advanced-filter/constants/DefaultAdvancedFilterDropdownOffset';
|
||||
import { ObjectFilterDropdownFilterInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput';
|
||||
import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState';
|
||||
import { NUMBER_FILTER_TYPES } from '@/object-record/object-filter-dropdown/constants/NumberFilterTypes';
|
||||
import { TEXT_FILTER_TYPES } from '@/object-record/object-filter-dropdown/constants/TextFilterTypes';
|
||||
import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState';
|
||||
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 { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownOffset } from '@/ui/layout/dropdown/types/DropdownOffset';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
|
||||
import styled from '@emotion/styled';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
const StyledValueDropdownContainer = styled.div`
|
||||
flex: 3;
|
||||
`;
|
||||
|
||||
type AdvancedFilterValueInputDropdownButtonProps = {
|
||||
type AdvancedFilterValueInputProps = {
|
||||
recordFilterId: string;
|
||||
};
|
||||
|
||||
export const AdvancedFilterValueInputDropdownButton = ({
|
||||
export const AdvancedFilterValueInput = ({
|
||||
recordFilterId,
|
||||
}: AdvancedFilterValueInputDropdownButtonProps) => {
|
||||
}: AdvancedFilterValueInputProps) => {
|
||||
const dropdownId = `advanced-filter-view-filter-value-input-${recordFilterId}`;
|
||||
|
||||
const currentRecordFilters = useRecoilComponentValueV2(
|
||||
currentRecordFiltersComponentState,
|
||||
);
|
||||
|
||||
const filter = currentRecordFilters.find(
|
||||
const recordFilter = currentRecordFilters.find(
|
||||
(recordFilter) => recordFilter.id === recordFilterId,
|
||||
);
|
||||
|
||||
const isDisabled = !filter?.fieldMetadataId || !filter.operand;
|
||||
const isDisabled = !recordFilter?.fieldMetadataId || !recordFilter.operand;
|
||||
|
||||
const setObjectFilterDropdownSearchInput = useSetRecoilComponentStateV2(
|
||||
objectFilterDropdownSearchInputComponentState,
|
||||
);
|
||||
|
||||
const setFieldMetadataItemIdUsedInDropdown = useSetRecoilComponentStateV2(
|
||||
fieldMetadataItemIdUsedInDropdownComponentState,
|
||||
);
|
||||
|
||||
const setSelectedOperandInDropdown = useSetRecoilComponentStateV2(
|
||||
selectedOperandInDropdownComponentState,
|
||||
);
|
||||
|
||||
const setSelectedFilter = useSetRecoilComponentStateV2(
|
||||
selectedFilterComponentState,
|
||||
);
|
||||
|
||||
const operandHasNoInput =
|
||||
filter && !configurableViewFilterOperands.has(filter.operand);
|
||||
recordFilter && !configurableViewFilterOperands.has(recordFilter.operand);
|
||||
|
||||
const handleFilterValueDropdownClose = () => {
|
||||
setObjectFilterDropdownSearchInput('');
|
||||
};
|
||||
|
||||
const filterType = recordFilter?.type;
|
||||
|
||||
const dropdownContentOffset =
|
||||
filterType === 'DATE' || filterType === 'DATE_TIME'
|
||||
? ({ y: -33, x: 0 } satisfies DropdownOffset)
|
||||
: DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET;
|
||||
|
||||
return (
|
||||
<StyledValueDropdownContainer>
|
||||
{operandHasNoInput ? (
|
||||
@ -67,6 +64,10 @@ export const AdvancedFilterValueInputDropdownButton = ({
|
||||
<AdvancedFilterValueInputDropdownButtonClickableSelect
|
||||
recordFilterId={recordFilterId}
|
||||
/>
|
||||
) : isDefined(filterType) &&
|
||||
(TEXT_FILTER_TYPES.includes(filterType) ||
|
||||
NUMBER_FILTER_TYPES.includes(filterType)) ? (
|
||||
<AdvancedFilterDropdownTextInput />
|
||||
) : (
|
||||
<Dropdown
|
||||
dropdownId={dropdownId}
|
||||
@ -75,16 +76,13 @@ export const AdvancedFilterValueInputDropdownButton = ({
|
||||
recordFilterId={recordFilterId}
|
||||
/>
|
||||
}
|
||||
onOpen={() => {
|
||||
setFieldMetadataItemIdUsedInDropdown(filter.fieldMetadataId);
|
||||
setSelectedOperandInDropdown(filter.operand);
|
||||
setSelectedFilter(filter);
|
||||
}}
|
||||
dropdownComponents={
|
||||
<ObjectFilterDropdownFilterInput recordFilterId={filter.id} />
|
||||
<AdvancedFilterDropdownFilterInput
|
||||
recordFilterId={recordFilter.id}
|
||||
/>
|
||||
}
|
||||
dropdownHotkeyScope={{ scope: dropdownId }}
|
||||
dropdownOffset={DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET}
|
||||
dropdownOffset={dropdownContentOffset}
|
||||
dropdownPlacement="bottom-start"
|
||||
dropdownMenuWidth={280}
|
||||
onClose={handleFilterValueDropdownClose}
|
||||
@ -3,10 +3,30 @@ import { getAdvancedFilterInputPlaceholderText } from '@/object-record/advanced-
|
||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||
import { SelectControl } from '@/ui/input/components/SelectControl';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
// TODO: factorize this with https://github.com/twentyhq/core-team-issues/issues/752
|
||||
const StyledControlContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
box-sizing: border-box;
|
||||
height: ${({ theme }) => theme.spacing(8)};
|
||||
max-width: 100%;
|
||||
padding: 0 ${({ theme }) => theme.spacing(2)};
|
||||
background-color: ${({ theme }) => theme.background.transparent.lighter};
|
||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
`;
|
||||
|
||||
type AdvancedFilterValueInputDropdownButtonClickableSelectProps = {
|
||||
recordFilterId: string;
|
||||
};
|
||||
@ -42,7 +62,12 @@ export const AdvancedFilterValueInputDropdownButtonClickableSelect = ({
|
||||
? placeholderText
|
||||
: (recordFilter?.displayValue ?? '');
|
||||
|
||||
return (
|
||||
const isDateTimeType =
|
||||
recordFilter?.type === 'DATE' || recordFilter?.type === 'DATE_TIME';
|
||||
|
||||
return isDateTimeType ? (
|
||||
<StyledControlContainer>{advancedFilterInputText}</StyledControlContainer>
|
||||
) : (
|
||||
<SelectControl
|
||||
selectedOption={{
|
||||
label: advancedFilterInputText,
|
||||
@ -50,6 +75,7 @@ export const AdvancedFilterValueInputDropdownButtonClickableSelect = ({
|
||||
disabled: isDisabled,
|
||||
}}
|
||||
textAccent={shouldUsePlaceholder ? 'placeholder' : 'default'}
|
||||
isDisabled={isDateTimeType}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@ -0,0 +1,47 @@
|
||||
import { getAdvancedFilterObjectFilterDropdownComponentInstanceId } from '@/object-record/advanced-filter/utils/getAdvancedFilterObjectFilterDropdownComponentInstanceId';
|
||||
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 { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
export const useSetRecordFilterUsedInAdvancedFilterDropdownRow = () => {
|
||||
const setRecordFilterUsedInAdvancedFilterDropdownRow = useRecoilCallback(
|
||||
({ set }) =>
|
||||
(recordFilter: RecordFilter) => {
|
||||
const advancedFilterRowObjectFilterDropdownComponentInstanceId =
|
||||
getAdvancedFilterObjectFilterDropdownComponentInstanceId(
|
||||
recordFilter.id,
|
||||
);
|
||||
|
||||
set(
|
||||
fieldMetadataItemIdUsedInDropdownComponentState.atomFamily({
|
||||
instanceId:
|
||||
advancedFilterRowObjectFilterDropdownComponentInstanceId,
|
||||
}),
|
||||
recordFilter.fieldMetadataId,
|
||||
);
|
||||
|
||||
set(
|
||||
selectedOperandInDropdownComponentState.atomFamily({
|
||||
instanceId:
|
||||
advancedFilterRowObjectFilterDropdownComponentInstanceId,
|
||||
}),
|
||||
recordFilter.operand,
|
||||
);
|
||||
|
||||
set(
|
||||
selectedFilterComponentState.atomFamily({
|
||||
instanceId:
|
||||
advancedFilterRowObjectFilterDropdownComponentInstanceId,
|
||||
}),
|
||||
recordFilter,
|
||||
);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return {
|
||||
setRecordFilterUsedInAdvancedFilterDropdownRow,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,5 @@
|
||||
export const getAdvancedFilterObjectFilterDropdownComponentInstanceId = (
|
||||
recordFilterId: string,
|
||||
) => {
|
||||
return `advanced-filter-${recordFilterId}`;
|
||||
};
|
||||
@ -8,6 +8,8 @@ import { RecordFilterGroupLogicalOperator } from '@/object-record/record-filter-
|
||||
import { useUpsertRecordFilter } from '@/object-record/record-filter/hooks/useUpsertRecordFilter';
|
||||
import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands';
|
||||
|
||||
import { useSetRecordFilterUsedInAdvancedFilterDropdownRow } from '@/object-record/advanced-filter/hooks/useSetRecordFilterUsedInAdvancedFilterDropdownRow';
|
||||
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { ADVANCED_FILTER_DROPDOWN_ID } from '@/views/constants/AdvancedFilterDropdownId';
|
||||
@ -16,11 +18,11 @@ import { ViewFilterGroupLogicalOperator } from '@/views/types/ViewFilterGroupLog
|
||||
import styled from '@emotion/styled';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { v4 } from 'uuid';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Pill } from 'twenty-ui/components';
|
||||
import { IconFilter } from 'twenty-ui/display';
|
||||
import { MenuItemLeftContent, StyledMenuItemBase } from 'twenty-ui/navigation';
|
||||
import { Pill } from 'twenty-ui/components';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
export const StyledContainer = styled.div`
|
||||
align-items: center;
|
||||
@ -80,6 +82,9 @@ export const AdvancedFilterButton = () => {
|
||||
currentRecordFilterGroupsComponentState,
|
||||
);
|
||||
|
||||
const { setRecordFilterUsedInAdvancedFilterDropdownRow } =
|
||||
useSetRecordFilterUsedInAdvancedFilterDropdownRow();
|
||||
|
||||
const handleClick = () => {
|
||||
if (!isDefined(currentView)) {
|
||||
throw new Error('Missing current view id');
|
||||
@ -118,7 +123,7 @@ export const AdvancedFilterButton = () => {
|
||||
filterType,
|
||||
})[0];
|
||||
|
||||
upsertRecordFilter({
|
||||
const newRecordFilter: RecordFilter = {
|
||||
id: v4(),
|
||||
fieldMetadataId: defaultFieldMetadataItem.id,
|
||||
operand: firstOperand,
|
||||
@ -128,7 +133,11 @@ export const AdvancedFilterButton = () => {
|
||||
type: getFilterTypeFromFieldType(defaultFieldMetadataItem.type),
|
||||
label: defaultFieldMetadataItem.label,
|
||||
positionInRecordFilterGroup: 1,
|
||||
});
|
||||
};
|
||||
|
||||
upsertRecordFilter(newRecordFilter);
|
||||
|
||||
setRecordFilterUsedInAdvancedFilterDropdownRow(newRecordFilter);
|
||||
}
|
||||
|
||||
closeObjectFilterDropdown();
|
||||
|
||||
@ -7,6 +7,7 @@ import { SelectOption } from 'twenty-ui/input';
|
||||
|
||||
export type SelectControlTextAccent = 'default' | 'placeholder';
|
||||
|
||||
// TODO: factorize this with https://github.com/twentyhq/core-team-issues/issues/752
|
||||
const StyledControlContainer = styled.div<{
|
||||
disabled?: boolean;
|
||||
hasIcon: boolean;
|
||||
|
||||
Reference in New Issue
Block a user