Refactored editable filter chip dropdown opening (#11765)
This PR is refactoring a part of the ongoing filter refactor that was blocking other refactor in that area. Precisely, the dropdown filter that was used with the editable filter chip was initialized by two conflicting useEffect, causing many unwanted and hard to tackle bugs when modifying other places in the code that used the same dropdown. We also remove a difficult to maintain pattern around onToggleColumnFilterComponentState, which was storing a click handler in a state, we want to avoid this pattern. The hook useHandleToggleColumnFilter is also removed and replaced by useOpenRecordFilterChipFromTableHeader. The code is now synchronous and starts from the user click event that is triggered on a table cell header filter button click. Also : - Created a useSetEditableFilterChipDropdownStates that allows to separate the code path of filter chip dropdown from the code path of view bar global filter dropdown (will be continued in another refactor) - Added useCreateEmptyFilterFromFieldMetadataItem to abstract empty filter creation when opening a filter dropdown (will be used for other refactor) - Created a useOpenDropdownFromOutside hook that will also be used for other refactor on filter - Deleted EditableFilterDropdownButtonEffect - Removed call to ViewBarFilterEffect (will be completely removed in other refactors)
This commit is contained in:
@ -13,11 +13,13 @@ import { useIcons } from 'twenty-ui/display';
|
||||
type EditableFilterChipProps = {
|
||||
recordFilter: RecordFilter;
|
||||
onRemove: () => void;
|
||||
onClick?: () => void;
|
||||
};
|
||||
|
||||
export const EditableFilterChip = ({
|
||||
recordFilter,
|
||||
onRemove,
|
||||
onClick,
|
||||
}: EditableFilterChipProps) => {
|
||||
const { getIcon } = useIcons();
|
||||
|
||||
@ -58,6 +60,7 @@ export const EditableFilterChip = ({
|
||||
labelValue={recordFilter.displayValue}
|
||||
Icon={FieldMetadataItemIcon}
|
||||
onRemove={onRemove}
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@ -9,7 +9,7 @@ import { EditableFilterChip } from '@/views/components/EditableFilterChip';
|
||||
import { ObjectFilterOperandSelectAndInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterOperandSelectAndInput';
|
||||
import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRemoveRecordFilter';
|
||||
import { isRecordFilterConsideredEmpty } from '@/object-record/record-filter/utils/isRecordFilterConsideredEmpty';
|
||||
import { EditableFilterDropdownButtonEffect } from '@/views/components/EditableFilterDropdownButtonEffect';
|
||||
import { useSetEditableFilterChipDropdownStates } from '@/views/hooks/useSetEditableFilterChipDropdownStates';
|
||||
|
||||
type EditableFilterDropdownButtonProps = {
|
||||
recordFilter: RecordFilter;
|
||||
@ -38,15 +38,22 @@ export const EditableFilterDropdownButton = ({
|
||||
}
|
||||
}, [recordFilter, removeRecordFilter]);
|
||||
|
||||
const { setEditableFilterChipDropdownStates } =
|
||||
useSetEditableFilterChipDropdownStates();
|
||||
|
||||
const handleFilterChipClick = () => {
|
||||
setEditableFilterChipDropdownStates(recordFilter);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<EditableFilterDropdownButtonEffect recordFilter={recordFilter} />
|
||||
<Dropdown
|
||||
dropdownId={recordFilter.id}
|
||||
clickableComponent={
|
||||
<EditableFilterChip
|
||||
recordFilter={recordFilter}
|
||||
onRemove={handleRemove}
|
||||
onClick={handleFilterChipClick}
|
||||
/>
|
||||
}
|
||||
dropdownComponents={
|
||||
|
||||
@ -1,81 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState';
|
||||
|
||||
import { objectFilterDropdownSelectedRecordIdsComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSelectedRecordIdsComponentState';
|
||||
import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState';
|
||||
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
|
||||
import { subFieldNameUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState';
|
||||
import { useFilterableFieldMetadataItemsInRecordIndexContext } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItemsInRecordIndexContext';
|
||||
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
type EditableFilterDropdownButtonEffectProps = {
|
||||
recordFilter: RecordFilter;
|
||||
};
|
||||
|
||||
export const EditableFilterDropdownButtonEffect = ({
|
||||
recordFilter,
|
||||
}: EditableFilterDropdownButtonEffectProps) => {
|
||||
const setFieldMetadataItemIdUsedInDropdown = useSetRecoilComponentStateV2(
|
||||
fieldMetadataItemIdUsedInDropdownComponentState,
|
||||
);
|
||||
|
||||
const setSelectedOperandInDropdown = useSetRecoilComponentStateV2(
|
||||
selectedOperandInDropdownComponentState,
|
||||
recordFilter.id,
|
||||
);
|
||||
|
||||
const setSubFieldNameUsedInDropdown = useSetRecoilComponentStateV2(
|
||||
subFieldNameUsedInDropdownComponentState,
|
||||
recordFilter.id,
|
||||
);
|
||||
|
||||
const setSelectedFilter = useSetRecoilComponentStateV2(
|
||||
selectedFilterComponentState,
|
||||
recordFilter.id,
|
||||
);
|
||||
|
||||
const setObjectFilterDropdownSelectedRecordIds = useSetRecoilComponentStateV2(
|
||||
objectFilterDropdownSelectedRecordIdsComponentState,
|
||||
recordFilter.id,
|
||||
);
|
||||
|
||||
const { filterableFieldMetadataItems } =
|
||||
useFilterableFieldMetadataItemsInRecordIndexContext();
|
||||
|
||||
useEffect(() => {
|
||||
const fieldMetadataItem = filterableFieldMetadataItems.find(
|
||||
(fieldMetadataItem) =>
|
||||
fieldMetadataItem.id === recordFilter.fieldMetadataId,
|
||||
);
|
||||
|
||||
if (!isDefined(fieldMetadataItem)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setFieldMetadataItemIdUsedInDropdown(fieldMetadataItem.id);
|
||||
setSelectedOperandInDropdown(recordFilter.operand);
|
||||
setSelectedFilter(recordFilter);
|
||||
setSubFieldNameUsedInDropdown(recordFilter.subFieldName);
|
||||
|
||||
try {
|
||||
const selectedOptions = JSON.parse(recordFilter.value);
|
||||
|
||||
setObjectFilterDropdownSelectedRecordIds(selectedOptions);
|
||||
} catch {
|
||||
setObjectFilterDropdownSelectedRecordIds([]);
|
||||
}
|
||||
}, [
|
||||
filterableFieldMetadataItems,
|
||||
setFieldMetadataItemIdUsedInDropdown,
|
||||
recordFilter,
|
||||
setSelectedOperandInDropdown,
|
||||
setSelectedFilter,
|
||||
setSubFieldNameUsedInDropdown,
|
||||
setObjectFilterDropdownSelectedRecordIds,
|
||||
]);
|
||||
|
||||
return null;
|
||||
};
|
||||
@ -10,7 +10,6 @@ import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/
|
||||
import { AdvancedFilterDropdownButton } from '@/views/components/AdvancedFilterDropdownButton';
|
||||
import { EditableFilterDropdownButton } from '@/views/components/EditableFilterDropdownButton';
|
||||
import { EditableSortChip } from '@/views/components/EditableSortChip';
|
||||
import { ViewBarFilterEffect } from '@/views/components/ViewBarFilterEffect';
|
||||
import { useViewFromQueryParams } from '@/views/hooks/internal/useViewFromQueryParams';
|
||||
|
||||
import { useCheckIsSoftDeleteFilter } from '@/object-record/record-filter/hooks/useCheckIsSoftDeleteFilter';
|
||||
@ -232,7 +231,6 @@ export const ViewBarDetails = ({
|
||||
value={{ instanceId: recordFilter.id }}
|
||||
>
|
||||
<DropdownScope dropdownScopeId={recordFilter.id}>
|
||||
<ViewBarFilterEffect filterDropdownId={recordFilter.id} />
|
||||
<EditableFilterDropdownButton
|
||||
recordFilter={recordFilter}
|
||||
hotkeyScope={{
|
||||
|
||||
@ -0,0 +1,104 @@
|
||||
import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState';
|
||||
import { objectFilterDropdownSelectedOptionValuesComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSelectedOptionValuesComponentState';
|
||||
import { objectFilterDropdownSelectedRecordIdsComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSelectedRecordIdsComponentState';
|
||||
import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState';
|
||||
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
|
||||
import { subFieldNameUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState';
|
||||
import { useFilterableFieldMetadataItemsInRecordIndexContext } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItemsInRecordIndexContext';
|
||||
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||
import { jsonRelationFilterValueSchema } from '@/views/view-filter-value/validation-schemas/jsonRelationFilterValueSchema';
|
||||
import { simpleRelationFilterValueSchema } from '@/views/view-filter-value/validation-schemas/simpleRelationFilterValueSchema';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const useSetEditableFilterChipDropdownStates = () => {
|
||||
const { filterableFieldMetadataItems } =
|
||||
useFilterableFieldMetadataItemsInRecordIndexContext();
|
||||
|
||||
const setEditableFilterChipDropdownStates = useRecoilCallback(
|
||||
({ set }) =>
|
||||
(recordFilter: RecordFilter) => {
|
||||
const fieldMetadataItem = filterableFieldMetadataItems.find(
|
||||
(fieldMetadataItem) =>
|
||||
fieldMetadataItem.id === recordFilter.fieldMetadataId,
|
||||
);
|
||||
|
||||
if (!isDefined(fieldMetadataItem)) {
|
||||
return;
|
||||
}
|
||||
|
||||
set(
|
||||
fieldMetadataItemIdUsedInDropdownComponentState.atomFamily({
|
||||
instanceId: recordFilter.id,
|
||||
}),
|
||||
fieldMetadataItem.id,
|
||||
);
|
||||
|
||||
set(
|
||||
selectedOperandInDropdownComponentState.atomFamily({
|
||||
instanceId: recordFilter.id,
|
||||
}),
|
||||
recordFilter.operand,
|
||||
);
|
||||
|
||||
set(
|
||||
selectedFilterComponentState.atomFamily({
|
||||
instanceId: recordFilter.id,
|
||||
}),
|
||||
recordFilter,
|
||||
);
|
||||
|
||||
set(
|
||||
subFieldNameUsedInDropdownComponentState.atomFamily({
|
||||
instanceId: recordFilter.id,
|
||||
}),
|
||||
recordFilter.subFieldName,
|
||||
);
|
||||
|
||||
if (recordFilter.type === 'RELATION') {
|
||||
const { selectedRecordIds } = jsonRelationFilterValueSchema
|
||||
.catch({
|
||||
isCurrentWorkspaceMemberSelected: false,
|
||||
selectedRecordIds: simpleRelationFilterValueSchema.parse(
|
||||
recordFilter.value,
|
||||
),
|
||||
})
|
||||
.parse(recordFilter.value);
|
||||
|
||||
set(
|
||||
objectFilterDropdownSelectedRecordIdsComponentState.atomFamily({
|
||||
instanceId: recordFilter.id,
|
||||
}),
|
||||
selectedRecordIds,
|
||||
);
|
||||
} else if (['SELECT', 'MULTI_SELECT'].includes(recordFilter.type)) {
|
||||
try {
|
||||
const selectedOptions = JSON.parse(recordFilter.value);
|
||||
|
||||
set(
|
||||
objectFilterDropdownSelectedOptionValuesComponentState.atomFamily(
|
||||
{
|
||||
instanceId: recordFilter.id,
|
||||
},
|
||||
),
|
||||
selectedOptions,
|
||||
);
|
||||
} catch {
|
||||
set(
|
||||
objectFilterDropdownSelectedOptionValuesComponentState.atomFamily(
|
||||
{
|
||||
instanceId: recordFilter.id,
|
||||
},
|
||||
),
|
||||
[],
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
[filterableFieldMetadataItems],
|
||||
);
|
||||
|
||||
return {
|
||||
setEditableFilterChipDropdownStates,
|
||||
};
|
||||
};
|
||||
@ -9,7 +9,7 @@ export const areViewFilterGroupsEqual = (
|
||||
'positionInViewFilterGroup',
|
||||
'logicalOperator',
|
||||
'parentViewFilterGroupId',
|
||||
'viewId',
|
||||
'id',
|
||||
];
|
||||
|
||||
return propertiesToCompare.every((property) =>
|
||||
|
||||
Reference in New Issue
Block a user