50cb32d122
This PR implements sub-field filtering on CURRENCY field type and improves many related zones. - Created a ObjectFilterDropdownCurrencySelect dropdown component for filtering on multiple currencies - Added currencyCode sub-field to CurrencyFilter type - Created getDefaultSubFieldNameForCompositeFilterableFieldType to avoid situation where we don't have any sub field name in sub field filtering situations. - Implemented filtering for currencyCode in computeFilterRecordGqlOperationFilter - Implemented CURRENCY type in getRecordFilterOperands - Implemented isMatchingCurrencyFilter for using in isRecordMatchingFilter for proper optimistic rendering - Created turnCurrencyIntoSelectableItem to help ObjectFilterDropdownCurrencySelect Testing : - Added test for currency sub fields in getOperandsForFilterType - Completely reworked isMatchingCurrencyFilter test Improvements : - Created a unique CURRENCIES constant to avoid re-creating it at various places - Derive the type FilterableFieldType from a constant array FILTERABLE_FIELD_TYPES, so it's easier to work with - Added areCompositeTypeSubFieldsFilterable - Fixed a bug with empty value '[]' that was preventing the auto-removal of a filter chip Miscellaneous : - Created isExpectedSubFieldName util to do a type-safe check of a subFieldName - Better naming : renamed isCompositeField to isCompositeFieldType - Created isCompositeTypeFilterableWithAny to specify which field types are filterable by any sub field - Better naming : renamed ObjectFilterDropdownFilterSelectCompositeFieldSubMenu to ObjectFilterDropdownSubFieldSelect - Better naming : renamed ObjectFilterDropdownFilterSelect to ObjectFilterDropdownFieldSelect - Created isEmptinessOperand util instead of duplicating the same hard-coded check in multiple places - Better naming : used subFieldName instead of compositeFieldName for consistency - UseEffect removal : removed unnecessary useEffect in MultipleSelectDropdown Fixes a bug where Empty and Not weren't appearing in filter chip in particular cases Fixes https://github.com/twentyhq/core-team-issues/issues/498 Fixes https://github.com/twentyhq/twenty/issues/7558
143 lines
4.4 KiB
TypeScript
143 lines
4.4 KiB
TypeScript
import { useState } from 'react';
|
|
import { ReadonlyDeep } from 'type-fest';
|
|
|
|
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
|
import { isCompositeFieldType } from '@/object-record/object-filter-dropdown/utils/isCompositeFieldType';
|
|
import { getSubFieldOptionKey } from '@/object-record/spreadsheet-import/utils/getSubFieldOptionKey';
|
|
import { MatchColumnSelectFieldSelectDropdownContent } from '@/spreadsheet-import/components/MatchColumnSelectFieldSelectDropdownContent';
|
|
import { MatchColumnSelectSubFieldSelectDropdownContent } from '@/spreadsheet-import/components/MatchColumnSelectSubFieldSelectDropdownContent';
|
|
import { DO_NOT_IMPORT_OPTION_KEY } from '@/spreadsheet-import/constants/DoNotImportOptionKey';
|
|
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
|
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
|
import { isDefined } from 'twenty-shared/utils';
|
|
import { SelectOption } from 'twenty-ui/input';
|
|
import { MenuItem } from 'twenty-ui/navigation';
|
|
|
|
interface MatchColumnToFieldSelectProps {
|
|
columnIndex: string;
|
|
onChange: (value: ReadonlyDeep<SelectOption> | null) => void;
|
|
value?: ReadonlyDeep<SelectOption>;
|
|
options: readonly ReadonlyDeep<SelectOption>[];
|
|
placeholder?: string;
|
|
}
|
|
|
|
export const MatchColumnToFieldSelect = ({
|
|
onChange,
|
|
value,
|
|
options,
|
|
placeholder,
|
|
columnIndex,
|
|
}: MatchColumnToFieldSelectProps) => {
|
|
const dropdownId = `match-column-select-v2-dropdown-${columnIndex}`;
|
|
|
|
const { closeDropdown } = useDropdown(dropdownId);
|
|
|
|
const [selectedFieldMetadataItem, setSelectedFieldMetadataItem] =
|
|
useState<FieldMetadataItem | null>(null);
|
|
|
|
const handleFieldMetadataItemSelect = (
|
|
selectedFieldMetadataItem: FieldMetadataItem,
|
|
) => {
|
|
setSelectedFieldMetadataItem(selectedFieldMetadataItem);
|
|
|
|
if (!isCompositeFieldType(selectedFieldMetadataItem.type)) {
|
|
const correspondingOption = options.find(
|
|
(option) => option.value === selectedFieldMetadataItem.name,
|
|
);
|
|
|
|
if (isDefined(correspondingOption)) {
|
|
setSelectedFieldMetadataItem(null);
|
|
|
|
onChange(correspondingOption);
|
|
closeDropdown();
|
|
}
|
|
}
|
|
};
|
|
|
|
const handleSubFieldSelect = (subFieldNameSelected: string) => {
|
|
if (!isDefined(selectedFieldMetadataItem)) {
|
|
return;
|
|
}
|
|
|
|
const correspondingOption = options.find((option) => {
|
|
const optionKey = getSubFieldOptionKey(
|
|
selectedFieldMetadataItem,
|
|
subFieldNameSelected,
|
|
);
|
|
|
|
return option.value === optionKey;
|
|
});
|
|
|
|
if (isDefined(correspondingOption)) {
|
|
setSelectedFieldMetadataItem(null);
|
|
|
|
onChange(correspondingOption);
|
|
closeDropdown();
|
|
}
|
|
};
|
|
|
|
const handleDoNotImportSelect = () => {
|
|
if (isDefined(doNotImportOption)) {
|
|
onChange(doNotImportOption);
|
|
closeDropdown();
|
|
}
|
|
};
|
|
|
|
const handleClickOutside = () => {
|
|
setSelectedFieldMetadataItem(null);
|
|
};
|
|
|
|
const handleSubFieldBack = () => {
|
|
setSelectedFieldMetadataItem(null);
|
|
};
|
|
|
|
const handleCancelSelectClick = () => {
|
|
setSelectedFieldMetadataItem(null);
|
|
closeDropdown();
|
|
};
|
|
|
|
const doNotImportOption = options.find(
|
|
(option) => option.value === DO_NOT_IMPORT_OPTION_KEY,
|
|
);
|
|
|
|
const shouldDisplaySubFieldMetadataItemSelect =
|
|
isDefined(selectedFieldMetadataItem?.type) &&
|
|
isCompositeFieldType(selectedFieldMetadataItem?.type);
|
|
|
|
return (
|
|
<Dropdown
|
|
dropdownId={dropdownId}
|
|
dropdownHotkeyScope={{
|
|
scope: dropdownId,
|
|
}}
|
|
dropdownPlacement="bottom-start"
|
|
clickableComponent={
|
|
<MenuItem
|
|
LeftIcon={value?.Icon}
|
|
text={value?.label ?? placeholder ?? ''}
|
|
accent={value?.label ? 'default' : 'placeholder'}
|
|
/>
|
|
}
|
|
dropdownComponents={
|
|
shouldDisplaySubFieldMetadataItemSelect && selectedFieldMetadataItem ? (
|
|
<MatchColumnSelectSubFieldSelectDropdownContent
|
|
fieldMetadataItem={selectedFieldMetadataItem}
|
|
onSubFieldSelect={handleSubFieldSelect}
|
|
options={options}
|
|
onBack={handleSubFieldBack}
|
|
/>
|
|
) : (
|
|
<MatchColumnSelectFieldSelectDropdownContent
|
|
selectedValue={value}
|
|
onSelectFieldMetadataItem={handleFieldMetadataItemSelect}
|
|
onCancelSelect={handleCancelSelectClick}
|
|
onDoNotImportSelect={handleDoNotImportSelect}
|
|
options={options}
|
|
/>
|
|
)
|
|
}
|
|
onClickOutside={handleClickOutside}
|
|
/>
|
|
);
|
|
};
|