Select Field Input Menu scrollable and add Select Field in Filter and Sort (#3656)
* - fix Select Option Menu scrollable and added search - add select field in filter and sort operation * Fix lint * Fix post merge * Fix select filter * Fix * Remove duplicated search input * fix turn object into query * Rename search inputs * Remove debounced for options * Simplify option filter * Rename option to MenuItemSelectTag * Fix test * Infer type from field metadata item --------- Co-authored-by: Charles Bochet <charles@twenty.com> Co-authored-by: Thomas Trompette <thomast@twenty.com>
This commit is contained in:
@ -1,6 +1,14 @@
|
||||
import { ThemeColor } from '@/ui/theme/constants/MainColorNames';
|
||||
import { Field, Relation } from '~/generated-metadata/graphql';
|
||||
|
||||
export type FieldMetadataItemOption = {
|
||||
color: ThemeColor;
|
||||
id: string;
|
||||
label: string;
|
||||
position: number;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type FieldMetadataItem = Omit<
|
||||
Field,
|
||||
| '__typename'
|
||||
@ -27,11 +35,5 @@ export type FieldMetadataItem = Omit<
|
||||
})
|
||||
| null;
|
||||
defaultValue?: any;
|
||||
options?: {
|
||||
color: ThemeColor;
|
||||
id: string;
|
||||
label: string;
|
||||
position: number;
|
||||
value: string;
|
||||
}[];
|
||||
options?: FieldMetadataItemOption[];
|
||||
};
|
||||
|
||||
@ -18,6 +18,7 @@ export const formatFieldMetadataItemsAsFilterDefinitions = ({
|
||||
FieldMetadataType.Link,
|
||||
FieldMetadataType.FullName,
|
||||
FieldMetadataType.Relation,
|
||||
FieldMetadataType.Select,
|
||||
FieldMetadataType.Currency,
|
||||
].includes(field.type)
|
||||
) {
|
||||
@ -67,5 +68,7 @@ export const formatFieldMetadataItemAsFilterDefinition = ({
|
||||
? 'TEXT'
|
||||
: field.type === FieldMetadataType.Relation
|
||||
? 'RELATION'
|
||||
: 'TEXT',
|
||||
: field.type === FieldMetadataType.Select
|
||||
? 'SELECT'
|
||||
: 'TEXT',
|
||||
});
|
||||
|
||||
@ -15,6 +15,7 @@ export const formatFieldMetadataItemsAsSortDefinitions = ({
|
||||
FieldMetadataType.Number,
|
||||
FieldMetadataType.Text,
|
||||
FieldMetadataType.Boolean,
|
||||
FieldMetadataType.Select,
|
||||
].includes(field.type)
|
||||
) {
|
||||
return acc;
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
import { ObjectFilterDropdownRecordSearchInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownEntitySearchInput';
|
||||
import { ObjectFilterDropdownSearchInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownSearchInput';
|
||||
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
|
||||
import { MultipleFiltersDropdownFilterOnFilterChangedEffect } from './MultipleFiltersDropdownFilterOnFilterChangedEffect';
|
||||
import { ObjectFilterDropdownDateSearchInput } from './ObjectFilterDropdownDateSearchInput';
|
||||
import { ObjectFilterDropdownDateInput } from './ObjectFilterDropdownDateInput';
|
||||
import { ObjectFilterDropdownFilterSelect } from './ObjectFilterDropdownFilterSelect';
|
||||
import { ObjectFilterDropdownNumberSearchInput } from './ObjectFilterDropdownNumberSearchInput';
|
||||
import { ObjectFilterDropdownNumberInput } from './ObjectFilterDropdownNumberInput';
|
||||
import { ObjectFilterDropdownOperandButton } from './ObjectFilterDropdownOperandButton';
|
||||
import { ObjectFilterDropdownOperandSelect } from './ObjectFilterDropdownOperandSelect';
|
||||
import { ObjectFilterDropdownOptionSelect } from './ObjectFilterDropdownOptionSelect';
|
||||
import { ObjectFilterDropdownRecordSelect } from './ObjectFilterDropdownRecordSelect';
|
||||
import { ObjectFilterDropdownTextSearchInput } from './ObjectFilterDropdownTextSearchInput';
|
||||
|
||||
@ -40,17 +41,24 @@ export const MultipleFiltersDropdownContent = ({
|
||||
) && <ObjectFilterDropdownTextSearchInput />}
|
||||
{['NUMBER', 'CURRENCY'].includes(
|
||||
filterDefinitionUsedInDropdown.type,
|
||||
) && <ObjectFilterDropdownNumberSearchInput />}
|
||||
) && <ObjectFilterDropdownNumberInput />}
|
||||
{filterDefinitionUsedInDropdown.type === 'DATE_TIME' && (
|
||||
<ObjectFilterDropdownDateSearchInput />
|
||||
<ObjectFilterDropdownDateInput />
|
||||
)}
|
||||
{filterDefinitionUsedInDropdown.type === 'RELATION' && (
|
||||
<>
|
||||
<ObjectFilterDropdownRecordSearchInput />
|
||||
<ObjectFilterDropdownSearchInput />
|
||||
<DropdownMenuSeparator />
|
||||
<ObjectFilterDropdownRecordSelect />
|
||||
</>
|
||||
)}
|
||||
{filterDefinitionUsedInDropdown.type === 'SELECT' && (
|
||||
<>
|
||||
<ObjectFilterDropdownSearchInput />
|
||||
<DropdownMenuSeparator />
|
||||
<ObjectFilterDropdownOptionSelect />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
)}
|
||||
|
||||
@ -2,7 +2,7 @@ import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/
|
||||
import { InternalDatePicker } from '@/ui/input/components/internal/date/components/InternalDatePicker';
|
||||
import { isNonNullable } from '~/utils/isNonNullable';
|
||||
|
||||
export const ObjectFilterDropdownDateSearchInput = () => {
|
||||
export const ObjectFilterDropdownDateInput = () => {
|
||||
const {
|
||||
filterDefinitionUsedInDropdown,
|
||||
selectedOperandInDropdown,
|
||||
@ -1,9 +1,9 @@
|
||||
import { ChangeEvent } from 'react';
|
||||
|
||||
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
|
||||
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
||||
import { DropdownMenuInput } from '@/ui/layout/dropdown/components/DropdownMenuInput';
|
||||
|
||||
export const ObjectFilterDropdownNumberSearchInput = () => {
|
||||
export const ObjectFilterDropdownNumberInput = () => {
|
||||
const {
|
||||
selectedOperandInDropdown,
|
||||
filterDefinitionUsedInDropdown,
|
||||
@ -13,7 +13,7 @@ export const ObjectFilterDropdownNumberSearchInput = () => {
|
||||
return (
|
||||
filterDefinitionUsedInDropdown &&
|
||||
selectedOperandInDropdown && (
|
||||
<DropdownMenuSearchInput
|
||||
<DropdownMenuInput
|
||||
autoFocus
|
||||
type="number"
|
||||
placeholder={filterDefinitionUsedInDropdown.label}
|
||||
@ -0,0 +1,111 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { MenuItem, MenuItemMultiSelect } from 'tsup.ui.index';
|
||||
|
||||
import { FieldMetadataItemOption } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
|
||||
import { useOptionsForSelect } from '@/object-record/object-filter-dropdown/hooks/useOptionsForSelect';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
|
||||
export const EMPTY_FILTER_VALUE = '';
|
||||
export const MAX_OPTIONS_TO_DISPLAY = 3;
|
||||
|
||||
type SelectOptionForFilter = FieldMetadataItemOption & {
|
||||
isSelected: boolean;
|
||||
};
|
||||
|
||||
export const ObjectFilterDropdownOptionSelect = () => {
|
||||
const {
|
||||
filterDefinitionUsedInDropdown,
|
||||
objectFilterDropdownSearchInput,
|
||||
selectedOperandInDropdown,
|
||||
objectFilterDropdownSelectedOptionValues,
|
||||
selectFilter,
|
||||
} = useFilterDropdown();
|
||||
|
||||
const fieldMetaDataId = filterDefinitionUsedInDropdown?.fieldMetadataId ?? '';
|
||||
|
||||
const { selectOptions } = useOptionsForSelect(fieldMetaDataId);
|
||||
|
||||
const [selectableOptions, setSelectableOptions] = useState<
|
||||
SelectOptionForFilter[]
|
||||
>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectOptions) {
|
||||
const options = selectOptions.map((option) => {
|
||||
const isSelected =
|
||||
objectFilterDropdownSelectedOptionValues?.includes(option.value) ??
|
||||
false;
|
||||
|
||||
return {
|
||||
...option,
|
||||
isSelected,
|
||||
};
|
||||
});
|
||||
|
||||
setSelectableOptions(options);
|
||||
}
|
||||
}, [objectFilterDropdownSelectedOptionValues, selectOptions]);
|
||||
|
||||
const handleMultipleOptionSelectChange = (
|
||||
optionChanged: SelectOptionForFilter,
|
||||
isSelected: boolean,
|
||||
) => {
|
||||
if (!selectOptions) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newSelectableOptions = selectableOptions.map((option) =>
|
||||
option.id === optionChanged.id ? { ...option, isSelected } : option,
|
||||
);
|
||||
|
||||
setSelectableOptions(newSelectableOptions);
|
||||
|
||||
const selectedOptions = newSelectableOptions.filter(
|
||||
(option) => option.isSelected,
|
||||
);
|
||||
|
||||
const filterDisplayValue =
|
||||
selectedOptions.length > MAX_OPTIONS_TO_DISPLAY
|
||||
? `${selectedOptions.length} options`
|
||||
: selectedOptions.map((option) => option.label).join(', ');
|
||||
|
||||
if (filterDefinitionUsedInDropdown && selectedOperandInDropdown) {
|
||||
const newFilterValue =
|
||||
selectedOptions.length > 0
|
||||
? JSON.stringify(selectedOptions.map((option) => option.value))
|
||||
: EMPTY_FILTER_VALUE;
|
||||
|
||||
selectFilter({
|
||||
definition: filterDefinitionUsedInDropdown,
|
||||
operand: selectedOperandInDropdown,
|
||||
displayValue: filterDisplayValue,
|
||||
fieldMetadataId: filterDefinitionUsedInDropdown.fieldMetadataId,
|
||||
value: newFilterValue,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const optionsInDropdown = selectableOptions?.filter((option) =>
|
||||
option.label.toLowerCase().includes(objectFilterDropdownSearchInput),
|
||||
);
|
||||
|
||||
const showNoResult = optionsInDropdown?.length === 0;
|
||||
|
||||
return (
|
||||
<DropdownMenuItemsContainer hasMaxHeight>
|
||||
{optionsInDropdown?.map((option) => (
|
||||
<MenuItemMultiSelect
|
||||
key={option.id}
|
||||
selected={option.isSelected}
|
||||
onSelectChange={(selected) =>
|
||||
handleMultipleOptionSelectChange(option, selected)
|
||||
}
|
||||
text={option.label}
|
||||
className=""
|
||||
/>
|
||||
))}
|
||||
{showNoResult && <MenuItem text="No result" />}
|
||||
</DropdownMenuItemsContainer>
|
||||
);
|
||||
};
|
||||
@ -3,7 +3,7 @@ import { ChangeEvent } from 'react';
|
||||
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
|
||||
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
||||
|
||||
export const ObjectFilterDropdownRecordSearchInput = () => {
|
||||
export const ObjectFilterDropdownSearchInput = () => {
|
||||
const {
|
||||
filterDefinitionUsedInDropdown,
|
||||
selectedOperandInDropdown,
|
||||
@ -13,8 +13,8 @@ import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||
import { getOperandsForFilterType } from '../utils/getOperandsForFilterType';
|
||||
|
||||
import { GenericEntityFilterChip } from './GenericEntityFilterChip';
|
||||
import { ObjectFilterDropdownRecordSearchInput } from './ObjectFilterDropdownEntitySearchInput';
|
||||
import { ObjectFilterDropdownRecordSelect } from './ObjectFilterDropdownRecordSelect';
|
||||
import { ObjectFilterDropdownSearchInput } from './ObjectFilterDropdownSearchInput';
|
||||
|
||||
export const SingleEntityObjectFilterDropdownButton = ({
|
||||
hotkeyScope,
|
||||
@ -66,7 +66,7 @@ export const SingleEntityObjectFilterDropdownButton = ({
|
||||
}
|
||||
dropdownComponents={
|
||||
<>
|
||||
<ObjectFilterDropdownRecordSearchInput />
|
||||
<ObjectFilterDropdownSearchInput />
|
||||
<DropdownMenuSeparator />
|
||||
<ObjectFilterDropdownRecordRemoveFilterMenuItem />
|
||||
<ObjectFilterDropdownRecordSelect />
|
||||
|
||||
@ -27,6 +27,8 @@ export const useFilterDropdown = (props?: UseFilterDropdownProps) => {
|
||||
setObjectFilterDropdownSelectedEntityId,
|
||||
objectFilterDropdownSelectedRecordIds,
|
||||
setObjectFilterDropdownSelectedRecordIds,
|
||||
objectFilterDropdownSelectedOptionValues,
|
||||
setObjectFilterDropdownSelectedOptionValues,
|
||||
isObjectFilterDropdownOperandSelectUnfolded,
|
||||
setIsObjectFilterDropdownOperandSelectUnfolded,
|
||||
isObjectFilterDropdownUnfolded,
|
||||
@ -87,6 +89,8 @@ export const useFilterDropdown = (props?: UseFilterDropdownProps) => {
|
||||
setObjectFilterDropdownSelectedEntityId,
|
||||
objectFilterDropdownSelectedRecordIds,
|
||||
setObjectFilterDropdownSelectedRecordIds,
|
||||
objectFilterDropdownSelectedOptionValues,
|
||||
setObjectFilterDropdownSelectedOptionValues,
|
||||
isObjectFilterDropdownOperandSelectUnfolded,
|
||||
setIsObjectFilterDropdownOperandSelectUnfolded,
|
||||
isObjectFilterDropdownUnfolded,
|
||||
|
||||
@ -8,6 +8,7 @@ import { isObjectFilterDropdownOperandSelectUnfoldedScopedState } from '../state
|
||||
import { isObjectFilterDropdownUnfoldedScopedState } from '../states/isObjectFilterDropdownUnfoldedScopedState';
|
||||
import { objectFilterDropdownSearchInputScopedState } from '../states/objectFilterDropdownSearchInputScopedState';
|
||||
import { objectFilterDropdownSelectedEntityIdScopedState } from '../states/objectFilterDropdownSelectedEntityIdScopedState';
|
||||
import { objectFilterDropdownSelectedOptionValuesScopedState } from '../states/objectFilterDropdownSelectedOptionValuesScopedState';
|
||||
import { selectedFilterScopedState } from '../states/selectedFilterScopedState';
|
||||
import { selectedOperandInDropdownScopedState } from '../states/selectedOperandInDropdownScopedState';
|
||||
|
||||
@ -37,6 +38,14 @@ export const useFilterDropdownStates = (scopeId: string) => {
|
||||
scopeId,
|
||||
);
|
||||
|
||||
const [
|
||||
objectFilterDropdownSelectedOptionValues,
|
||||
setObjectFilterDropdownSelectedOptionValues,
|
||||
] = useRecoilScopedStateV2(
|
||||
objectFilterDropdownSelectedOptionValuesScopedState,
|
||||
scopeId,
|
||||
);
|
||||
|
||||
const [
|
||||
isObjectFilterDropdownOperandSelectUnfolded,
|
||||
setIsObjectFilterDropdownOperandSelectUnfolded,
|
||||
@ -71,6 +80,8 @@ export const useFilterDropdownStates = (scopeId: string) => {
|
||||
objectFilterDropdownSelectedEntityId,
|
||||
setObjectFilterDropdownSelectedEntityId,
|
||||
objectFilterDropdownSelectedRecordIds,
|
||||
objectFilterDropdownSelectedOptionValues,
|
||||
setObjectFilterDropdownSelectedOptionValues,
|
||||
setObjectFilterDropdownSelectedRecordIds,
|
||||
isObjectFilterDropdownOperandSelectUnfolded,
|
||||
setIsObjectFilterDropdownOperandSelectUnfolded,
|
||||
|
||||
@ -0,0 +1,26 @@
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
|
||||
|
||||
export const DEFAULT_SEARCH_REQUEST_LIMIT = 60;
|
||||
|
||||
export const useOptionsForSelect = (fieldMetadataId: string) => {
|
||||
const objectNamePlural = useParams().objectNamePlural ?? '';
|
||||
|
||||
const { objectNameSingular } = useObjectNameSingularFromPlural({
|
||||
objectNamePlural,
|
||||
});
|
||||
|
||||
const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular });
|
||||
|
||||
const fieldMetadataItem = objectMetadataItem.fields.find(
|
||||
(field) => field.id === fieldMetadataId,
|
||||
);
|
||||
|
||||
const selectOptions = fieldMetadataItem?.options;
|
||||
|
||||
return {
|
||||
selectOptions,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,7 @@
|
||||
import { createStateScopeMap } from '@/ui/utilities/recoil-scope/utils/createStateScopeMap';
|
||||
|
||||
export const objectFilterDropdownSelectedOptionValuesScopedState =
|
||||
createStateScopeMap<string[]>({
|
||||
key: 'objectFilterDropdownSelectedOptionValuesScopedState',
|
||||
defaultValue: [],
|
||||
});
|
||||
@ -7,4 +7,5 @@ export type FilterType =
|
||||
| 'CURRENCY'
|
||||
| 'FULL_NAME'
|
||||
| 'LINK'
|
||||
| 'RELATION';
|
||||
| 'RELATION'
|
||||
| 'SELECT';
|
||||
|
||||
@ -16,6 +16,7 @@ export const getOperandsForFilterType = (
|
||||
case 'DATE_TIME':
|
||||
return [ViewFilterOperand.GreaterThan, ViewFilterOperand.LessThan];
|
||||
case 'RELATION':
|
||||
case 'SELECT':
|
||||
return [ViewFilterOperand.Is, ViewFilterOperand.IsNot];
|
||||
default:
|
||||
return [];
|
||||
|
||||
@ -1,10 +1,13 @@
|
||||
import { useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { MenuItem } from 'tsup.ui.index';
|
||||
|
||||
import { useSelectField } from '@/object-record/record-field/meta-types/hooks/useSelectField';
|
||||
import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent';
|
||||
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { MenuItemSelectTag } from '@/ui/navigation/menu-item/components/MenuItemSelectTag';
|
||||
|
||||
const StyledRelationPickerContainer = styled.div`
|
||||
left: -1px;
|
||||
@ -17,16 +20,36 @@ export type SelectFieldInputProps = {
|
||||
};
|
||||
|
||||
export const SelectFieldInput = ({ onSubmit }: SelectFieldInputProps) => {
|
||||
const { persistField, fieldDefinition } = useSelectField();
|
||||
const { persistField, fieldDefinition, fieldValue } = useSelectField();
|
||||
const [searchFilter, setSearchFilter] = useState('');
|
||||
|
||||
const selectedOption = fieldDefinition.metadata.options.find(
|
||||
(option) => option.value === fieldValue,
|
||||
);
|
||||
const optionsToSelect =
|
||||
fieldDefinition.metadata.options.filter((option) => {
|
||||
return option.value !== fieldValue && option.label.includes(searchFilter);
|
||||
}) || [];
|
||||
const optionsInDropDown = selectedOption
|
||||
? [selectedOption, ...optionsToSelect]
|
||||
: optionsToSelect;
|
||||
|
||||
return (
|
||||
<StyledRelationPickerContainer>
|
||||
<DropdownMenu data-select-disable>
|
||||
<DropdownMenuItemsContainer>
|
||||
{fieldDefinition.metadata.options.map((option) => {
|
||||
<DropdownMenuSearchInput
|
||||
value={searchFilter}
|
||||
onChange={(event) => setSearchFilter(event.currentTarget.value)}
|
||||
autoFocus
|
||||
/>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer hasMaxHeight>
|
||||
{optionsInDropDown.map((option) => {
|
||||
return (
|
||||
<MenuItem
|
||||
<MenuItemSelectTag
|
||||
selected={option.value === fieldValue}
|
||||
text={option.label}
|
||||
color={option.color}
|
||||
onClick={() => onSubmit?.(() => persistField(option.value))}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -141,6 +141,7 @@ export const isRecordMatchingFilter = ({
|
||||
switch (objectMetadataField.type) {
|
||||
case FieldMetadataType.Email:
|
||||
case FieldMetadataType.Phone:
|
||||
case FieldMetadataType.Select:
|
||||
case FieldMetadataType.Text: {
|
||||
return isMatchingStringFilter({
|
||||
stringFilter: filterValue as StringFilter,
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
import {
|
||||
CurrencyFilter,
|
||||
DateFilter,
|
||||
@ -254,6 +256,48 @@ export const turnObjectDropdownFilterIntoQueryFilter = (
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'SELECT': {
|
||||
const stringifiedSelectValues = rawUIFilter.value;
|
||||
let parsedOptionValues: string[] = [];
|
||||
|
||||
if (!isNonEmptyString(stringifiedSelectValues)) {
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
parsedOptionValues = JSON.parse(stringifiedSelectValues);
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`Cannot parse filter value for SELECT filter : "${stringifiedSelectValues}"`,
|
||||
);
|
||||
}
|
||||
|
||||
if (parsedOptionValues.length > 0) {
|
||||
switch (rawUIFilter.operand) {
|
||||
case ViewFilterOperand.Is:
|
||||
objectRecordFilters.push({
|
||||
[correspondingField.name]: {
|
||||
in: parsedOptionValues,
|
||||
} as UUIDFilter,
|
||||
});
|
||||
break;
|
||||
case ViewFilterOperand.IsNot:
|
||||
objectRecordFilters.push({
|
||||
not: {
|
||||
[correspondingField.name]: {
|
||||
in: parsedOptionValues,
|
||||
} as UUIDFilter,
|
||||
},
|
||||
});
|
||||
break;
|
||||
default:
|
||||
throw new Error(
|
||||
`Unknown operand ${rawUIFilter.operand} for ${rawUIFilter.definition.type} filter`,
|
||||
);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Error('Unknown filter type');
|
||||
}
|
||||
|
||||
@ -0,0 +1,40 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { Tag } from 'tsup.ui.index';
|
||||
|
||||
import { IconCheck } from '@/ui/display/icon';
|
||||
import { ThemeColor } from '@/ui/theme/constants/MainColorNames';
|
||||
|
||||
import { StyledMenuItemLeftContent } from '../internals/components/StyledMenuItemBase';
|
||||
|
||||
import { StyledMenuItemSelect } from './MenuItemSelect';
|
||||
|
||||
type MenuItemSelectTagProps = {
|
||||
selected: boolean;
|
||||
className?: string;
|
||||
onClick?: () => void;
|
||||
color: ThemeColor;
|
||||
text: string;
|
||||
};
|
||||
|
||||
export const MenuItemSelectTag = ({
|
||||
color,
|
||||
selected,
|
||||
className,
|
||||
onClick,
|
||||
text,
|
||||
}: MenuItemSelectTagProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<StyledMenuItemSelect
|
||||
onClick={onClick}
|
||||
className={className}
|
||||
selected={selected}
|
||||
>
|
||||
<StyledMenuItemLeftContent>
|
||||
<Tag color={color} text={text} />
|
||||
</StyledMenuItemLeftContent>
|
||||
{selected && <IconCheck size={theme.icon.size.sm} />}
|
||||
</StyledMenuItemSelect>
|
||||
);
|
||||
};
|
||||
@ -26,6 +26,7 @@ export const ViewBarFilterEffect = ({
|
||||
setOnFilterSelect,
|
||||
filterDefinitionUsedInDropdown,
|
||||
setObjectFilterDropdownSelectedRecordIds,
|
||||
setObjectFilterDropdownSelectedOptionValues,
|
||||
isObjectFilterDropdownUnfolded,
|
||||
} = useFilterDropdown({ filterDropdownId });
|
||||
|
||||
@ -61,12 +62,29 @@ export const ViewBarFilterEffect = ({
|
||||
: [];
|
||||
|
||||
setObjectFilterDropdownSelectedRecordIds(viewFilterSelectedRecordIds);
|
||||
} else if (filterDefinitionUsedInDropdown?.type === 'SELECT') {
|
||||
const viewFilterUsedInDropdown = currentViewFilters.find(
|
||||
(filter) =>
|
||||
filter.fieldMetadataId ===
|
||||
filterDefinitionUsedInDropdown.fieldMetadataId,
|
||||
);
|
||||
|
||||
const viewFilterSelectedOptionValues = isNonEmptyString(
|
||||
viewFilterUsedInDropdown?.value,
|
||||
)
|
||||
? JSON.parse(viewFilterUsedInDropdown.value)
|
||||
: [];
|
||||
|
||||
setObjectFilterDropdownSelectedOptionValues(
|
||||
viewFilterSelectedOptionValues,
|
||||
);
|
||||
}
|
||||
}, [
|
||||
filterDefinitionUsedInDropdown,
|
||||
currentViewFilters,
|
||||
setObjectFilterDropdownSelectedRecordIds,
|
||||
isObjectFilterDropdownUnfolded,
|
||||
setObjectFilterDropdownSelectedOptionValues,
|
||||
]);
|
||||
|
||||
return <></>;
|
||||
|
||||
@ -82,5 +82,41 @@ describe('ArgsStringFactory', () => {
|
||||
'orderBy: [{id: AscNullsFirst}, {name: AscNullsFirst}]',
|
||||
);
|
||||
});
|
||||
|
||||
it('when orderBy is present with position criteria, should return position at the end of the list', () => {
|
||||
const args = {
|
||||
orderBy: {
|
||||
position: 'AscNullsFirst',
|
||||
id: 'AscNullsFirst',
|
||||
name: 'AscNullsFirst',
|
||||
},
|
||||
};
|
||||
|
||||
argsAliasCreate.mockReturnValue(args);
|
||||
|
||||
const result = service.create(args, []);
|
||||
|
||||
expect(result).toEqual(
|
||||
'orderBy: [{id: AscNullsFirst}, {name: AscNullsFirst}, {position: AscNullsFirst}]',
|
||||
);
|
||||
});
|
||||
|
||||
it('when orderBy is present with position in the middle, should return position at the end of the list', () => {
|
||||
const args = {
|
||||
orderBy: {
|
||||
id: 'AscNullsFirst',
|
||||
position: 'AscNullsFirst',
|
||||
name: 'AscNullsFirst',
|
||||
},
|
||||
};
|
||||
|
||||
argsAliasCreate.mockReturnValue(args);
|
||||
|
||||
const result = service.create(args, []);
|
||||
|
||||
expect(result).toEqual(
|
||||
'orderBy: [{id: AscNullsFirst}, {name: AscNullsFirst}, {position: AscNullsFirst}]',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -62,6 +62,9 @@ export class ArgsStringFactory {
|
||||
// PgGraphql is expecting the orderBy argument to be an array of objects
|
||||
if (key === 'orderBy') {
|
||||
const orderByString = Object.keys(obj)
|
||||
.sort((_, b) => {
|
||||
return b === 'position' ? -1 : 0;
|
||||
})
|
||||
.map((orderByKey) => `{${orderByKey}: ${obj[orderByKey]}}`)
|
||||
.join(', ');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user