mirror of
https://github.com/lingble/twenty.git
synced 2025-10-30 20:27:55 +00:00
Ability to filter by composite's subfields (#6832)
# This PR - Fix #6425 See https://github.com/twentyhq/twenty/issues/7188 because there's some more work to do. --------- Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
committed by
GitHub
parent
af4f3cebb0
commit
4156d7821c
@@ -6,6 +6,7 @@ import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
|||||||
import { ObjectFilterDropdownRecordSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect';
|
import { ObjectFilterDropdownRecordSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect';
|
||||||
import { ObjectFilterDropdownSourceSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownSourceSelect';
|
import { ObjectFilterDropdownSourceSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownSourceSelect';
|
||||||
import { ObjectFilterDropdownTextSearchInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownTextSearchInput';
|
import { ObjectFilterDropdownTextSearchInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownTextSearchInput';
|
||||||
|
import { isActorSourceCompositeFilter } from '@/object-record/object-filter-dropdown/utils/isActorSourceCompositeFilter';
|
||||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
@@ -98,9 +99,10 @@ export const MultipleFiltersDropdownContent = ({
|
|||||||
'ACTOR',
|
'ACTOR',
|
||||||
'ARRAY',
|
'ARRAY',
|
||||||
'PHONES',
|
'PHONES',
|
||||||
].includes(filterDefinitionUsedInDropdown.type) && (
|
].includes(filterDefinitionUsedInDropdown.type) &&
|
||||||
<ObjectFilterDropdownTextSearchInput />
|
!isActorSourceCompositeFilter(
|
||||||
)}
|
filterDefinitionUsedInDropdown,
|
||||||
|
) && <ObjectFilterDropdownTextSearchInput />}
|
||||||
{['NUMBER', 'CURRENCY'].includes(
|
{['NUMBER', 'CURRENCY'].includes(
|
||||||
filterDefinitionUsedInDropdown.type,
|
filterDefinitionUsedInDropdown.type,
|
||||||
) && <ObjectFilterDropdownNumberInput />}
|
) && <ObjectFilterDropdownNumberInput />}
|
||||||
@@ -116,7 +118,7 @@ export const MultipleFiltersDropdownContent = ({
|
|||||||
<ObjectFilterDropdownRecordSelect />
|
<ObjectFilterDropdownRecordSelect />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{filterDefinitionUsedInDropdown.type === 'SOURCE' && (
|
{isActorSourceCompositeFilter(filterDefinitionUsedInDropdown) && (
|
||||||
<>
|
<>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<ObjectFilterDropdownSourceSelect />
|
<ObjectFilterDropdownSourceSelect />
|
||||||
|
|||||||
@@ -1,16 +1,27 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useEffect, useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { ObjectFilterSelectMenu } from '@/object-record/object-filter-dropdown/components/ObjectFilterSelectMenu';
|
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||||
import { ObjectFilterSelectSubMenu } from '@/object-record/object-filter-dropdown/components/ObjectFilterSelectSubMenu';
|
|
||||||
|
import { ObjectFilterDropdownFilterSelectCompositeFieldSubMenu } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectCompositeFieldSubMenu';
|
||||||
import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdown/constants/ObjectFilterDropdownId';
|
import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdown/constants/ObjectFilterDropdownId';
|
||||||
|
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
|
||||||
import { useSelectFilter } from '@/object-record/object-filter-dropdown/hooks/useSelectFilter';
|
import { useSelectFilter } from '@/object-record/object-filter-dropdown/hooks/useSelectFilter';
|
||||||
import { currentSubMenuState } from '@/object-record/object-filter-dropdown/states/subMenuStates';
|
import { CompositeFilterableFieldType } from '@/object-record/object-filter-dropdown/types/CompositeFilterableFieldType';
|
||||||
|
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
|
||||||
|
import { FiltersHotkeyScope } from '@/object-record/object-filter-dropdown/types/FiltersHotkeyScope';
|
||||||
|
import { isCompositeField } from '@/object-record/object-filter-dropdown/utils/isCompositeField';
|
||||||
|
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
|
||||||
|
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
|
||||||
|
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||||
|
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||||
|
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
|
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { isDefined } from 'twenty-ui';
|
import { isDefined, useIcons } from 'twenty-ui';
|
||||||
|
import { getOperandsForFilterDefinition } from '../utils/getOperandsForFilterType';
|
||||||
|
|
||||||
export const StyledInput = styled.input`
|
export const StyledInput = styled.input`
|
||||||
background: transparent;
|
background: transparent;
|
||||||
@@ -39,19 +50,33 @@ export const StyledInput = styled.input`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const ObjectFilterDropdownFilterSelect = () => {
|
export const ObjectFilterDropdownFilterSelect = () => {
|
||||||
const [searchText, setSearchText] = useState('');
|
const [subMenuFieldType, setSubMenuFieldType] =
|
||||||
|
useState<CompositeFilterableFieldType | null>(null);
|
||||||
|
|
||||||
|
const [firstLevelFilterDefinition, setFirstLevelFilterDefinition] =
|
||||||
|
useState<FilterDefinition | null>(null);
|
||||||
|
|
||||||
|
const {
|
||||||
|
setFilterDefinitionUsedInDropdown,
|
||||||
|
setSelectedOperandInDropdown,
|
||||||
|
setObjectFilterDropdownSearchInput,
|
||||||
|
objectFilterDropdownSearchInputState,
|
||||||
|
} = useFilterDropdown();
|
||||||
|
|
||||||
|
const objectFilterDropdownSearchInput = useRecoilValue(
|
||||||
|
objectFilterDropdownSearchInputState,
|
||||||
|
);
|
||||||
|
|
||||||
const availableFilterDefinitions = useRecoilComponentValueV2(
|
const availableFilterDefinitions = useRecoilComponentValueV2(
|
||||||
availableFilterDefinitionsComponentState,
|
availableFilterDefinitionsComponentState,
|
||||||
);
|
);
|
||||||
|
|
||||||
const [currentSubMenu, setCurrentSubMenu] =
|
|
||||||
useRecoilState(currentSubMenuState);
|
|
||||||
|
|
||||||
const sortedAvailableFilterDefinitions = [...availableFilterDefinitions]
|
const sortedAvailableFilterDefinitions = [...availableFilterDefinitions]
|
||||||
.sort((a, b) => a.label.localeCompare(b.label))
|
.sort((a, b) => a.label.localeCompare(b.label))
|
||||||
.filter((item) =>
|
.filter((item) =>
|
||||||
item.label.toLocaleLowerCase().includes(searchText.toLocaleLowerCase()),
|
item.label
|
||||||
|
.toLocaleLowerCase()
|
||||||
|
.includes(objectFilterDropdownSearchInput.toLocaleLowerCase()),
|
||||||
);
|
);
|
||||||
|
|
||||||
const selectableListItemIds = sortedAvailableFilterDefinitions.map(
|
const selectableListItemIds = sortedAvailableFilterDefinitions.map(
|
||||||
@@ -76,21 +101,96 @@ export const ObjectFilterDropdownFilterSelect = () => {
|
|||||||
selectFilter({ filterDefinition: selectedFilterDefinition });
|
selectFilter({ filterDefinition: selectedFilterDefinition });
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const setHotkeyScope = useSetHotkeyScope();
|
||||||
return () => {
|
const { getIcon } = useIcons();
|
||||||
setCurrentSubMenu(null);
|
|
||||||
};
|
|
||||||
}, [setCurrentSubMenu]);
|
|
||||||
|
|
||||||
return !currentSubMenu ? (
|
const handleSelectFilter = (availableFilterDefinition: FilterDefinition) => {
|
||||||
<ObjectFilterSelectMenu
|
setFilterDefinitionUsedInDropdown(availableFilterDefinition);
|
||||||
searchText={searchText}
|
|
||||||
setSearchText={setSearchText}
|
if (
|
||||||
sortedAvailableFilterDefinitions={sortedAvailableFilterDefinitions}
|
availableFilterDefinition.type === 'RELATION' ||
|
||||||
selectableListItemIds={selectableListItemIds}
|
availableFilterDefinition.type === 'SELECT'
|
||||||
handleEnter={handleEnter}
|
) {
|
||||||
/>
|
setHotkeyScope(RelationPickerHotkeyScope.RelationPicker);
|
||||||
) : (
|
}
|
||||||
<ObjectFilterSelectSubMenu />
|
|
||||||
|
setSelectedOperandInDropdown(
|
||||||
|
getOperandsForFilterDefinition(availableFilterDefinition)[0],
|
||||||
|
);
|
||||||
|
|
||||||
|
setObjectFilterDropdownSearchInput('');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubMenuBack = () => {
|
||||||
|
setSubMenuFieldType(null);
|
||||||
|
setFirstLevelFilterDefinition(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const shouldShowFirstLevelMenu = !isDefined(subMenuFieldType);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{shouldShowFirstLevelMenu ? (
|
||||||
|
<>
|
||||||
|
<StyledInput
|
||||||
|
value={objectFilterDropdownSearchInput}
|
||||||
|
autoFocus
|
||||||
|
placeholder="Search fields"
|
||||||
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
|
setObjectFilterDropdownSearchInput(event.target.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<SelectableList
|
||||||
|
hotkeyScope={FiltersHotkeyScope.ObjectFilterDropdownButton}
|
||||||
|
selectableItemIdArray={selectableListItemIds}
|
||||||
|
selectableListId={OBJECT_FILTER_DROPDOWN_ID}
|
||||||
|
onEnter={handleEnter}
|
||||||
|
>
|
||||||
|
<DropdownMenuItemsContainer>
|
||||||
|
{[...availableFilterDefinitions]
|
||||||
|
.sort((a, b) => a.label.localeCompare(b.label))
|
||||||
|
.filter((item) =>
|
||||||
|
item.label
|
||||||
|
.toLocaleLowerCase()
|
||||||
|
.includes(
|
||||||
|
objectFilterDropdownSearchInput.toLocaleLowerCase(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.map((availableFilterDefinition, index) => (
|
||||||
|
<SelectableItem
|
||||||
|
itemId={availableFilterDefinition.fieldMetadataId}
|
||||||
|
>
|
||||||
|
<MenuItem
|
||||||
|
key={`select-filter-${index}`}
|
||||||
|
testId={`select-filter-${index}`}
|
||||||
|
onClick={() => {
|
||||||
|
if (isCompositeField(availableFilterDefinition.type)) {
|
||||||
|
setSubMenuFieldType(availableFilterDefinition.type);
|
||||||
|
setFirstLevelFilterDefinition(
|
||||||
|
availableFilterDefinition,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
handleSelectFilter(availableFilterDefinition);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
LeftIcon={getIcon(availableFilterDefinition.iconName)}
|
||||||
|
text={availableFilterDefinition.label}
|
||||||
|
hasSubMenu={isCompositeField(
|
||||||
|
availableFilterDefinition.type,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</SelectableItem>
|
||||||
|
))}
|
||||||
|
</DropdownMenuItemsContainer>
|
||||||
|
</SelectableList>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<ObjectFilterDropdownFilterSelectCompositeFieldSubMenu
|
||||||
|
fieldType={subMenuFieldType}
|
||||||
|
firstLevelFieldDefinition={firstLevelFilterDefinition}
|
||||||
|
onBack={handleSubMenuBack}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import { StyledInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelect';
|
||||||
|
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
|
||||||
|
import { CompositeFilterableFieldType } from '@/object-record/object-filter-dropdown/types/CompositeFilterableFieldType';
|
||||||
|
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
|
||||||
|
import { getCompositeSubFieldLabel } from '@/object-record/object-filter-dropdown/utils/getCompositeSubFieldLabel';
|
||||||
|
import { getFilterableFieldTypeLabel } from '@/object-record/object-filter-dropdown/utils/getFilterableFieldTypeLabel';
|
||||||
|
import { getOperandsForFilterDefinition } from '@/object-record/object-filter-dropdown/utils/getOperandsForFilterType';
|
||||||
|
import { SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsCompositeFieldTypeConfigs';
|
||||||
|
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader';
|
||||||
|
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||||
|
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { IconApps, IconChevronLeft, useIcons } from 'twenty-ui';
|
||||||
|
|
||||||
|
type ObjectFilterDropdownFilterSelectCompositeFieldSubMenuProps = {
|
||||||
|
fieldType: CompositeFilterableFieldType;
|
||||||
|
firstLevelFieldDefinition: FilterDefinition | null;
|
||||||
|
onBack: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ObjectFilterDropdownFilterSelectCompositeFieldSubMenu = ({
|
||||||
|
fieldType,
|
||||||
|
firstLevelFieldDefinition,
|
||||||
|
onBack,
|
||||||
|
}: ObjectFilterDropdownFilterSelectCompositeFieldSubMenuProps) => {
|
||||||
|
const [searchText, setSearchText] = useState('');
|
||||||
|
|
||||||
|
const { getIcon } = useIcons();
|
||||||
|
|
||||||
|
const {
|
||||||
|
setFilterDefinitionUsedInDropdown,
|
||||||
|
setSelectedOperandInDropdown,
|
||||||
|
setObjectFilterDropdownSearchInput,
|
||||||
|
} = useFilterDropdown();
|
||||||
|
|
||||||
|
const handleSelectFilter = (definition: FilterDefinition | null) => {
|
||||||
|
if (definition !== null) {
|
||||||
|
setFilterDefinitionUsedInDropdown(definition);
|
||||||
|
|
||||||
|
setSelectedOperandInDropdown(
|
||||||
|
getOperandsForFilterDefinition(definition)[0],
|
||||||
|
);
|
||||||
|
|
||||||
|
setObjectFilterDropdownSearchInput('');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const options = SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS[
|
||||||
|
fieldType
|
||||||
|
].filterableSubFields
|
||||||
|
.sort((a, b) => a.localeCompare(b))
|
||||||
|
.filter((item) =>
|
||||||
|
item.toLocaleLowerCase().includes(searchText.toLocaleLowerCase()),
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DropdownMenuHeader StartIcon={IconChevronLeft} onClick={onBack}>
|
||||||
|
{getFilterableFieldTypeLabel(fieldType)}
|
||||||
|
</DropdownMenuHeader>
|
||||||
|
<StyledInput
|
||||||
|
value={searchText}
|
||||||
|
autoFocus
|
||||||
|
placeholder="Search fields"
|
||||||
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
|
setSearchText(event.target.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<DropdownMenuItemsContainer>
|
||||||
|
<MenuItem
|
||||||
|
key={`select-filter-${-1}`}
|
||||||
|
testId={`select-filter-${-1}`}
|
||||||
|
onClick={() => {
|
||||||
|
handleSelectFilter(firstLevelFieldDefinition);
|
||||||
|
}}
|
||||||
|
LeftIcon={IconApps}
|
||||||
|
text={`Any ${getFilterableFieldTypeLabel(fieldType)} field`}
|
||||||
|
/>
|
||||||
|
{options.map((subFieldName, index) => (
|
||||||
|
<MenuItem
|
||||||
|
key={`select-filter-${index}`}
|
||||||
|
testId={`select-filter-${index}`}
|
||||||
|
onClick={() =>
|
||||||
|
firstLevelFieldDefinition &&
|
||||||
|
handleSelectFilter({
|
||||||
|
...firstLevelFieldDefinition,
|
||||||
|
label: getCompositeSubFieldLabel(fieldType, subFieldName),
|
||||||
|
compositeFieldName: subFieldName,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
text={getCompositeSubFieldLabel(fieldType, subFieldName)}
|
||||||
|
LeftIcon={getIcon(firstLevelFieldDefinition?.iconName)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</DropdownMenuItemsContainer>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,14 +1,10 @@
|
|||||||
import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdown/constants/ObjectFilterDropdownId';
|
import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdown/constants/ObjectFilterDropdownId';
|
||||||
import { useSelectFilter } from '@/object-record/object-filter-dropdown/hooks/useSelectFilter';
|
import { useSelectFilter } from '@/object-record/object-filter-dropdown/hooks/useSelectFilter';
|
||||||
import {
|
|
||||||
currentParentFilterDefinitionState,
|
|
||||||
currentSubMenuState,
|
|
||||||
} from '@/object-record/object-filter-dropdown/states/subMenuStates';
|
|
||||||
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
|
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
|
||||||
import { hasSubMenuFilter } from '@/object-record/object-filter-dropdown/utils/hasSubMenuFilter';
|
|
||||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||||
import { MenuItemSelect } from '@/ui/navigation/menu-item/components/MenuItemSelect';
|
import { MenuItemSelect } from '@/ui/navigation/menu-item/components/MenuItemSelect';
|
||||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { useIcons } from 'twenty-ui';
|
import { useIcons } from 'twenty-ui';
|
||||||
|
|
||||||
export type ObjectFilterDropdownFilterSelectMenuItemProps = {
|
export type ObjectFilterDropdownFilterSelectMenuItemProps = {
|
||||||
@@ -28,24 +24,12 @@ export const ObjectFilterDropdownFilterSelectMenuItem = ({
|
|||||||
isSelectedItemIdSelector(filterDefinition.fieldMetadataId),
|
isSelectedItemIdSelector(filterDefinition.fieldMetadataId),
|
||||||
);
|
);
|
||||||
|
|
||||||
const hasSubMenu = hasSubMenuFilter(filterDefinition.type);
|
|
||||||
|
|
||||||
const { getIcon } = useIcons();
|
const { getIcon } = useIcons();
|
||||||
|
|
||||||
const setCurrentSubMenu = useSetRecoilState(currentSubMenuState);
|
|
||||||
const setCurrentParentFilterDefinition = useSetRecoilState(
|
|
||||||
currentParentFilterDefinitionState,
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
resetSelectedItem();
|
resetSelectedItem();
|
||||||
|
|
||||||
if (hasSubMenu) {
|
selectFilter({ filterDefinition });
|
||||||
setCurrentSubMenu(filterDefinition.type);
|
|
||||||
setCurrentParentFilterDefinition(filterDefinition);
|
|
||||||
} else {
|
|
||||||
selectFilter({ filterDefinition });
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -55,7 +39,6 @@ export const ObjectFilterDropdownFilterSelectMenuItem = ({
|
|||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
LeftIcon={getIcon(filterDefinition.iconName)}
|
LeftIcon={getIcon(filterDefinition.iconName)}
|
||||||
text={filterDefinition.label}
|
text={filterDefinition.label}
|
||||||
hasSubMenu={hasSubMenu}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { isDefined } from '~/utils/isDefined';
|
|||||||
|
|
||||||
import { getInitialFilterValue } from '@/object-record/object-filter-dropdown/utils/getInitialFilterValue';
|
import { getInitialFilterValue } from '@/object-record/object-filter-dropdown/utils/getInitialFilterValue';
|
||||||
import { getOperandLabel } from '../utils/getOperandLabel';
|
import { getOperandLabel } from '../utils/getOperandLabel';
|
||||||
import { getOperandsForFilterType } from '../utils/getOperandsForFilterType';
|
import { getOperandsForFilterDefinition } from '../utils/getOperandsForFilterType';
|
||||||
|
|
||||||
export const ObjectFilterDropdownOperandSelect = () => {
|
export const ObjectFilterDropdownOperandSelect = () => {
|
||||||
const {
|
const {
|
||||||
@@ -31,9 +31,9 @@ export const ObjectFilterDropdownOperandSelect = () => {
|
|||||||
|
|
||||||
const selectedFilter = useRecoilValue(selectedFilterState);
|
const selectedFilter = useRecoilValue(selectedFilterState);
|
||||||
|
|
||||||
const operandsForFilterType = getOperandsForFilterType(
|
const operandsForFilterType = isDefined(filterDefinitionUsedInDropdown)
|
||||||
filterDefinitionUsedInDropdown?.type,
|
? getOperandsForFilterDefinition(filterDefinitionUsedInDropdown)
|
||||||
);
|
: [];
|
||||||
|
|
||||||
const handleOperandChange = (newOperand: ViewFilterOperand) => {
|
const handleOperandChange = (newOperand: ViewFilterOperand) => {
|
||||||
const isValuelessOperand = [
|
const isValuelessOperand = [
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useRecoilValue } from 'recoil';
|
|||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
|
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
|
||||||
import { getSourceEnumOptions } from '@/object-record/object-filter-dropdown/utils/getSourceEnumOptions';
|
import { getActorSourceMultiSelectOptions } from '@/object-record/object-filter-dropdown/utils/getActorSourceMultiSelectOptions';
|
||||||
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
|
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
|
||||||
import { MultipleSelectDropdown } from '@/object-record/select/components/MultipleSelectDropdown';
|
import { MultipleSelectDropdown } from '@/object-record/select/components/MultipleSelectDropdown';
|
||||||
import { SelectableItem } from '@/object-record/select/types/SelectableItem';
|
import { SelectableItem } from '@/object-record/select/types/SelectableItem';
|
||||||
@@ -55,7 +55,7 @@ export const ObjectFilterDropdownSourceSelect = ({
|
|||||||
|
|
||||||
const selectedFilter = useRecoilValue(selectedFilterState);
|
const selectedFilter = useRecoilValue(selectedFilterState);
|
||||||
|
|
||||||
const sourceTypes = getSourceEnumOptions(
|
const sourceTypes = getActorSourceMultiSelectOptions(
|
||||||
objectFilterDropdownSelectedRecordIds,
|
objectFilterDropdownSelectedRecordIds,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
import styled from '@emotion/styled';
|
|
||||||
|
|
||||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
|
||||||
|
|
||||||
import { ObjectFilterDropdownFilterSelectMenuItem } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectMenuItem';
|
|
||||||
import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdown/constants/ObjectFilterDropdownId';
|
|
||||||
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
|
|
||||||
import { FiltersHotkeyScope } from '@/object-record/object-filter-dropdown/types/FiltersHotkeyScope';
|
|
||||||
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
|
|
||||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
|
||||||
|
|
||||||
export const StyledInput = styled.input`
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
border-top: none;
|
|
||||||
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
|
|
||||||
border-radius: 0;
|
|
||||||
border-top-left-radius: ${({ theme }) => theme.border.radius.md};
|
|
||||||
border-top-right-radius: ${({ theme }) => theme.border.radius.md};
|
|
||||||
color: ${({ theme }) => theme.font.color.primary};
|
|
||||||
margin: 0;
|
|
||||||
outline: none;
|
|
||||||
padding: ${({ theme }) => theme.spacing(2)};
|
|
||||||
height: 19px;
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: ${({ theme }) => theme.font.size.sm};
|
|
||||||
|
|
||||||
font-weight: inherit;
|
|
||||||
max-width: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
text-decoration: none;
|
|
||||||
|
|
||||||
&::placeholder {
|
|
||||||
color: ${({ theme }) => theme.font.color.light};
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
type ObjectFilterSelectMenuProps = {
|
|
||||||
searchText: string;
|
|
||||||
setSearchText: (searchText: string) => void;
|
|
||||||
sortedAvailableFilterDefinitions: FilterDefinition[];
|
|
||||||
selectableListItemIds: string[];
|
|
||||||
handleEnter: (itemId: string) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ObjectFilterSelectMenu = ({
|
|
||||||
searchText,
|
|
||||||
setSearchText,
|
|
||||||
sortedAvailableFilterDefinitions,
|
|
||||||
selectableListItemIds,
|
|
||||||
handleEnter,
|
|
||||||
}: ObjectFilterSelectMenuProps) => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<StyledInput
|
|
||||||
value={searchText}
|
|
||||||
autoFocus
|
|
||||||
placeholder="Search fields"
|
|
||||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
|
||||||
setSearchText(event.target.value)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<SelectableList
|
|
||||||
hotkeyScope={FiltersHotkeyScope.ObjectFilterDropdownButton}
|
|
||||||
selectableItemIdArray={selectableListItemIds}
|
|
||||||
selectableListId={OBJECT_FILTER_DROPDOWN_ID}
|
|
||||||
onEnter={handleEnter}
|
|
||||||
>
|
|
||||||
<DropdownMenuItemsContainer>
|
|
||||||
{sortedAvailableFilterDefinitions.map(
|
|
||||||
(availableFilterDefinition: FilterDefinition, index: number) => (
|
|
||||||
<SelectableItem
|
|
||||||
key={`selectable-item-${availableFilterDefinition.fieldMetadataId}`}
|
|
||||||
itemId={availableFilterDefinition.fieldMetadataId}
|
|
||||||
>
|
|
||||||
<ObjectFilterDropdownFilterSelectMenuItem
|
|
||||||
key={`select-filter-${index}`}
|
|
||||||
filterDefinition={availableFilterDefinition}
|
|
||||||
/>
|
|
||||||
</SelectableItem>
|
|
||||||
),
|
|
||||||
)}
|
|
||||||
</DropdownMenuItemsContainer>
|
|
||||||
</SelectableList>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
import { StyledInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelect';
|
|
||||||
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
|
|
||||||
import {
|
|
||||||
currentParentFilterDefinitionState,
|
|
||||||
currentSubMenuState,
|
|
||||||
} from '@/object-record/object-filter-dropdown/states/subMenuStates';
|
|
||||||
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
|
|
||||||
import { FilterType } from '@/object-record/object-filter-dropdown/types/FilterType';
|
|
||||||
import { getHeaderTitle } from '@/object-record/object-filter-dropdown/utils/getHeaderTitle';
|
|
||||||
import { getOperandsForFilterType } from '@/object-record/object-filter-dropdown/utils/getOperandsForFilterType';
|
|
||||||
import { getSubMenuOptions } from '@/object-record/object-filter-dropdown/utils/getSubMenuOptions';
|
|
||||||
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
|
|
||||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader';
|
|
||||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
|
||||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
|
||||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
|
||||||
import { useState } from 'react';
|
|
||||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
|
||||||
import { IconChevronLeft, useIcons } from 'twenty-ui';
|
|
||||||
|
|
||||||
export const ObjectFilterSelectSubMenu = () => {
|
|
||||||
const [searchText, setSearchText] = useState('');
|
|
||||||
const { getIcon } = useIcons();
|
|
||||||
|
|
||||||
const [currentSubMenu, setCurrentSubMenu] =
|
|
||||||
useRecoilState(currentSubMenuState);
|
|
||||||
|
|
||||||
const currentParentFilterDefinition = useRecoilValue(
|
|
||||||
currentParentFilterDefinitionState,
|
|
||||||
);
|
|
||||||
|
|
||||||
const {
|
|
||||||
setFilterDefinitionUsedInDropdown,
|
|
||||||
setSelectedOperandInDropdown,
|
|
||||||
setObjectFilterDropdownSearchInput,
|
|
||||||
} = useFilterDropdown();
|
|
||||||
|
|
||||||
const setHotkeyScope = useSetHotkeyScope();
|
|
||||||
|
|
||||||
const handleSelectFilter = (definition: FilterDefinition | null) => {
|
|
||||||
if (definition !== null) {
|
|
||||||
setFilterDefinitionUsedInDropdown(definition);
|
|
||||||
if (definition.type === 'SOURCE') {
|
|
||||||
setHotkeyScope(RelationPickerHotkeyScope.RelationPicker);
|
|
||||||
}
|
|
||||||
|
|
||||||
setSelectedOperandInDropdown(
|
|
||||||
getOperandsForFilterType(definition.type)?.[0],
|
|
||||||
);
|
|
||||||
|
|
||||||
setObjectFilterDropdownSearchInput('');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<DropdownMenuHeader
|
|
||||||
StartIcon={IconChevronLeft}
|
|
||||||
onClick={() => {
|
|
||||||
setCurrentSubMenu(null);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{getHeaderTitle(currentSubMenu)}
|
|
||||||
</DropdownMenuHeader>
|
|
||||||
<StyledInput
|
|
||||||
value={searchText}
|
|
||||||
autoFocus
|
|
||||||
placeholder="Search fields"
|
|
||||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
|
||||||
setSearchText(event.target.value)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<DropdownMenuItemsContainer>
|
|
||||||
{getSubMenuOptions(currentSubMenu)
|
|
||||||
.sort((a, b) => a.name.localeCompare(b.name))
|
|
||||||
.filter((item) =>
|
|
||||||
item.name
|
|
||||||
.toLocaleLowerCase()
|
|
||||||
.includes(searchText.toLocaleLowerCase()),
|
|
||||||
)
|
|
||||||
.map((menuOption, index) => (
|
|
||||||
<MenuItem
|
|
||||||
key={`select-filter-${index}`}
|
|
||||||
testId={`select-filter-${index}`}
|
|
||||||
onClick={() => {
|
|
||||||
currentParentFilterDefinition &&
|
|
||||||
handleSelectFilter({
|
|
||||||
...currentParentFilterDefinition,
|
|
||||||
label: menuOption.name,
|
|
||||||
type: menuOption.type as FilterType,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
text={menuOption.name}
|
|
||||||
LeftIcon={getIcon(
|
|
||||||
menuOption.icon || currentParentFilterDefinition?.iconName,
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</DropdownMenuItemsContainer>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -13,7 +13,7 @@ import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
|||||||
|
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
|
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
|
||||||
import { getOperandsForFilterType } from '../utils/getOperandsForFilterType';
|
import { getOperandsForFilterDefinition } from '../utils/getOperandsForFilterType';
|
||||||
import { GenericEntityFilterChip } from './GenericEntityFilterChip';
|
import { GenericEntityFilterChip } from './GenericEntityFilterChip';
|
||||||
import { ObjectFilterDropdownRecordSelect } from './ObjectFilterDropdownRecordSelect';
|
import { ObjectFilterDropdownRecordSelect } from './ObjectFilterDropdownRecordSelect';
|
||||||
import { ObjectFilterDropdownSearchInput } from './ObjectFilterDropdownSearchInput';
|
import { ObjectFilterDropdownSearchInput } from './ObjectFilterDropdownSearchInput';
|
||||||
@@ -36,14 +36,16 @@ export const SingleEntityObjectFilterDropdownButton = ({
|
|||||||
);
|
);
|
||||||
const selectedFilter = useRecoilValue(selectedFilterState);
|
const selectedFilter = useRecoilValue(selectedFilterState);
|
||||||
|
|
||||||
const availableFilter = availableFilterDefinitions[0];
|
const availableFilterDefinition = availableFilterDefinitions[0];
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
setFilterDefinitionUsedInDropdown(availableFilter);
|
setFilterDefinitionUsedInDropdown(availableFilterDefinition);
|
||||||
const defaultOperand = getOperandsForFilterType(availableFilter?.type)[0];
|
const defaultOperand = getOperandsForFilterDefinition(
|
||||||
|
availableFilterDefinition,
|
||||||
|
)[0];
|
||||||
setSelectedOperandInDropdown(defaultOperand);
|
setSelectedOperandInDropdown(defaultOperand);
|
||||||
}, [
|
}, [
|
||||||
availableFilter,
|
availableFilterDefinition,
|
||||||
setFilterDefinitionUsedInDropdown,
|
setFilterDefinitionUsedInDropdown,
|
||||||
setSelectedOperandInDropdown,
|
setSelectedOperandInDropdown,
|
||||||
]);
|
]);
|
||||||
@@ -62,7 +64,7 @@ export const SingleEntityObjectFilterDropdownButton = ({
|
|||||||
filter={selectedFilter}
|
filter={selectedFilter}
|
||||||
Icon={
|
Icon={
|
||||||
selectedFilter.operand === ViewFilterOperand.IsNotNull
|
selectedFilter.operand === ViewFilterOperand.IsNotNull
|
||||||
? availableFilter.SelectAllIcon
|
? availableFilterDefinition.SelectAllIcon
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
|
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
|
||||||
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
|
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
|
||||||
import { getInitialFilterValue } from '@/object-record/object-filter-dropdown/utils/getInitialFilterValue';
|
import { getInitialFilterValue } from '@/object-record/object-filter-dropdown/utils/getInitialFilterValue';
|
||||||
import { getOperandsForFilterType } from '@/object-record/object-filter-dropdown/utils/getOperandsForFilterType';
|
import { getOperandsForFilterDefinition } from '@/object-record/object-filter-dropdown/utils/getOperandsForFilterType';
|
||||||
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
|
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
|
||||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
@@ -31,12 +31,12 @@ export const useSelectFilter = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setSelectedOperandInDropdown(
|
setSelectedOperandInDropdown(
|
||||||
getOperandsForFilterType(filterDefinition.type)?.[0],
|
getOperandsForFilterDefinition(filterDefinition)[0],
|
||||||
);
|
);
|
||||||
|
|
||||||
const { value, displayValue } = getInitialFilterValue(
|
const { value, displayValue } = getInitialFilterValue(
|
||||||
filterDefinition.type,
|
filterDefinition.type,
|
||||||
getOperandsForFilterType(filterDefinition.type)?.[0],
|
getOperandsForFilterDefinition(filterDefinition)[0],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (value !== '') {
|
if (value !== '') {
|
||||||
@@ -44,7 +44,7 @@ export const useSelectFilter = () => {
|
|||||||
id: v4(),
|
id: v4(),
|
||||||
fieldMetadataId: filterDefinition.fieldMetadataId,
|
fieldMetadataId: filterDefinition.fieldMetadataId,
|
||||||
displayValue,
|
displayValue,
|
||||||
operand: getOperandsForFilterType(filterDefinition.type)?.[0],
|
operand: getOperandsForFilterDefinition(filterDefinition)[0],
|
||||||
value,
|
value,
|
||||||
definition: filterDefinition,
|
definition: filterDefinition,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
|
|
||||||
import { FilterType } from '@/object-record/object-filter-dropdown/types/FilterType';
|
|
||||||
import { atom } from 'recoil';
|
|
||||||
|
|
||||||
export const currentSubMenuState = atom<FilterType | null>({
|
|
||||||
key: 'currentSubMenuState',
|
|
||||||
default: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const currentParentFilterDefinitionState = atom<FilterDefinition | null>(
|
|
||||||
{
|
|
||||||
key: 'currentParentFilterDefinitionState',
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { FilterableFieldType } from '@/object-record/object-filter-dropdown/types/FilterableFieldType';
|
||||||
|
import { CompositeFieldType } from '@/settings/data-model/types/CompositeFieldType';
|
||||||
|
|
||||||
|
export type CompositeFilterableFieldType = FilterableFieldType &
|
||||||
|
CompositeFieldType;
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
import { IconComponent } from 'twenty-ui';
|
import { IconComponent } from 'twenty-ui';
|
||||||
|
|
||||||
import { FilterType } from './FilterType';
|
import { FilterableFieldType } from './FilterableFieldType';
|
||||||
|
|
||||||
export type FilterDefinition = {
|
export type FilterDefinition = {
|
||||||
fieldMetadataId: string;
|
fieldMetadataId: string;
|
||||||
label: string;
|
label: string;
|
||||||
iconName: string;
|
iconName: string;
|
||||||
type: FilterType;
|
type: FilterableFieldType;
|
||||||
relationObjectMetadataNamePlural?: string;
|
relationObjectMetadataNamePlural?: string;
|
||||||
relationObjectMetadataNameSingular?: string;
|
relationObjectMetadataNameSingular?: string;
|
||||||
selectAllLabel?: string;
|
selectAllLabel?: string;
|
||||||
SelectAllIcon?: IconComponent;
|
SelectAllIcon?: IconComponent;
|
||||||
subFieldType?: FilterType;
|
compositeFieldName?: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
export type FilterType =
|
import { FieldType } from '@/settings/data-model/types/FieldType';
|
||||||
|
import { PickLiteral } from '~/types/PickLiteral';
|
||||||
|
|
||||||
|
export type FilterableFieldType = PickLiteral<
|
||||||
|
FieldType,
|
||||||
| 'TEXT'
|
| 'TEXT'
|
||||||
| 'PHONE'
|
| 'PHONE'
|
||||||
| 'PHONES'
|
| 'PHONES'
|
||||||
@@ -18,4 +22,4 @@ export type FilterType =
|
|||||||
| 'MULTI_SELECT'
|
| 'MULTI_SELECT'
|
||||||
| 'ACTOR'
|
| 'ACTOR'
|
||||||
| 'ARRAY'
|
| 'ARRAY'
|
||||||
| 'SOURCE';
|
>;
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import { FilterType } from '@/object-record/object-filter-dropdown/types/FilterType';
|
|
||||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||||
|
|
||||||
import { getOperandsForFilterType } from '../getOperandsForFilterType';
|
import { FilterableFieldType } from '@/object-record/object-filter-dropdown/types/FilterableFieldType';
|
||||||
|
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
|
||||||
|
import { getOperandsForFilterDefinition } from '../getOperandsForFilterType';
|
||||||
|
|
||||||
describe('getOperandsForFilterType', () => {
|
describe('getOperandsForFilterType', () => {
|
||||||
const emptyOperands = [
|
const emptyOperands = [
|
||||||
@@ -51,7 +52,9 @@ describe('getOperandsForFilterType', () => {
|
|||||||
|
|
||||||
testCases.forEach(([filterType, expectedOperands]) => {
|
testCases.forEach(([filterType, expectedOperands]) => {
|
||||||
it(`should return correct operands for FilterType.${filterType}`, () => {
|
it(`should return correct operands for FilterType.${filterType}`, () => {
|
||||||
const result = getOperandsForFilterType(filterType as FilterType);
|
const result = getOperandsForFilterDefinition({
|
||||||
|
type: filterType as FilterableFieldType,
|
||||||
|
} as FilterDefinition);
|
||||||
expect(result).toEqual(expectedOperands);
|
expect(result).toEqual(expectedOperands);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,54 +9,54 @@ import {
|
|||||||
IconUserCircle,
|
IconUserCircle,
|
||||||
} from 'twenty-ui';
|
} from 'twenty-ui';
|
||||||
|
|
||||||
export const getSourceEnumOptions = (
|
export const getActorSourceMultiSelectOptions = (
|
||||||
selectedItemIds: string[],
|
selectedSourceNames: string[],
|
||||||
): SelectableItem[] => {
|
): SelectableItem[] => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
id: 'MANUAL',
|
id: 'MANUAL',
|
||||||
name: 'User',
|
name: 'User',
|
||||||
isSelected: selectedItemIds.includes('MANUAL'),
|
isSelected: selectedSourceNames.includes('MANUAL'),
|
||||||
AvatarIcon: IconUserCircle,
|
AvatarIcon: IconUserCircle,
|
||||||
isIconInverted: true,
|
isIconInverted: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'IMPORT',
|
id: 'IMPORT',
|
||||||
name: 'Import',
|
name: 'Import',
|
||||||
isSelected: selectedItemIds.includes('IMPORT'),
|
isSelected: selectedSourceNames.includes('IMPORT'),
|
||||||
AvatarIcon: IconCsv,
|
AvatarIcon: IconCsv,
|
||||||
isIconInverted: true,
|
isIconInverted: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'API',
|
id: 'API',
|
||||||
name: 'Api',
|
name: 'Api',
|
||||||
isSelected: selectedItemIds.includes('API'),
|
isSelected: selectedSourceNames.includes('API'),
|
||||||
AvatarIcon: IconApi,
|
AvatarIcon: IconApi,
|
||||||
isIconInverted: true,
|
isIconInverted: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'EMAIL',
|
id: 'EMAIL',
|
||||||
name: 'Email',
|
name: 'Email',
|
||||||
isSelected: selectedItemIds.includes('EMAIL'),
|
isSelected: selectedSourceNames.includes('EMAIL'),
|
||||||
AvatarIcon: IconGmail,
|
AvatarIcon: IconGmail,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'CALENDAR',
|
id: 'CALENDAR',
|
||||||
name: 'Calendar',
|
name: 'Calendar',
|
||||||
isSelected: selectedItemIds.includes('CALENDAR'),
|
isSelected: selectedSourceNames.includes('CALENDAR'),
|
||||||
AvatarIcon: IconGoogleCalendar,
|
AvatarIcon: IconGoogleCalendar,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'WORKFLOW',
|
id: 'WORKFLOW',
|
||||||
name: 'Workflow',
|
name: 'Workflow',
|
||||||
isSelected: selectedItemIds.includes('WORKFLOW'),
|
isSelected: selectedSourceNames.includes('WORKFLOW'),
|
||||||
AvatarIcon: IconSettingsAutomation,
|
AvatarIcon: IconSettingsAutomation,
|
||||||
isIconInverted: true,
|
isIconInverted: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'SYSTEM',
|
id: 'SYSTEM',
|
||||||
name: 'System',
|
name: 'System',
|
||||||
isSelected: selectedItemIds.includes('SYSTEM'),
|
isSelected: selectedSourceNames.includes('SYSTEM'),
|
||||||
AvatarIcon: IconRobot,
|
AvatarIcon: IconRobot,
|
||||||
isIconInverted: true,
|
isIconInverted: true,
|
||||||
},
|
},
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsCompositeFieldTypeConfigs';
|
||||||
|
import { CompositeFieldType } from '@/settings/data-model/types/CompositeFieldType';
|
||||||
|
|
||||||
|
export const getCompositeSubFieldLabel = (
|
||||||
|
compositeFieldType: CompositeFieldType,
|
||||||
|
subFieldName: (typeof SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS)[CompositeFieldType]['subFields'][number],
|
||||||
|
): string => {
|
||||||
|
return (
|
||||||
|
SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS[compositeFieldType]
|
||||||
|
.labelBySubField as any
|
||||||
|
)[subFieldName];
|
||||||
|
};
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { FilterableFieldType } from '@/object-record/object-filter-dropdown/types/FilterableFieldType';
|
||||||
|
import { SETTINGS_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsFieldTypeConfigs';
|
||||||
|
|
||||||
|
export const getFilterableFieldTypeLabel = (
|
||||||
|
filterableFieldType: FilterableFieldType,
|
||||||
|
) => {
|
||||||
|
return SETTINGS_FIELD_TYPE_CONFIGS[filterableFieldType].label;
|
||||||
|
};
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import { FilterType } from '@/object-record/object-filter-dropdown/types/FilterType';
|
|
||||||
|
|
||||||
export const getHeaderTitle = (
|
|
||||||
subMenu: FilterType | null,
|
|
||||||
): string | undefined => {
|
|
||||||
switch (subMenu) {
|
|
||||||
case 'ACTOR':
|
|
||||||
return 'Actor';
|
|
||||||
case 'SOURCE':
|
|
||||||
return 'Creation Source';
|
|
||||||
default:
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
|
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
|
||||||
import { FilterType } from '@/object-record/object-filter-dropdown/types/FilterType';
|
import { FilterableFieldType } from '@/object-record/object-filter-dropdown/types/FilterableFieldType';
|
||||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
export const getInitialFilterValue = (
|
export const getInitialFilterValue = (
|
||||||
newType: FilterType,
|
newType: FilterableFieldType,
|
||||||
newOperand: ViewFilterOperand,
|
newOperand: ViewFilterOperand,
|
||||||
oldValue?: string,
|
oldValue?: string,
|
||||||
oldDisplayValue?: string,
|
oldDisplayValue?: string,
|
||||||
@@ -35,6 +35,7 @@ export const getInitialFilterValue = (
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
value: oldValue ?? '',
|
value: oldValue ?? '',
|
||||||
displayValue: oldDisplayValue ?? '',
|
displayValue: oldDisplayValue ?? '',
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
|
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
|
||||||
|
import { isActorSourceCompositeFilter } from '@/object-record/object-filter-dropdown/utils/isActorSourceCompositeFilter';
|
||||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||||
|
|
||||||
import { FilterType } from '../types/FilterType';
|
export const getOperandsForFilterDefinition = (
|
||||||
|
filterDefinition: FilterDefinition,
|
||||||
export const getOperandsForFilterType = (
|
|
||||||
filterType: FilterType | null | undefined,
|
|
||||||
): ViewFilterOperand[] => {
|
): ViewFilterOperand[] => {
|
||||||
const emptyOperands = [
|
const emptyOperands = [
|
||||||
ViewFilterOperand.IsEmpty,
|
ViewFilterOperand.IsEmpty,
|
||||||
@@ -12,7 +12,7 @@ export const getOperandsForFilterType = (
|
|||||||
|
|
||||||
const relationOperands = [ViewFilterOperand.Is, ViewFilterOperand.IsNot];
|
const relationOperands = [ViewFilterOperand.Is, ViewFilterOperand.IsNot];
|
||||||
|
|
||||||
switch (filterType) {
|
switch (filterDefinition.type) {
|
||||||
case 'TEXT':
|
case 'TEXT':
|
||||||
case 'EMAIL':
|
case 'EMAIL':
|
||||||
case 'EMAILS':
|
case 'EMAILS':
|
||||||
@@ -21,7 +21,6 @@ export const getOperandsForFilterType = (
|
|||||||
case 'PHONE':
|
case 'PHONE':
|
||||||
case 'LINK':
|
case 'LINK':
|
||||||
case 'LINKS':
|
case 'LINKS':
|
||||||
case 'ACTOR':
|
|
||||||
case 'ARRAY':
|
case 'ARRAY':
|
||||||
case 'PHONES':
|
case 'PHONES':
|
||||||
return [
|
return [
|
||||||
@@ -57,10 +56,23 @@ export const getOperandsForFilterType = (
|
|||||||
];
|
];
|
||||||
case 'RELATION':
|
case 'RELATION':
|
||||||
return [...relationOperands, ...emptyOperands];
|
return [...relationOperands, ...emptyOperands];
|
||||||
case 'SOURCE':
|
|
||||||
return [...relationOperands];
|
|
||||||
case 'SELECT':
|
case 'SELECT':
|
||||||
return [...relationOperands];
|
return [...relationOperands];
|
||||||
|
case 'ACTOR': {
|
||||||
|
if (isActorSourceCompositeFilter(filterDefinition)) {
|
||||||
|
return [
|
||||||
|
ViewFilterOperand.Is,
|
||||||
|
ViewFilterOperand.IsNot,
|
||||||
|
...emptyOperands,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
ViewFilterOperand.Contains,
|
||||||
|
ViewFilterOperand.DoesNotContain,
|
||||||
|
...emptyOperands,
|
||||||
|
];
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { SETTINGS_NON_COMPOSITE_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsNonCompositeFieldTypeConfigs';
|
||||||
|
import { SettingsNonCompositeFieldType } from '@/settings/data-model/types/SettingsNonCompositeFieldType';
|
||||||
|
|
||||||
|
export const getSettingsNonCompositeFieldTypeLabel = (
|
||||||
|
settingsNonCompositeFieldType: SettingsNonCompositeFieldType,
|
||||||
|
) => {
|
||||||
|
return SETTINGS_NON_COMPOSITE_FIELD_TYPE_CONFIGS[
|
||||||
|
settingsNonCompositeFieldType
|
||||||
|
].label;
|
||||||
|
};
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { FilterType } from '@/object-record/object-filter-dropdown/types/FilterType';
|
import { FilterableFieldType } from '@/object-record/object-filter-dropdown/types/FilterableFieldType';
|
||||||
|
|
||||||
export const getSubMenuOptions = (subMenu: FilterType | null) => {
|
export const getSubMenuOptions = (subMenu: FilterableFieldType | null) => {
|
||||||
switch (subMenu) {
|
switch (subMenu) {
|
||||||
case 'ACTOR':
|
case 'ACTOR':
|
||||||
return [
|
return [
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
import { FilterType } from '@/object-record/object-filter-dropdown/types/FilterType';
|
|
||||||
|
|
||||||
export const hasSubMenuFilter = (type: FilterType) => ['ACTOR'].includes(type);
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
|
||||||
|
import { FieldActorValue } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
|
|
||||||
|
export const isActorSourceCompositeFilter = (
|
||||||
|
filterDefinition: FilterDefinition,
|
||||||
|
) => {
|
||||||
|
return (
|
||||||
|
filterDefinition.compositeFieldName ===
|
||||||
|
('source' satisfies keyof FieldActorValue)
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import {
|
||||||
|
COMPOSITE_FIELD_TYPES,
|
||||||
|
CompositeFieldType,
|
||||||
|
} from '@/settings/data-model/types/CompositeFieldType';
|
||||||
|
import { FieldType } from '@/settings/data-model/types/FieldType';
|
||||||
|
|
||||||
|
export const isCompositeField = (type: FieldType): type is CompositeFieldType =>
|
||||||
|
COMPOSITE_FIELD_TYPES.includes(type as any);
|
||||||
@@ -177,7 +177,7 @@ export type FieldMetadata =
|
|||||||
| FieldArrayMetadata;
|
| FieldArrayMetadata;
|
||||||
|
|
||||||
export type FieldTextValue = string;
|
export type FieldTextValue = string;
|
||||||
export type FieldUUidValue = string;
|
export type FieldUUidValue = string; // TODO: can we replace with a template literal type, or maybe overkill ?
|
||||||
export type FieldDateTimeValue = string | null;
|
export type FieldDateTimeValue = string | null;
|
||||||
export type FieldDateValue = string | null;
|
export type FieldDateValue = string | null;
|
||||||
export type FieldNumberValue = number | null;
|
export type FieldNumberValue = number | null;
|
||||||
@@ -225,6 +225,8 @@ export type FieldRelationValue<
|
|||||||
export type Json = ZodHelperLiteral | { [key: string]: Json } | Json[];
|
export type Json = ZodHelperLiteral | { [key: string]: Json } | Json[];
|
||||||
export type FieldJsonValue = Record<string, Json> | Json[] | null;
|
export type FieldJsonValue = Record<string, Json> | Json[] | null;
|
||||||
|
|
||||||
|
export type FieldRichTextValue = Record<string, Json> | Json[] | null;
|
||||||
|
|
||||||
export type FieldActorValue = {
|
export type FieldActorValue = {
|
||||||
source: string;
|
source: string;
|
||||||
workspaceMemberId?: string;
|
workspaceMemberId?: string;
|
||||||
|
|||||||
@@ -0,0 +1,339 @@
|
|||||||
|
import {
|
||||||
|
ActorFilter,
|
||||||
|
AddressFilter,
|
||||||
|
CurrencyFilter,
|
||||||
|
DateFilter,
|
||||||
|
EmailsFilter,
|
||||||
|
FloatFilter,
|
||||||
|
RecordGqlOperationFilter,
|
||||||
|
RelationFilter,
|
||||||
|
StringFilter,
|
||||||
|
URLFilter,
|
||||||
|
UUIDFilter,
|
||||||
|
} from '@/object-record/graphql/types/RecordGqlOperationFilter';
|
||||||
|
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
|
||||||
|
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||||
|
import { isNonEmptyString } from '@sniptt/guards';
|
||||||
|
import { Field } from '~/generated/graphql';
|
||||||
|
import { generateILikeFiltersForCompositeFields } from '~/utils/array/generateILikeFiltersForCompositeFields';
|
||||||
|
|
||||||
|
// TODO: fix this
|
||||||
|
export const applyEmptyFilters = (
|
||||||
|
operand: ViewFilterOperand,
|
||||||
|
correspondingField: Pick<Field, 'id' | 'name'>,
|
||||||
|
objectRecordFilters: RecordGqlOperationFilter[],
|
||||||
|
definition: FilterDefinition,
|
||||||
|
) => {
|
||||||
|
let emptyRecordFilter: RecordGqlOperationFilter = {};
|
||||||
|
|
||||||
|
const compositeFieldName = definition.compositeFieldName;
|
||||||
|
|
||||||
|
const isCompositeField = isNonEmptyString(compositeFieldName);
|
||||||
|
|
||||||
|
switch (definition.type) {
|
||||||
|
case 'TEXT':
|
||||||
|
case 'EMAIL':
|
||||||
|
case 'PHONE':
|
||||||
|
emptyRecordFilter = {
|
||||||
|
or: [
|
||||||
|
{ [correspondingField.name]: { ilike: '' } as StringFilter },
|
||||||
|
{ [correspondingField.name]: { is: 'NULL' } as StringFilter },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 'PHONES': {
|
||||||
|
if (!isCompositeField) {
|
||||||
|
const phonesFilter = generateILikeFiltersForCompositeFields(
|
||||||
|
'',
|
||||||
|
correspondingField.name,
|
||||||
|
['primaryPhoneNumber', 'primaryPhoneCountryCode'],
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
emptyRecordFilter = {
|
||||||
|
and: phonesFilter,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
emptyRecordFilter = {
|
||||||
|
or: [
|
||||||
|
{
|
||||||
|
[correspondingField.name]: {
|
||||||
|
[compositeFieldName]: { ilike: '' },
|
||||||
|
} as StringFilter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[correspondingField.name]: {
|
||||||
|
[compositeFieldName]: { is: 'NULL' },
|
||||||
|
} as StringFilter,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 'CURRENCY':
|
||||||
|
emptyRecordFilter = {
|
||||||
|
or: [
|
||||||
|
{
|
||||||
|
[correspondingField.name]: {
|
||||||
|
amountMicros: { is: 'NULL' },
|
||||||
|
} as CurrencyFilter,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 'FULL_NAME': {
|
||||||
|
if (!isCompositeField) {
|
||||||
|
const fullNameFilters = generateILikeFiltersForCompositeFields(
|
||||||
|
'',
|
||||||
|
correspondingField.name,
|
||||||
|
['firstName', 'lastName'],
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
emptyRecordFilter = {
|
||||||
|
and: fullNameFilters,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
emptyRecordFilter = {
|
||||||
|
or: [
|
||||||
|
{
|
||||||
|
[correspondingField.name]: {
|
||||||
|
[compositeFieldName]: { ilike: '' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[correspondingField.name]: {
|
||||||
|
[compositeFieldName]: { is: 'NULL' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'LINK':
|
||||||
|
emptyRecordFilter = {
|
||||||
|
or: [
|
||||||
|
{ [correspondingField.name]: { url: { ilike: '' } } as URLFilter },
|
||||||
|
{
|
||||||
|
[correspondingField.name]: { url: { is: 'NULL' } } as URLFilter,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 'LINKS': {
|
||||||
|
if (!isCompositeField) {
|
||||||
|
const linksFilters = generateILikeFiltersForCompositeFields(
|
||||||
|
'',
|
||||||
|
correspondingField.name,
|
||||||
|
['primaryLinkLabel', 'primaryLinkUrl'],
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
emptyRecordFilter = {
|
||||||
|
and: linksFilters,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
emptyRecordFilter = {
|
||||||
|
or: [
|
||||||
|
{
|
||||||
|
[correspondingField.name]: {
|
||||||
|
[compositeFieldName]: { ilike: '' },
|
||||||
|
} as URLFilter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[correspondingField.name]: {
|
||||||
|
[compositeFieldName]: { is: 'NULL' },
|
||||||
|
} as URLFilter,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'ADDRESS':
|
||||||
|
if (!isCompositeField) {
|
||||||
|
emptyRecordFilter = {
|
||||||
|
and: [
|
||||||
|
{
|
||||||
|
or: [
|
||||||
|
{
|
||||||
|
[correspondingField.name]: {
|
||||||
|
addressStreet1: { ilike: '' },
|
||||||
|
} as AddressFilter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[correspondingField.name]: {
|
||||||
|
addressStreet1: { is: 'NULL' },
|
||||||
|
} as AddressFilter,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
or: [
|
||||||
|
{
|
||||||
|
[correspondingField.name]: {
|
||||||
|
addressStreet2: { ilike: '' },
|
||||||
|
} as AddressFilter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[correspondingField.name]: {
|
||||||
|
addressStreet2: { is: 'NULL' },
|
||||||
|
} as AddressFilter,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
or: [
|
||||||
|
{
|
||||||
|
[correspondingField.name]: {
|
||||||
|
addressCity: { ilike: '' },
|
||||||
|
} as AddressFilter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[correspondingField.name]: {
|
||||||
|
addressCity: { is: 'NULL' },
|
||||||
|
} as AddressFilter,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
or: [
|
||||||
|
{
|
||||||
|
[correspondingField.name]: {
|
||||||
|
addressState: { ilike: '' },
|
||||||
|
} as AddressFilter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[correspondingField.name]: {
|
||||||
|
addressState: { is: 'NULL' },
|
||||||
|
} as AddressFilter,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
or: [
|
||||||
|
{
|
||||||
|
[correspondingField.name]: {
|
||||||
|
addressCountry: { ilike: '' },
|
||||||
|
} as AddressFilter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[correspondingField.name]: {
|
||||||
|
addressCountry: { is: 'NULL' },
|
||||||
|
} as AddressFilter,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
or: [
|
||||||
|
{
|
||||||
|
[correspondingField.name]: {
|
||||||
|
addressPostcode: { ilike: '' },
|
||||||
|
} as AddressFilter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[correspondingField.name]: {
|
||||||
|
addressPostcode: { is: 'NULL' },
|
||||||
|
} as AddressFilter,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
emptyRecordFilter = {
|
||||||
|
or: [
|
||||||
|
{
|
||||||
|
[correspondingField.name]: {
|
||||||
|
[compositeFieldName]: { ilike: '' },
|
||||||
|
} as AddressFilter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[correspondingField.name]: {
|
||||||
|
[compositeFieldName]: { is: 'NULL' },
|
||||||
|
} as AddressFilter,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'NUMBER':
|
||||||
|
emptyRecordFilter = {
|
||||||
|
[correspondingField.name]: { is: 'NULL' } as FloatFilter,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 'RATING':
|
||||||
|
emptyRecordFilter = {
|
||||||
|
[correspondingField.name]: { is: 'NULL' } as StringFilter,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 'DATE':
|
||||||
|
case 'DATE_TIME':
|
||||||
|
emptyRecordFilter = {
|
||||||
|
[correspondingField.name]: { is: 'NULL' } as DateFilter,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 'SELECT':
|
||||||
|
emptyRecordFilter = {
|
||||||
|
[correspondingField.name]: { is: 'NULL' } as UUIDFilter,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 'RELATION':
|
||||||
|
emptyRecordFilter = {
|
||||||
|
[correspondingField.name + 'Id']: { is: 'NULL' } as RelationFilter,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 'ACTOR':
|
||||||
|
emptyRecordFilter = {
|
||||||
|
or: [
|
||||||
|
{
|
||||||
|
[correspondingField.name]: {
|
||||||
|
name: { ilike: '' },
|
||||||
|
} as ActorFilter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[correspondingField.name]: {
|
||||||
|
name: { is: 'NULL' },
|
||||||
|
} as ActorFilter,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 'EMAILS':
|
||||||
|
emptyRecordFilter = {
|
||||||
|
or: [
|
||||||
|
{
|
||||||
|
[correspondingField.name]: {
|
||||||
|
primaryEmail: { ilike: '' },
|
||||||
|
} as EmailsFilter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[correspondingField.name]: {
|
||||||
|
primaryEmail: { is: 'NULL' },
|
||||||
|
} as EmailsFilter,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported empty filter type ${definition.type}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (operand) {
|
||||||
|
case ViewFilterOperand.IsEmpty:
|
||||||
|
objectRecordFilters.push(emptyRecordFilter);
|
||||||
|
break;
|
||||||
|
case ViewFilterOperand.IsNotEmpty:
|
||||||
|
objectRecordFilters.push({
|
||||||
|
not: emptyRecordFilter,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(
|
||||||
|
`Unknown operand ${operand} for ${definition.type} filter`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -10,9 +10,9 @@ import {
|
|||||||
RecordGqlOperationFilter,
|
RecordGqlOperationFilter,
|
||||||
RelationFilter,
|
RelationFilter,
|
||||||
StringFilter,
|
StringFilter,
|
||||||
|
URLFilter,
|
||||||
UUIDFilter,
|
UUIDFilter,
|
||||||
} from '@/object-record/graphql/types/RecordGqlOperationFilter';
|
} from '@/object-record/graphql/types/RecordGqlOperationFilter';
|
||||||
import { FilterType } from '@/object-record/object-filter-dropdown/types/FilterType';
|
|
||||||
import { makeAndFilterVariables } from '@/object-record/utils/makeAndFilterVariables';
|
import { makeAndFilterVariables } from '@/object-record/utils/makeAndFilterVariables';
|
||||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||||
import { Field } from '~/generated/graphql';
|
import { Field } from '~/generated/graphql';
|
||||||
@@ -24,244 +24,15 @@ import {
|
|||||||
convertLessThanRatingToArrayOfRatingValues,
|
convertLessThanRatingToArrayOfRatingValues,
|
||||||
convertRatingToRatingValue,
|
convertRatingToRatingValue,
|
||||||
} from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRatingInput';
|
} from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRatingInput';
|
||||||
|
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
|
||||||
|
import { isActorSourceCompositeFilter } from '@/object-record/object-filter-dropdown/utils/isActorSourceCompositeFilter';
|
||||||
|
import { applyEmptyFilters } from '@/object-record/record-filter/utils/applyEmptyFilters';
|
||||||
import { resolveFilterValue } from '@/views/utils/view-filter-value/resolveFilterValue';
|
import { resolveFilterValue } from '@/views/utils/view-filter-value/resolveFilterValue';
|
||||||
import { endOfDay, roundToNearestMinutes, startOfDay } from 'date-fns';
|
import { endOfDay, roundToNearestMinutes, startOfDay } from 'date-fns';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { Filter } from '../../object-filter-dropdown/types/Filter';
|
|
||||||
|
|
||||||
const applyEmptyFilters = (
|
|
||||||
operand: ViewFilterOperand,
|
|
||||||
correspondingField: Pick<Field, 'id' | 'name'>,
|
|
||||||
objectRecordFilters: RecordGqlOperationFilter[],
|
|
||||||
filterType: FilterType,
|
|
||||||
) => {
|
|
||||||
let emptyRecordFilter: RecordGqlOperationFilter = {};
|
|
||||||
|
|
||||||
switch (filterType) {
|
|
||||||
case 'TEXT':
|
|
||||||
emptyRecordFilter = {
|
|
||||||
or: [
|
|
||||||
{ [correspondingField.name]: { ilike: '' } as StringFilter },
|
|
||||||
{ [correspondingField.name]: { is: 'NULL' } as StringFilter },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
case 'PHONES': {
|
|
||||||
const phonesFilter = generateILikeFiltersForCompositeFields(
|
|
||||||
'',
|
|
||||||
correspondingField.name,
|
|
||||||
['primaryPhoneNumber', 'primaryPhoneCountryCode'],
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
|
|
||||||
emptyRecordFilter = {
|
|
||||||
and: phonesFilter,
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'CURRENCY':
|
|
||||||
emptyRecordFilter = {
|
|
||||||
or: [
|
|
||||||
{
|
|
||||||
[correspondingField.name]: {
|
|
||||||
amountMicros: { is: 'NULL' },
|
|
||||||
} as CurrencyFilter,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
case 'FULL_NAME': {
|
|
||||||
const fullNameFilters = generateILikeFiltersForCompositeFields(
|
|
||||||
'',
|
|
||||||
correspondingField.name,
|
|
||||||
['firstName', 'lastName'],
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
|
|
||||||
emptyRecordFilter = {
|
|
||||||
and: fullNameFilters,
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'LINKS': {
|
|
||||||
const linksFilters = generateILikeFiltersForCompositeFields(
|
|
||||||
'',
|
|
||||||
correspondingField.name,
|
|
||||||
['primaryLinkLabel', 'primaryLinkUrl'],
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
|
|
||||||
emptyRecordFilter = {
|
|
||||||
and: linksFilters,
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'ADDRESS':
|
|
||||||
emptyRecordFilter = {
|
|
||||||
and: [
|
|
||||||
{
|
|
||||||
or: [
|
|
||||||
{
|
|
||||||
[correspondingField.name]: {
|
|
||||||
addressStreet1: { ilike: '' },
|
|
||||||
} as AddressFilter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[correspondingField.name]: {
|
|
||||||
addressStreet1: { is: 'NULL' },
|
|
||||||
} as AddressFilter,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
or: [
|
|
||||||
{
|
|
||||||
[correspondingField.name]: {
|
|
||||||
addressStreet2: { ilike: '' },
|
|
||||||
} as AddressFilter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[correspondingField.name]: {
|
|
||||||
addressStreet2: { is: 'NULL' },
|
|
||||||
} as AddressFilter,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
or: [
|
|
||||||
{
|
|
||||||
[correspondingField.name]: {
|
|
||||||
addressCity: { ilike: '' },
|
|
||||||
} as AddressFilter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[correspondingField.name]: {
|
|
||||||
addressCity: { is: 'NULL' },
|
|
||||||
} as AddressFilter,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
or: [
|
|
||||||
{
|
|
||||||
[correspondingField.name]: {
|
|
||||||
addressState: { ilike: '' },
|
|
||||||
} as AddressFilter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[correspondingField.name]: {
|
|
||||||
addressState: { is: 'NULL' },
|
|
||||||
} as AddressFilter,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
or: [
|
|
||||||
{
|
|
||||||
[correspondingField.name]: {
|
|
||||||
addressCountry: { ilike: '' },
|
|
||||||
} as AddressFilter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[correspondingField.name]: {
|
|
||||||
addressCountry: { is: 'NULL' },
|
|
||||||
} as AddressFilter,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
or: [
|
|
||||||
{
|
|
||||||
[correspondingField.name]: {
|
|
||||||
addressPostcode: { ilike: '' },
|
|
||||||
} as AddressFilter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[correspondingField.name]: {
|
|
||||||
addressPostcode: { is: 'NULL' },
|
|
||||||
} as AddressFilter,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
case 'NUMBER':
|
|
||||||
emptyRecordFilter = {
|
|
||||||
[correspondingField.name]: { is: 'NULL' } as FloatFilter,
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
case 'RATING':
|
|
||||||
emptyRecordFilter = {
|
|
||||||
[correspondingField.name]: { is: 'NULL' } as StringFilter,
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
case 'DATE':
|
|
||||||
case 'DATE_TIME':
|
|
||||||
emptyRecordFilter = {
|
|
||||||
[correspondingField.name]: { is: 'NULL' } as DateFilter,
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
case 'SELECT':
|
|
||||||
emptyRecordFilter = {
|
|
||||||
[correspondingField.name]: { is: 'NULL' } as UUIDFilter,
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
case 'RELATION':
|
|
||||||
emptyRecordFilter = {
|
|
||||||
[correspondingField.name + 'Id']: { is: 'NULL' } as RelationFilter,
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
case 'ACTOR':
|
|
||||||
emptyRecordFilter = {
|
|
||||||
or: [
|
|
||||||
{
|
|
||||||
[correspondingField.name]: {
|
|
||||||
name: { ilike: '' },
|
|
||||||
} as ActorFilter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[correspondingField.name]: {
|
|
||||||
name: { is: 'NULL' },
|
|
||||||
} as ActorFilter,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
case 'EMAILS':
|
|
||||||
emptyRecordFilter = {
|
|
||||||
or: [
|
|
||||||
{
|
|
||||||
[correspondingField.name]: {
|
|
||||||
primaryEmail: { ilike: '' },
|
|
||||||
} as EmailsFilter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[correspondingField.name]: {
|
|
||||||
primaryEmail: { is: 'NULL' },
|
|
||||||
} as EmailsFilter,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error(`Unsupported empty filter type ${filterType}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (operand) {
|
|
||||||
case ViewFilterOperand.IsEmpty:
|
|
||||||
objectRecordFilters.push(emptyRecordFilter);
|
|
||||||
break;
|
|
||||||
case ViewFilterOperand.IsNotEmpty:
|
|
||||||
objectRecordFilters.push({
|
|
||||||
not: emptyRecordFilter,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error(`Unknown operand ${operand} for ${filterType} filter`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
// TODO: break this down into smaller functions and make the whole thing immutable
|
||||||
|
// Especially applyEmptyFilters
|
||||||
export const turnObjectDropdownFilterIntoQueryFilter = (
|
export const turnObjectDropdownFilterIntoQueryFilter = (
|
||||||
rawUIFilters: Filter[],
|
rawUIFilters: Filter[],
|
||||||
fields: Pick<Field, 'id' | 'name'>[],
|
fields: Pick<Field, 'id' | 'name'>[],
|
||||||
@@ -273,7 +44,11 @@ export const turnObjectDropdownFilterIntoQueryFilter = (
|
|||||||
(field) => field.id === rawUIFilter.fieldMetadataId,
|
(field) => field.id === rawUIFilter.fieldMetadataId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const isValuelessOperand = [
|
const compositeFieldName = rawUIFilter.definition.compositeFieldName;
|
||||||
|
|
||||||
|
const isCompositeFieldFiter = isNonEmptyString(compositeFieldName);
|
||||||
|
|
||||||
|
const isEmptyOperand = [
|
||||||
ViewFilterOperand.IsEmpty,
|
ViewFilterOperand.IsEmpty,
|
||||||
ViewFilterOperand.IsNotEmpty,
|
ViewFilterOperand.IsNotEmpty,
|
||||||
ViewFilterOperand.IsInPast,
|
ViewFilterOperand.IsInPast,
|
||||||
@@ -285,7 +60,7 @@ export const turnObjectDropdownFilterIntoQueryFilter = (
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isValuelessOperand) {
|
if (!isEmptyOperand) {
|
||||||
if (!isDefined(rawUIFilter.value) || rawUIFilter.value === '') {
|
if (!isDefined(rawUIFilter.value) || rawUIFilter.value === '') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -316,7 +91,7 @@ export const turnObjectDropdownFilterIntoQueryFilter = (
|
|||||||
rawUIFilter.operand,
|
rawUIFilter.operand,
|
||||||
correspondingField,
|
correspondingField,
|
||||||
objectRecordFilters,
|
objectRecordFilters,
|
||||||
rawUIFilter.definition.type,
|
rawUIFilter.definition,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -355,7 +130,7 @@ export const turnObjectDropdownFilterIntoQueryFilter = (
|
|||||||
rawUIFilter.operand,
|
rawUIFilter.operand,
|
||||||
correspondingField,
|
correspondingField,
|
||||||
objectRecordFilters,
|
objectRecordFilters,
|
||||||
rawUIFilter.definition.type,
|
rawUIFilter.definition,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -372,8 +147,9 @@ export const turnObjectDropdownFilterIntoQueryFilter = (
|
|||||||
operand: ViewFilterOperand.IsRelative,
|
operand: ViewFilterOperand.IsRelative,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!defaultDateRange)
|
if (!defaultDateRange) {
|
||||||
throw new Error('Failed to resolve default date range');
|
throw new Error('Failed to resolve default date range');
|
||||||
|
}
|
||||||
|
|
||||||
const { start, end } = dateRange ?? defaultDateRange;
|
const { start, end } = dateRange ?? defaultDateRange;
|
||||||
|
|
||||||
@@ -484,7 +260,7 @@ export const turnObjectDropdownFilterIntoQueryFilter = (
|
|||||||
rawUIFilter.operand,
|
rawUIFilter.operand,
|
||||||
correspondingField,
|
correspondingField,
|
||||||
objectRecordFilters,
|
objectRecordFilters,
|
||||||
rawUIFilter.definition.type,
|
rawUIFilter.definition,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -515,7 +291,7 @@ export const turnObjectDropdownFilterIntoQueryFilter = (
|
|||||||
rawUIFilter.operand,
|
rawUIFilter.operand,
|
||||||
correspondingField,
|
correspondingField,
|
||||||
objectRecordFilters,
|
objectRecordFilters,
|
||||||
rawUIFilter.definition.type,
|
rawUIFilter.definition,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -525,7 +301,7 @@ export const turnObjectDropdownFilterIntoQueryFilter = (
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'RELATION': {
|
case 'RELATION': {
|
||||||
if (!isValuelessOperand) {
|
if (!isEmptyOperand) {
|
||||||
try {
|
try {
|
||||||
JSON.parse(rawUIFilter.value);
|
JSON.parse(rawUIFilter.value);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -570,7 +346,7 @@ export const turnObjectDropdownFilterIntoQueryFilter = (
|
|||||||
rawUIFilter.operand,
|
rawUIFilter.operand,
|
||||||
correspondingField,
|
correspondingField,
|
||||||
objectRecordFilters,
|
objectRecordFilters,
|
||||||
rawUIFilter.definition.type,
|
rawUIFilter.definition,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -603,7 +379,44 @@ export const turnObjectDropdownFilterIntoQueryFilter = (
|
|||||||
rawUIFilter.operand,
|
rawUIFilter.operand,
|
||||||
correspondingField,
|
correspondingField,
|
||||||
objectRecordFilters,
|
objectRecordFilters,
|
||||||
rawUIFilter.definition.type,
|
rawUIFilter.definition,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(
|
||||||
|
`Unknown operand ${rawUIFilter.operand} for ${rawUIFilter.definition.type} filter`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'LINK':
|
||||||
|
switch (rawUIFilter.operand) {
|
||||||
|
case ViewFilterOperand.Contains:
|
||||||
|
objectRecordFilters.push({
|
||||||
|
[correspondingField.name]: {
|
||||||
|
url: {
|
||||||
|
ilike: `%${rawUIFilter.value}%`,
|
||||||
|
},
|
||||||
|
} as URLFilter,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case ViewFilterOperand.DoesNotContain:
|
||||||
|
objectRecordFilters.push({
|
||||||
|
not: {
|
||||||
|
[correspondingField.name]: {
|
||||||
|
url: {
|
||||||
|
ilike: `%${rawUIFilter.value}%`,
|
||||||
|
},
|
||||||
|
} as URLFilter,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case ViewFilterOperand.IsEmpty:
|
||||||
|
case ViewFilterOperand.IsNotEmpty:
|
||||||
|
applyEmptyFilters(
|
||||||
|
rawUIFilter.operand,
|
||||||
|
correspondingField,
|
||||||
|
objectRecordFilters,
|
||||||
|
rawUIFilter.definition,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -618,20 +431,43 @@ export const turnObjectDropdownFilterIntoQueryFilter = (
|
|||||||
correspondingField.name,
|
correspondingField.name,
|
||||||
['primaryLinkLabel', 'primaryLinkUrl'],
|
['primaryLinkLabel', 'primaryLinkUrl'],
|
||||||
);
|
);
|
||||||
|
|
||||||
switch (rawUIFilter.operand) {
|
switch (rawUIFilter.operand) {
|
||||||
case ViewFilterOperand.Contains:
|
case ViewFilterOperand.Contains:
|
||||||
objectRecordFilters.push({
|
if (!isCompositeFieldFiter) {
|
||||||
or: linksFilters,
|
objectRecordFilters.push({
|
||||||
});
|
or: linksFilters,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
objectRecordFilters.push({
|
||||||
|
[correspondingField.name]: {
|
||||||
|
[compositeFieldName]: {
|
||||||
|
ilike: `%${rawUIFilter.value}%`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case ViewFilterOperand.DoesNotContain:
|
case ViewFilterOperand.DoesNotContain:
|
||||||
objectRecordFilters.push({
|
if (!isCompositeFieldFiter) {
|
||||||
and: linksFilters.map((filter) => {
|
objectRecordFilters.push({
|
||||||
return {
|
and: linksFilters.map((filter) => {
|
||||||
not: filter,
|
return {
|
||||||
};
|
not: filter,
|
||||||
}),
|
};
|
||||||
});
|
}),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
objectRecordFilters.push({
|
||||||
|
not: {
|
||||||
|
[correspondingField.name]: {
|
||||||
|
[compositeFieldName]: {
|
||||||
|
ilike: `%${rawUIFilter.value}%`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case ViewFilterOperand.IsEmpty:
|
case ViewFilterOperand.IsEmpty:
|
||||||
case ViewFilterOperand.IsNotEmpty:
|
case ViewFilterOperand.IsNotEmpty:
|
||||||
@@ -639,7 +475,7 @@ export const turnObjectDropdownFilterIntoQueryFilter = (
|
|||||||
rawUIFilter.operand,
|
rawUIFilter.operand,
|
||||||
correspondingField,
|
correspondingField,
|
||||||
objectRecordFilters,
|
objectRecordFilters,
|
||||||
rawUIFilter.definition.type,
|
rawUIFilter.definition,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -657,18 +493,40 @@ export const turnObjectDropdownFilterIntoQueryFilter = (
|
|||||||
);
|
);
|
||||||
switch (rawUIFilter.operand) {
|
switch (rawUIFilter.operand) {
|
||||||
case ViewFilterOperand.Contains:
|
case ViewFilterOperand.Contains:
|
||||||
objectRecordFilters.push({
|
if (!isCompositeFieldFiter) {
|
||||||
or: fullNameFilters,
|
objectRecordFilters.push({
|
||||||
});
|
or: fullNameFilters,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
objectRecordFilters.push({
|
||||||
|
[correspondingField.name]: {
|
||||||
|
[compositeFieldName]: {
|
||||||
|
ilike: `%${rawUIFilter.value}%`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case ViewFilterOperand.DoesNotContain:
|
case ViewFilterOperand.DoesNotContain:
|
||||||
objectRecordFilters.push({
|
if (!isCompositeFieldFiter) {
|
||||||
and: fullNameFilters.map((filter) => {
|
objectRecordFilters.push({
|
||||||
return {
|
and: fullNameFilters.map((filter) => {
|
||||||
not: filter,
|
return {
|
||||||
};
|
not: filter,
|
||||||
}),
|
};
|
||||||
});
|
}),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
objectRecordFilters.push({
|
||||||
|
not: {
|
||||||
|
[correspondingField.name]: {
|
||||||
|
[compositeFieldName]: {
|
||||||
|
ilike: `%${rawUIFilter.value}%`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case ViewFilterOperand.IsEmpty:
|
case ViewFilterOperand.IsEmpty:
|
||||||
case ViewFilterOperand.IsNotEmpty:
|
case ViewFilterOperand.IsNotEmpty:
|
||||||
@@ -676,7 +534,7 @@ export const turnObjectDropdownFilterIntoQueryFilter = (
|
|||||||
rawUIFilter.operand,
|
rawUIFilter.operand,
|
||||||
correspondingField,
|
correspondingField,
|
||||||
objectRecordFilters,
|
objectRecordFilters,
|
||||||
rawUIFilter.definition.type,
|
rawUIFilter.definition,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -689,85 +547,107 @@ export const turnObjectDropdownFilterIntoQueryFilter = (
|
|||||||
case 'ADDRESS':
|
case 'ADDRESS':
|
||||||
switch (rawUIFilter.operand) {
|
switch (rawUIFilter.operand) {
|
||||||
case ViewFilterOperand.Contains:
|
case ViewFilterOperand.Contains:
|
||||||
objectRecordFilters.push({
|
if (!isCompositeFieldFiter) {
|
||||||
or: [
|
objectRecordFilters.push({
|
||||||
{
|
or: [
|
||||||
[correspondingField.name]: {
|
{
|
||||||
addressStreet1: {
|
|
||||||
ilike: `%${rawUIFilter.value}%`,
|
|
||||||
},
|
|
||||||
} as AddressFilter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[correspondingField.name]: {
|
|
||||||
addressStreet2: {
|
|
||||||
ilike: `%${rawUIFilter.value}%`,
|
|
||||||
},
|
|
||||||
} as AddressFilter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[correspondingField.name]: {
|
|
||||||
addressCity: {
|
|
||||||
ilike: `%${rawUIFilter.value}%`,
|
|
||||||
},
|
|
||||||
} as AddressFilter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[correspondingField.name]: {
|
|
||||||
addressState: {
|
|
||||||
ilike: `%${rawUIFilter.value}%`,
|
|
||||||
},
|
|
||||||
} as AddressFilter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[correspondingField.name]: {
|
|
||||||
addressCountry: {
|
|
||||||
ilike: `%${rawUIFilter.value}%`,
|
|
||||||
},
|
|
||||||
} as AddressFilter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[correspondingField.name]: {
|
|
||||||
addressPostcode: {
|
|
||||||
ilike: `%${rawUIFilter.value}%`,
|
|
||||||
},
|
|
||||||
} as AddressFilter,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case ViewFilterOperand.DoesNotContain:
|
|
||||||
objectRecordFilters.push({
|
|
||||||
and: [
|
|
||||||
{
|
|
||||||
not: {
|
|
||||||
[correspondingField.name]: {
|
[correspondingField.name]: {
|
||||||
addressStreet1: {
|
addressStreet1: {
|
||||||
ilike: `%${rawUIFilter.value}%`,
|
ilike: `%${rawUIFilter.value}%`,
|
||||||
},
|
},
|
||||||
} as AddressFilter,
|
} as AddressFilter,
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
|
||||||
not: {
|
|
||||||
[correspondingField.name]: {
|
[correspondingField.name]: {
|
||||||
addressStreet2: {
|
addressStreet2: {
|
||||||
ilike: `%${rawUIFilter.value}%`,
|
ilike: `%${rawUIFilter.value}%`,
|
||||||
},
|
},
|
||||||
} as AddressFilter,
|
} as AddressFilter,
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
|
||||||
not: {
|
|
||||||
[correspondingField.name]: {
|
[correspondingField.name]: {
|
||||||
addressCity: {
|
addressCity: {
|
||||||
ilike: `%${rawUIFilter.value}%`,
|
ilike: `%${rawUIFilter.value}%`,
|
||||||
},
|
},
|
||||||
} as AddressFilter,
|
} as AddressFilter,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
[correspondingField.name]: {
|
||||||
|
addressState: {
|
||||||
|
ilike: `%${rawUIFilter.value}%`,
|
||||||
|
},
|
||||||
|
} as AddressFilter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[correspondingField.name]: {
|
||||||
|
addressCountry: {
|
||||||
|
ilike: `%${rawUIFilter.value}%`,
|
||||||
|
},
|
||||||
|
} as AddressFilter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[correspondingField.name]: {
|
||||||
|
addressPostcode: {
|
||||||
|
ilike: `%${rawUIFilter.value}%`,
|
||||||
|
},
|
||||||
|
} as AddressFilter,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
objectRecordFilters.push({
|
||||||
|
[correspondingField.name]: {
|
||||||
|
[compositeFieldName]: {
|
||||||
|
ilike: `%${rawUIFilter.value}%`,
|
||||||
|
} as AddressFilter,
|
||||||
},
|
},
|
||||||
],
|
});
|
||||||
});
|
}
|
||||||
|
break;
|
||||||
|
case ViewFilterOperand.DoesNotContain:
|
||||||
|
if (!isCompositeFieldFiter) {
|
||||||
|
objectRecordFilters.push({
|
||||||
|
and: [
|
||||||
|
{
|
||||||
|
not: {
|
||||||
|
[correspondingField.name]: {
|
||||||
|
addressStreet1: {
|
||||||
|
ilike: `%${rawUIFilter.value}%`,
|
||||||
|
},
|
||||||
|
} as AddressFilter,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
not: {
|
||||||
|
[correspondingField.name]: {
|
||||||
|
addressStreet2: {
|
||||||
|
ilike: `%${rawUIFilter.value}%`,
|
||||||
|
},
|
||||||
|
} as AddressFilter,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
not: {
|
||||||
|
[correspondingField.name]: {
|
||||||
|
addressCity: {
|
||||||
|
ilike: `%${rawUIFilter.value}%`,
|
||||||
|
},
|
||||||
|
} as AddressFilter,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
objectRecordFilters.push({
|
||||||
|
not: {
|
||||||
|
[correspondingField.name]: {
|
||||||
|
[compositeFieldName]: {
|
||||||
|
ilike: `%${rawUIFilter.value}%`,
|
||||||
|
} as AddressFilter,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case ViewFilterOperand.IsEmpty:
|
case ViewFilterOperand.IsEmpty:
|
||||||
case ViewFilterOperand.IsNotEmpty:
|
case ViewFilterOperand.IsNotEmpty:
|
||||||
@@ -775,7 +655,7 @@ export const turnObjectDropdownFilterIntoQueryFilter = (
|
|||||||
rawUIFilter.operand,
|
rawUIFilter.operand,
|
||||||
correspondingField,
|
correspondingField,
|
||||||
objectRecordFilters,
|
objectRecordFilters,
|
||||||
rawUIFilter.definition.type,
|
rawUIFilter.definition,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -785,12 +665,12 @@ export const turnObjectDropdownFilterIntoQueryFilter = (
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'SELECT': {
|
case 'SELECT': {
|
||||||
if (isValuelessOperand) {
|
if (isEmptyOperand) {
|
||||||
applyEmptyFilters(
|
applyEmptyFilters(
|
||||||
rawUIFilter.operand,
|
rawUIFilter.operand,
|
||||||
correspondingField,
|
correspondingField,
|
||||||
objectRecordFilters,
|
objectRecordFilters,
|
||||||
rawUIFilter.definition.type,
|
rawUIFilter.definition,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -836,41 +716,33 @@ export const turnObjectDropdownFilterIntoQueryFilter = (
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'ACTOR':
|
case 'ACTOR':
|
||||||
if (rawUIFilter.definition.subFieldType !== undefined) {
|
if (isActorSourceCompositeFilter(rawUIFilter.definition)) {
|
||||||
const parsedRecordIds = JSON.parse(rawUIFilter.value) as string[];
|
const parsedRecordIds = JSON.parse(rawUIFilter.value) as string[];
|
||||||
switch (rawUIFilter.definition.subFieldType) {
|
|
||||||
case 'SOURCE':
|
switch (rawUIFilter.operand) {
|
||||||
switch (rawUIFilter.operand) {
|
case ViewFilterOperand.Is:
|
||||||
case ViewFilterOperand.Is:
|
objectRecordFilters.push({
|
||||||
objectRecordFilters.push({
|
[correspondingField.name]: {
|
||||||
|
source: {
|
||||||
|
in: parsedRecordIds,
|
||||||
|
} as RelationFilter,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
case ViewFilterOperand.IsNot:
|
||||||
|
if (parsedRecordIds.length > 0) {
|
||||||
|
objectRecordFilters.push({
|
||||||
|
not: {
|
||||||
[correspondingField.name]: {
|
[correspondingField.name]: {
|
||||||
source: {
|
source: {
|
||||||
in: parsedRecordIds,
|
in: parsedRecordIds,
|
||||||
} as RelationFilter,
|
} as RelationFilter,
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
|
});
|
||||||
break;
|
|
||||||
case ViewFilterOperand.IsNot:
|
|
||||||
if (parsedRecordIds.length > 0) {
|
|
||||||
objectRecordFilters.push({
|
|
||||||
not: {
|
|
||||||
[correspondingField.name]: {
|
|
||||||
[rawUIFilter.definition.subFieldType.toLowerCase()]: {
|
|
||||||
in: parsedRecordIds,
|
|
||||||
} as RelationFilter,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new Error(
|
|
||||||
`Unknown operand ${rawUIFilter.operand} for ${rawUIFilter.definition.subFieldType} filter`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
switch (rawUIFilter.operand) {
|
switch (rawUIFilter.operand) {
|
||||||
@@ -908,15 +780,14 @@ export const turnObjectDropdownFilterIntoQueryFilter = (
|
|||||||
rawUIFilter.operand,
|
rawUIFilter.operand,
|
||||||
correspondingField,
|
correspondingField,
|
||||||
objectRecordFilters,
|
objectRecordFilters,
|
||||||
rawUIFilter.definition.type,
|
rawUIFilter.definition,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unknown operand ${rawUIFilter.operand} for ${rawUIFilter.definition.type} filter`,
|
`Unknown operand ${rawUIFilter.operand} for ${rawUIFilter.definition.label} filter`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'EMAILS':
|
case 'EMAILS':
|
||||||
@@ -955,7 +826,7 @@ export const turnObjectDropdownFilterIntoQueryFilter = (
|
|||||||
rawUIFilter.operand,
|
rawUIFilter.operand,
|
||||||
correspondingField,
|
correspondingField,
|
||||||
objectRecordFilters,
|
objectRecordFilters,
|
||||||
rawUIFilter.definition.type,
|
rawUIFilter.definition,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -991,7 +862,7 @@ export const turnObjectDropdownFilterIntoQueryFilter = (
|
|||||||
rawUIFilter.operand,
|
rawUIFilter.operand,
|
||||||
correspondingField,
|
correspondingField,
|
||||||
objectRecordFilters,
|
objectRecordFilters,
|
||||||
rawUIFilter.definition.type,
|
rawUIFilter.definition,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/u
|
|||||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||||
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
|
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
|
||||||
import { getOperandsForFilterType } from '@/object-record/object-filter-dropdown/utils/getOperandsForFilterType';
|
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
|
||||||
|
import { getOperandsForFilterDefinition } from '@/object-record/object-filter-dropdown/utils/getOperandsForFilterType';
|
||||||
import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
|
import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
|
||||||
import { useUpsertCombinedViewFilters } from '@/views/hooks/useUpsertCombinedViewFilters';
|
import { useUpsertCombinedViewFilters } from '@/views/hooks/useUpsertCombinedViewFilters';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
@@ -42,7 +43,15 @@ export const useHandleToggleColumnFilter = ({
|
|||||||
correspondingColumnDefinition?.type,
|
correspondingColumnDefinition?.type,
|
||||||
);
|
);
|
||||||
|
|
||||||
const availableOperandsForFilter = getOperandsForFilterType(filterType);
|
const filterDefinition = {
|
||||||
|
label: correspondingColumnDefinition.label,
|
||||||
|
iconName: correspondingColumnDefinition.iconName,
|
||||||
|
fieldMetadataId,
|
||||||
|
type: filterType,
|
||||||
|
} satisfies FilterDefinition;
|
||||||
|
|
||||||
|
const availableOperandsForFilter =
|
||||||
|
getOperandsForFilterDefinition(filterDefinition);
|
||||||
|
|
||||||
const defaultOperand = availableOperandsForFilter[0];
|
const defaultOperand = availableOperandsForFilter[0];
|
||||||
|
|
||||||
@@ -51,12 +60,7 @@ export const useHandleToggleColumnFilter = ({
|
|||||||
fieldMetadataId,
|
fieldMetadataId,
|
||||||
operand: defaultOperand,
|
operand: defaultOperand,
|
||||||
displayValue: '',
|
displayValue: '',
|
||||||
definition: {
|
definition: filterDefinition,
|
||||||
label: correspondingColumnDefinition.label,
|
|
||||||
iconName: correspondingColumnDefinition.iconName,
|
|
||||||
fieldMetadataId,
|
|
||||||
type: filterType,
|
|
||||||
},
|
|
||||||
value: '',
|
value: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,190 @@
|
|||||||
|
import { CurrencyCode } from '@/object-record/record-field/types/CurrencyCode';
|
||||||
|
import {
|
||||||
|
FieldActorValue,
|
||||||
|
FieldAddressValue,
|
||||||
|
FieldCurrencyValue,
|
||||||
|
FieldEmailsValue,
|
||||||
|
FieldFullNameValue,
|
||||||
|
FieldLinksValue,
|
||||||
|
FieldLinkValue,
|
||||||
|
FieldPhonesValue,
|
||||||
|
} from '@/object-record/record-field/types/FieldMetadata';
|
||||||
|
import { SettingsFieldTypeConfig } from '@/settings/data-model/constants/SettingsNonCompositeFieldTypeConfigs';
|
||||||
|
import { CompositeFieldType } from '@/settings/data-model/types/CompositeFieldType';
|
||||||
|
import {
|
||||||
|
IllustrationIconCurrency,
|
||||||
|
IllustrationIconLink,
|
||||||
|
IllustrationIconMail,
|
||||||
|
IllustrationIconMap,
|
||||||
|
IllustrationIconPhone,
|
||||||
|
IllustrationIconSetting,
|
||||||
|
IllustrationIconUser,
|
||||||
|
} from 'twenty-ui';
|
||||||
|
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
|
export type SettingsCompositeFieldTypeConfig<T> = SettingsFieldTypeConfig<T> & {
|
||||||
|
subFields: (keyof T)[];
|
||||||
|
filterableSubFields: (keyof T)[];
|
||||||
|
labelBySubField: Record<keyof T, string>;
|
||||||
|
exampleValue: T;
|
||||||
|
};
|
||||||
|
|
||||||
|
type SettingsCompositeFieldTypeConfigArray = Record<
|
||||||
|
CompositeFieldType,
|
||||||
|
SettingsCompositeFieldTypeConfig<any>
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS = {
|
||||||
|
[FieldMetadataType.Currency]: {
|
||||||
|
label: 'Currency',
|
||||||
|
Icon: IllustrationIconCurrency,
|
||||||
|
subFields: ['amountMicros', 'currencyCode'],
|
||||||
|
filterableSubFields: ['amountMicros', 'currencyCode'],
|
||||||
|
labelBySubField: {
|
||||||
|
amountMicros: 'Amount',
|
||||||
|
currencyCode: 'Currency',
|
||||||
|
},
|
||||||
|
exampleValue: {
|
||||||
|
amountMicros: 2000000000,
|
||||||
|
currencyCode: CurrencyCode.USD,
|
||||||
|
},
|
||||||
|
category: 'Basic',
|
||||||
|
} as const satisfies SettingsCompositeFieldTypeConfig<FieldCurrencyValue>,
|
||||||
|
[FieldMetadataType.Emails]: {
|
||||||
|
label: 'Emails',
|
||||||
|
Icon: IllustrationIconMail,
|
||||||
|
subFields: ['primaryEmail', 'additionalEmails'],
|
||||||
|
filterableSubFields: ['primaryEmail'],
|
||||||
|
labelBySubField: {
|
||||||
|
primaryEmail: 'Primary Email',
|
||||||
|
additionalEmails: 'Additional Emails',
|
||||||
|
},
|
||||||
|
exampleValue: {
|
||||||
|
primaryEmail: 'john@twenty.com',
|
||||||
|
additionalEmails: [
|
||||||
|
'tim@twenty.com',
|
||||||
|
'timapple@twenty.com',
|
||||||
|
'johnappletim@twenty.com',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
category: 'Basic',
|
||||||
|
} as const satisfies SettingsCompositeFieldTypeConfig<FieldEmailsValue>,
|
||||||
|
[FieldMetadataType.Link]: {
|
||||||
|
label: 'Link',
|
||||||
|
Icon: IllustrationIconLink,
|
||||||
|
exampleValue: { url: 'www.twenty.com', label: '' },
|
||||||
|
category: 'Basic',
|
||||||
|
subFields: ['url', 'label'],
|
||||||
|
filterableSubFields: ['url', 'label'],
|
||||||
|
labelBySubField: {
|
||||||
|
url: 'URL',
|
||||||
|
label: 'Label',
|
||||||
|
},
|
||||||
|
} as const satisfies SettingsCompositeFieldTypeConfig<FieldLinkValue>,
|
||||||
|
[FieldMetadataType.Links]: {
|
||||||
|
label: 'Links',
|
||||||
|
Icon: IllustrationIconLink,
|
||||||
|
exampleValue: {
|
||||||
|
primaryLinkUrl: 'twenty.com',
|
||||||
|
primaryLinkLabel: '',
|
||||||
|
secondaryLinks: [{ url: 'twenty.com', label: 'Twenty' }],
|
||||||
|
},
|
||||||
|
category: 'Basic',
|
||||||
|
subFields: ['primaryLinkUrl', 'primaryLinkLabel', 'secondaryLinks'],
|
||||||
|
filterableSubFields: ['primaryLinkUrl', 'primaryLinkLabel'],
|
||||||
|
labelBySubField: {
|
||||||
|
primaryLinkUrl: 'Link URL',
|
||||||
|
primaryLinkLabel: 'Link Label',
|
||||||
|
secondaryLinks: 'Secondary Links',
|
||||||
|
},
|
||||||
|
} as const satisfies SettingsCompositeFieldTypeConfig<FieldLinksValue>,
|
||||||
|
[FieldMetadataType.Phones]: {
|
||||||
|
label: 'Phones',
|
||||||
|
Icon: IllustrationIconPhone,
|
||||||
|
exampleValue: {
|
||||||
|
primaryPhoneNumber: '234-567-890',
|
||||||
|
primaryPhoneCountryCode: '+1',
|
||||||
|
additionalPhones: [{ number: '234-567-890', countryCode: '+1' }],
|
||||||
|
},
|
||||||
|
subFields: [
|
||||||
|
'primaryPhoneNumber',
|
||||||
|
'primaryPhoneCountryCode',
|
||||||
|
'additionalPhones',
|
||||||
|
],
|
||||||
|
filterableSubFields: ['primaryPhoneNumber', 'primaryPhoneCountryCode'],
|
||||||
|
labelBySubField: {
|
||||||
|
primaryPhoneNumber: 'Primary Phone Number',
|
||||||
|
primaryPhoneCountryCode: 'Primary Phone Country Code',
|
||||||
|
additionalPhones: 'Additional Phones',
|
||||||
|
},
|
||||||
|
category: 'Basic',
|
||||||
|
} as const satisfies SettingsCompositeFieldTypeConfig<FieldPhonesValue>,
|
||||||
|
[FieldMetadataType.FullName]: {
|
||||||
|
label: 'Full Name',
|
||||||
|
Icon: IllustrationIconUser,
|
||||||
|
exampleValue: { firstName: 'John', lastName: 'Doe' },
|
||||||
|
category: 'Advanced',
|
||||||
|
subFields: ['firstName', 'lastName'],
|
||||||
|
filterableSubFields: ['firstName', 'lastName'],
|
||||||
|
labelBySubField: {
|
||||||
|
firstName: 'First Name',
|
||||||
|
lastName: 'Last Name',
|
||||||
|
},
|
||||||
|
} as const satisfies SettingsCompositeFieldTypeConfig<FieldFullNameValue>,
|
||||||
|
[FieldMetadataType.Address]: {
|
||||||
|
label: 'Address',
|
||||||
|
Icon: IllustrationIconMap,
|
||||||
|
subFields: [
|
||||||
|
'addressStreet1',
|
||||||
|
'addressStreet2',
|
||||||
|
'addressCity',
|
||||||
|
'addressState',
|
||||||
|
'addressCountry',
|
||||||
|
'addressPostcode',
|
||||||
|
'addressLat',
|
||||||
|
'addressLng',
|
||||||
|
],
|
||||||
|
filterableSubFields: [
|
||||||
|
'addressStreet1',
|
||||||
|
'addressStreet2',
|
||||||
|
'addressCity',
|
||||||
|
'addressState',
|
||||||
|
'addressCountry',
|
||||||
|
'addressPostcode',
|
||||||
|
],
|
||||||
|
labelBySubField: {
|
||||||
|
addressStreet1: 'Address 1',
|
||||||
|
addressStreet2: 'Address 2',
|
||||||
|
addressCity: 'City',
|
||||||
|
addressState: 'State',
|
||||||
|
addressCountry: 'Country',
|
||||||
|
addressPostcode: 'Post Code',
|
||||||
|
addressLat: 'Latitude',
|
||||||
|
addressLng: 'Longitude',
|
||||||
|
},
|
||||||
|
exampleValue: {
|
||||||
|
addressStreet1: '456 Oak Street',
|
||||||
|
addressStreet2: 'Unit 3B',
|
||||||
|
addressCity: 'Springfield',
|
||||||
|
addressState: 'California',
|
||||||
|
addressCountry: 'United States',
|
||||||
|
addressPostcode: '90210',
|
||||||
|
addressLat: 34.0522,
|
||||||
|
addressLng: -118.2437,
|
||||||
|
},
|
||||||
|
category: 'Basic',
|
||||||
|
} as const satisfies SettingsCompositeFieldTypeConfig<FieldAddressValue>,
|
||||||
|
[FieldMetadataType.Actor]: {
|
||||||
|
label: 'Actor',
|
||||||
|
Icon: IllustrationIconSetting,
|
||||||
|
category: 'Basic',
|
||||||
|
subFields: ['source', 'name', 'workspaceMemberId'],
|
||||||
|
filterableSubFields: ['source', 'name', 'workspaceMemberId'],
|
||||||
|
labelBySubField: {
|
||||||
|
source: 'Source',
|
||||||
|
name: 'Name',
|
||||||
|
workspaceMemberId: 'Workspace Member ID',
|
||||||
|
},
|
||||||
|
exampleValue: { source: 'source', name: 'name', workspaceMemberId: 'id' },
|
||||||
|
} as const satisfies SettingsCompositeFieldTypeConfig<FieldActorValue>,
|
||||||
|
} as const satisfies SettingsCompositeFieldTypeConfigArray;
|
||||||
@@ -1,196 +1,7 @@
|
|||||||
import {
|
import { SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsCompositeFieldTypeConfigs';
|
||||||
IconComponent,
|
import { SETTINGS_NON_COMPOSITE_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsNonCompositeFieldTypeConfigs';
|
||||||
IllustrationIconArray,
|
|
||||||
IllustrationIconCalendarEvent,
|
|
||||||
IllustrationIconCalendarTime,
|
|
||||||
IllustrationIconCurrency,
|
|
||||||
IllustrationIconJson,
|
|
||||||
IllustrationIconLink,
|
|
||||||
IllustrationIconMail,
|
|
||||||
IllustrationIconMap,
|
|
||||||
IllustrationIconNumbers,
|
|
||||||
IllustrationIconOneToMany,
|
|
||||||
IllustrationIconPhone,
|
|
||||||
IllustrationIconSetting,
|
|
||||||
IllustrationIconStar,
|
|
||||||
IllustrationIconTag,
|
|
||||||
IllustrationIconTags,
|
|
||||||
IllustrationIconText,
|
|
||||||
IllustrationIconToggle,
|
|
||||||
IllustrationIconUid,
|
|
||||||
IllustrationIconUser,
|
|
||||||
} from 'twenty-ui';
|
|
||||||
|
|
||||||
import { CurrencyCode } from '@/object-record/record-field/types/CurrencyCode';
|
|
||||||
import { DEFAULT_DATE_VALUE } from '@/settings/data-model/constants/DefaultDateValue';
|
|
||||||
import { SettingsFieldTypeCategoryType } from '@/settings/data-model/types/SettingsFieldTypeCategoryType';
|
|
||||||
import { SettingsSupportedFieldType } from '@/settings/data-model/types/SettingsSupportedFieldType';
|
|
||||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
|
||||||
|
|
||||||
DEFAULT_DATE_VALUE.setFullYear(DEFAULT_DATE_VALUE.getFullYear() + 2);
|
|
||||||
|
|
||||||
export type SettingsFieldTypeConfig = {
|
|
||||||
label: string;
|
|
||||||
Icon: IconComponent;
|
|
||||||
exampleValue?: unknown;
|
|
||||||
category: SettingsFieldTypeCategoryType;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SETTINGS_FIELD_TYPE_CONFIGS = {
|
export const SETTINGS_FIELD_TYPE_CONFIGS = {
|
||||||
[FieldMetadataType.Uuid]: {
|
...SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS,
|
||||||
label: 'Unique ID',
|
...SETTINGS_NON_COMPOSITE_FIELD_TYPE_CONFIGS,
|
||||||
Icon: IllustrationIconUid,
|
};
|
||||||
exampleValue: '00000000-0000-0000-0000-000000000000',
|
|
||||||
category: 'Advanced',
|
|
||||||
},
|
|
||||||
[FieldMetadataType.Text]: {
|
|
||||||
label: 'Text',
|
|
||||||
Icon: IllustrationIconText,
|
|
||||||
exampleValue:
|
|
||||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum magna enim, dapibus non enim in, lacinia faucibus nunc. Sed interdum ante sed felis facilisis, eget ultricies neque molestie. Mauris auctor, justo eu volutpat cursus, libero erat tempus nulla, non sodales lorem lacus a est.',
|
|
||||||
category: 'Basic',
|
|
||||||
},
|
|
||||||
[FieldMetadataType.Numeric]: {
|
|
||||||
label: 'Numeric',
|
|
||||||
Icon: IllustrationIconNumbers,
|
|
||||||
exampleValue: 2000,
|
|
||||||
category: 'Basic',
|
|
||||||
},
|
|
||||||
[FieldMetadataType.Number]: {
|
|
||||||
label: 'Number',
|
|
||||||
Icon: IllustrationIconNumbers,
|
|
||||||
exampleValue: 2000,
|
|
||||||
category: 'Basic',
|
|
||||||
},
|
|
||||||
[FieldMetadataType.Link]: {
|
|
||||||
label: 'Link',
|
|
||||||
Icon: IllustrationIconLink,
|
|
||||||
exampleValue: { url: 'www.twenty.com', label: '' },
|
|
||||||
category: 'Basic',
|
|
||||||
},
|
|
||||||
[FieldMetadataType.Links]: {
|
|
||||||
label: 'Links',
|
|
||||||
Icon: IllustrationIconLink,
|
|
||||||
exampleValue: { primaryLinkUrl: 'twenty.com', primaryLinkLabel: '' },
|
|
||||||
category: 'Basic',
|
|
||||||
},
|
|
||||||
[FieldMetadataType.Boolean]: {
|
|
||||||
label: 'True/False',
|
|
||||||
Icon: IllustrationIconToggle,
|
|
||||||
exampleValue: true,
|
|
||||||
category: 'Basic',
|
|
||||||
},
|
|
||||||
[FieldMetadataType.DateTime]: {
|
|
||||||
label: 'Date and Time',
|
|
||||||
Icon: IllustrationIconCalendarTime,
|
|
||||||
exampleValue: DEFAULT_DATE_VALUE.toISOString(),
|
|
||||||
category: 'Basic',
|
|
||||||
},
|
|
||||||
[FieldMetadataType.Date]: {
|
|
||||||
label: 'Date',
|
|
||||||
Icon: IllustrationIconCalendarEvent,
|
|
||||||
exampleValue: DEFAULT_DATE_VALUE.toISOString(),
|
|
||||||
category: 'Basic',
|
|
||||||
},
|
|
||||||
[FieldMetadataType.Select]: {
|
|
||||||
label: 'Select',
|
|
||||||
Icon: IllustrationIconTag,
|
|
||||||
category: 'Basic',
|
|
||||||
},
|
|
||||||
[FieldMetadataType.MultiSelect]: {
|
|
||||||
label: 'Multi-select',
|
|
||||||
Icon: IllustrationIconTags,
|
|
||||||
category: 'Basic',
|
|
||||||
},
|
|
||||||
[FieldMetadataType.Currency]: {
|
|
||||||
label: 'Currency',
|
|
||||||
Icon: IllustrationIconCurrency,
|
|
||||||
exampleValue: { amountMicros: 2000000000, currencyCode: CurrencyCode.USD },
|
|
||||||
category: 'Basic',
|
|
||||||
},
|
|
||||||
[FieldMetadataType.Relation]: {
|
|
||||||
label: 'Relation',
|
|
||||||
Icon: IllustrationIconOneToMany,
|
|
||||||
category: 'Relation',
|
|
||||||
},
|
|
||||||
[FieldMetadataType.Email]: {
|
|
||||||
label: 'Email',
|
|
||||||
Icon: IllustrationIconMail,
|
|
||||||
category: 'Basic',
|
|
||||||
},
|
|
||||||
[FieldMetadataType.Emails]: {
|
|
||||||
label: 'Emails',
|
|
||||||
Icon: IllustrationIconMail,
|
|
||||||
exampleValue: { primaryEmail: 'john@twenty.com' },
|
|
||||||
category: 'Basic',
|
|
||||||
},
|
|
||||||
[FieldMetadataType.Phone]: {
|
|
||||||
label: 'Phone',
|
|
||||||
Icon: IllustrationIconPhone,
|
|
||||||
exampleValue: '+1234-567-890',
|
|
||||||
category: 'Basic',
|
|
||||||
},
|
|
||||||
[FieldMetadataType.Phones]: {
|
|
||||||
label: 'Phones',
|
|
||||||
Icon: IllustrationIconPhone,
|
|
||||||
exampleValue: {
|
|
||||||
primaryPhoneNumber: '234-567-890',
|
|
||||||
primaryPhoneCountryCode: '+1',
|
|
||||||
},
|
|
||||||
category: 'Basic',
|
|
||||||
},
|
|
||||||
[FieldMetadataType.Rating]: {
|
|
||||||
label: 'Rating',
|
|
||||||
Icon: IllustrationIconStar,
|
|
||||||
exampleValue: '3',
|
|
||||||
category: 'Basic',
|
|
||||||
},
|
|
||||||
[FieldMetadataType.FullName]: {
|
|
||||||
label: 'Full Name',
|
|
||||||
Icon: IllustrationIconUser,
|
|
||||||
exampleValue: { firstName: 'John', lastName: 'Doe' },
|
|
||||||
category: 'Advanced',
|
|
||||||
},
|
|
||||||
[FieldMetadataType.Address]: {
|
|
||||||
label: 'Address',
|
|
||||||
Icon: IllustrationIconMap,
|
|
||||||
exampleValue: {
|
|
||||||
addressStreet1: '456 Oak Street',
|
|
||||||
addressStreet2: 'Unit 3B',
|
|
||||||
addressCity: 'Springfield',
|
|
||||||
addressState: 'California',
|
|
||||||
addressCountry: 'United States',
|
|
||||||
addressPostcode: '90210',
|
|
||||||
addressLat: 34.0522,
|
|
||||||
addressLng: -118.2437,
|
|
||||||
},
|
|
||||||
category: 'Basic',
|
|
||||||
},
|
|
||||||
[FieldMetadataType.RawJson]: {
|
|
||||||
label: 'JSON',
|
|
||||||
Icon: IllustrationIconJson,
|
|
||||||
exampleValue: { key: 'value' },
|
|
||||||
|
|
||||||
category: 'Basic',
|
|
||||||
},
|
|
||||||
[FieldMetadataType.RichText]: {
|
|
||||||
label: 'System',
|
|
||||||
Icon: IllustrationIconSetting,
|
|
||||||
exampleValue: { key: 'value' },
|
|
||||||
category: 'Basic',
|
|
||||||
},
|
|
||||||
[FieldMetadataType.Actor]: {
|
|
||||||
label: 'System',
|
|
||||||
Icon: IllustrationIconSetting,
|
|
||||||
category: 'Basic',
|
|
||||||
},
|
|
||||||
[FieldMetadataType.Array]: {
|
|
||||||
label: 'Array',
|
|
||||||
Icon: IllustrationIconArray,
|
|
||||||
category: 'Basic',
|
|
||||||
exampleValue: ['value1', 'value2'],
|
|
||||||
},
|
|
||||||
} as const satisfies Record<
|
|
||||||
SettingsSupportedFieldType,
|
|
||||||
SettingsFieldTypeConfig
|
|
||||||
>;
|
|
||||||
|
|||||||
@@ -0,0 +1,152 @@
|
|||||||
|
import {
|
||||||
|
IconComponent,
|
||||||
|
IllustrationIconArray,
|
||||||
|
IllustrationIconCalendarEvent,
|
||||||
|
IllustrationIconCalendarTime,
|
||||||
|
IllustrationIconJson,
|
||||||
|
IllustrationIconMail,
|
||||||
|
IllustrationIconNumbers,
|
||||||
|
IllustrationIconOneToMany,
|
||||||
|
IllustrationIconPhone,
|
||||||
|
IllustrationIconSetting,
|
||||||
|
IllustrationIconStar,
|
||||||
|
IllustrationIconTag,
|
||||||
|
IllustrationIconTags,
|
||||||
|
IllustrationIconText,
|
||||||
|
IllustrationIconToggle,
|
||||||
|
IllustrationIconUid,
|
||||||
|
} from 'twenty-ui';
|
||||||
|
|
||||||
|
import {
|
||||||
|
FieldArrayValue,
|
||||||
|
FieldBooleanValue,
|
||||||
|
FieldDateTimeValue,
|
||||||
|
FieldDateValue,
|
||||||
|
FieldEmailValue,
|
||||||
|
FieldJsonValue,
|
||||||
|
FieldMultiSelectValue,
|
||||||
|
FieldNumberValue,
|
||||||
|
FieldPhoneValue,
|
||||||
|
FieldRatingValue,
|
||||||
|
FieldRelationValue,
|
||||||
|
FieldRichTextValue,
|
||||||
|
FieldSelectValue,
|
||||||
|
FieldTextValue,
|
||||||
|
FieldUUidValue,
|
||||||
|
} from '@/object-record/record-field/types/FieldMetadata';
|
||||||
|
import { DEFAULT_DATE_VALUE } from '@/settings/data-model/constants/DefaultDateValue';
|
||||||
|
import { SettingsFieldTypeCategoryType } from '@/settings/data-model/types/SettingsFieldTypeCategoryType';
|
||||||
|
import { SettingsNonCompositeFieldType } from '@/settings/data-model/types/SettingsNonCompositeFieldType';
|
||||||
|
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
|
DEFAULT_DATE_VALUE.setFullYear(DEFAULT_DATE_VALUE.getFullYear() + 2);
|
||||||
|
|
||||||
|
export type SettingsFieldTypeConfig<T> = {
|
||||||
|
label: string;
|
||||||
|
Icon: IconComponent;
|
||||||
|
exampleValue?: T;
|
||||||
|
category: SettingsFieldTypeCategoryType;
|
||||||
|
};
|
||||||
|
|
||||||
|
type SettingsNonCompositeFieldTypeConfigArray = Record<
|
||||||
|
SettingsNonCompositeFieldType,
|
||||||
|
SettingsFieldTypeConfig<any>
|
||||||
|
>;
|
||||||
|
|
||||||
|
// TODO: can we derive this from backend definitions ?
|
||||||
|
export const SETTINGS_NON_COMPOSITE_FIELD_TYPE_CONFIGS: SettingsNonCompositeFieldTypeConfigArray =
|
||||||
|
{
|
||||||
|
[FieldMetadataType.Uuid]: {
|
||||||
|
label: 'Unique ID',
|
||||||
|
Icon: IllustrationIconUid,
|
||||||
|
exampleValue: '00000000-0000-0000-0000-000000000000',
|
||||||
|
category: 'Advanced',
|
||||||
|
} as const satisfies SettingsFieldTypeConfig<FieldUUidValue>,
|
||||||
|
[FieldMetadataType.Text]: {
|
||||||
|
label: 'Text',
|
||||||
|
Icon: IllustrationIconText,
|
||||||
|
exampleValue:
|
||||||
|
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum magna enim, dapibus non enim in, lacinia faucibus nunc. Sed interdum ante sed felis facilisis, eget ultricies neque molestie. Mauris auctor, justo eu volutpat cursus, libero erat tempus nulla, non sodales lorem lacus a est.',
|
||||||
|
category: 'Basic',
|
||||||
|
} as const satisfies SettingsFieldTypeConfig<FieldTextValue>,
|
||||||
|
[FieldMetadataType.Numeric]: {
|
||||||
|
label: 'Numeric',
|
||||||
|
Icon: IllustrationIconNumbers,
|
||||||
|
exampleValue: 2000,
|
||||||
|
category: 'Basic',
|
||||||
|
} as const satisfies SettingsFieldTypeConfig<FieldNumberValue>,
|
||||||
|
[FieldMetadataType.Number]: {
|
||||||
|
label: 'Number',
|
||||||
|
Icon: IllustrationIconNumbers,
|
||||||
|
exampleValue: 2000,
|
||||||
|
category: 'Basic',
|
||||||
|
} as const satisfies SettingsFieldTypeConfig<FieldNumberValue>,
|
||||||
|
[FieldMetadataType.Boolean]: {
|
||||||
|
label: 'True/False',
|
||||||
|
Icon: IllustrationIconToggle,
|
||||||
|
exampleValue: true,
|
||||||
|
category: 'Basic',
|
||||||
|
} as const satisfies SettingsFieldTypeConfig<FieldBooleanValue>,
|
||||||
|
[FieldMetadataType.DateTime]: {
|
||||||
|
label: 'Date and Time',
|
||||||
|
Icon: IllustrationIconCalendarTime,
|
||||||
|
exampleValue: DEFAULT_DATE_VALUE.toISOString(),
|
||||||
|
category: 'Basic',
|
||||||
|
} as const satisfies SettingsFieldTypeConfig<FieldDateTimeValue>,
|
||||||
|
[FieldMetadataType.Date]: {
|
||||||
|
label: 'Date',
|
||||||
|
Icon: IllustrationIconCalendarEvent,
|
||||||
|
exampleValue: DEFAULT_DATE_VALUE.toISOString(),
|
||||||
|
category: 'Basic',
|
||||||
|
} as const satisfies SettingsFieldTypeConfig<FieldDateValue>,
|
||||||
|
[FieldMetadataType.Select]: {
|
||||||
|
label: 'Select',
|
||||||
|
Icon: IllustrationIconTag,
|
||||||
|
category: 'Basic',
|
||||||
|
} as const satisfies SettingsFieldTypeConfig<FieldSelectValue>,
|
||||||
|
[FieldMetadataType.MultiSelect]: {
|
||||||
|
label: 'Multi-select',
|
||||||
|
Icon: IllustrationIconTags,
|
||||||
|
category: 'Basic',
|
||||||
|
} as const satisfies SettingsFieldTypeConfig<FieldMultiSelectValue>,
|
||||||
|
[FieldMetadataType.Relation]: {
|
||||||
|
label: 'Relation',
|
||||||
|
Icon: IllustrationIconOneToMany,
|
||||||
|
category: 'Relation',
|
||||||
|
} as const satisfies SettingsFieldTypeConfig<FieldRelationValue<any>>,
|
||||||
|
[FieldMetadataType.Email]: {
|
||||||
|
label: 'Email',
|
||||||
|
Icon: IllustrationIconMail,
|
||||||
|
category: 'Basic',
|
||||||
|
} as const satisfies SettingsFieldTypeConfig<FieldEmailValue>,
|
||||||
|
[FieldMetadataType.Phone]: {
|
||||||
|
label: 'Phone',
|
||||||
|
Icon: IllustrationIconPhone,
|
||||||
|
exampleValue: '+1234-567-890',
|
||||||
|
category: 'Basic',
|
||||||
|
} as const satisfies SettingsFieldTypeConfig<FieldPhoneValue>,
|
||||||
|
[FieldMetadataType.Rating]: {
|
||||||
|
label: 'Rating',
|
||||||
|
Icon: IllustrationIconStar,
|
||||||
|
exampleValue: 'RATING_3',
|
||||||
|
category: 'Basic',
|
||||||
|
} as const satisfies SettingsFieldTypeConfig<FieldRatingValue>,
|
||||||
|
[FieldMetadataType.RawJson]: {
|
||||||
|
label: 'JSON',
|
||||||
|
Icon: IllustrationIconJson,
|
||||||
|
exampleValue: { key: 'value' },
|
||||||
|
category: 'Basic',
|
||||||
|
} as const satisfies SettingsFieldTypeConfig<FieldJsonValue>,
|
||||||
|
[FieldMetadataType.RichText]: {
|
||||||
|
label: 'Rich Text',
|
||||||
|
Icon: IllustrationIconSetting,
|
||||||
|
exampleValue: { key: 'value' },
|
||||||
|
category: 'Basic',
|
||||||
|
} as const satisfies SettingsFieldTypeConfig<FieldRichTextValue>,
|
||||||
|
[FieldMetadataType.Array]: {
|
||||||
|
label: 'Array',
|
||||||
|
Icon: IllustrationIconArray,
|
||||||
|
category: 'Basic',
|
||||||
|
exampleValue: ['value1', 'value2'],
|
||||||
|
} as const satisfies SettingsFieldTypeConfig<FieldArrayValue>,
|
||||||
|
};
|
||||||
@@ -2,14 +2,13 @@ import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
|||||||
import { SettingsCard } from '@/settings/components/SettingsCard';
|
import { SettingsCard } from '@/settings/components/SettingsCard';
|
||||||
import { SETTINGS_FIELD_TYPE_CATEGORIES } from '@/settings/data-model/constants/SettingsFieldTypeCategories';
|
import { SETTINGS_FIELD_TYPE_CATEGORIES } from '@/settings/data-model/constants/SettingsFieldTypeCategories';
|
||||||
import { SETTINGS_FIELD_TYPE_CATEGORY_DESCRIPTIONS } from '@/settings/data-model/constants/SettingsFieldTypeCategoryDescriptions';
|
import { SETTINGS_FIELD_TYPE_CATEGORY_DESCRIPTIONS } from '@/settings/data-model/constants/SettingsFieldTypeCategoryDescriptions';
|
||||||
import {
|
import { SETTINGS_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsFieldTypeConfigs';
|
||||||
SETTINGS_FIELD_TYPE_CONFIGS,
|
import { SettingsFieldTypeConfig } from '@/settings/data-model/constants/SettingsNonCompositeFieldTypeConfigs';
|
||||||
SettingsFieldTypeConfig,
|
|
||||||
} from '@/settings/data-model/constants/SettingsFieldTypeConfigs';
|
|
||||||
import { useBooleanSettingsFormInitialValues } from '@/settings/data-model/fields/forms/boolean/hooks/useBooleanSettingsFormInitialValues';
|
import { useBooleanSettingsFormInitialValues } from '@/settings/data-model/fields/forms/boolean/hooks/useBooleanSettingsFormInitialValues';
|
||||||
import { useCurrencySettingsFormInitialValues } from '@/settings/data-model/fields/forms/currency/hooks/useCurrencySettingsFormInitialValues';
|
import { useCurrencySettingsFormInitialValues } from '@/settings/data-model/fields/forms/currency/hooks/useCurrencySettingsFormInitialValues';
|
||||||
import { useSelectSettingsFormInitialValues } from '@/settings/data-model/fields/forms/select/hooks/useSelectSettingsFormInitialValues';
|
import { useSelectSettingsFormInitialValues } from '@/settings/data-model/fields/forms/select/hooks/useSelectSettingsFormInitialValues';
|
||||||
import { SettingsSupportedFieldType } from '@/settings/data-model/types/SettingsSupportedFieldType';
|
import { SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType';
|
||||||
import { TextInput } from '@/ui/input/components/TextInput';
|
import { TextInput } from '@/ui/input/components/TextInput';
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
@@ -23,8 +22,8 @@ import { FieldMetadataType } from '~/generated-metadata/graphql';
|
|||||||
export const settingsDataModelFieldTypeFormSchema = z.object({
|
export const settingsDataModelFieldTypeFormSchema = z.object({
|
||||||
type: z.enum(
|
type: z.enum(
|
||||||
Object.keys(SETTINGS_FIELD_TYPE_CONFIGS) as [
|
Object.keys(SETTINGS_FIELD_TYPE_CONFIGS) as [
|
||||||
SettingsSupportedFieldType,
|
SettingsFieldType,
|
||||||
...SettingsSupportedFieldType[],
|
...SettingsFieldType[],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
@@ -35,7 +34,7 @@ export type SettingsDataModelFieldTypeFormValues = z.infer<
|
|||||||
|
|
||||||
type SettingsDataModelFieldTypeSelectProps = {
|
type SettingsDataModelFieldTypeSelectProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
excludedFieldTypes?: SettingsSupportedFieldType[];
|
excludedFieldTypes?: SettingsFieldType[];
|
||||||
fieldMetadataItem?: Pick<
|
fieldMetadataItem?: Pick<
|
||||||
FieldMetadataItem,
|
FieldMetadataItem,
|
||||||
'defaultValue' | 'options' | 'type'
|
'defaultValue' | 'options' | 'type'
|
||||||
@@ -78,11 +77,11 @@ export const SettingsDataModelFieldTypeSelect = ({
|
|||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { control } = useFormContext<SettingsDataModelFieldTypeFormValues>();
|
const { control } = useFormContext<SettingsDataModelFieldTypeFormValues>();
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
const fieldTypeConfigs = Object.entries<SettingsFieldTypeConfig>(
|
const fieldTypeConfigs = Object.entries<SettingsFieldTypeConfig<any>>(
|
||||||
SETTINGS_FIELD_TYPE_CONFIGS,
|
SETTINGS_FIELD_TYPE_CONFIGS,
|
||||||
).filter(
|
).filter(
|
||||||
([key, config]) =>
|
([key, config]) =>
|
||||||
!excludedFieldTypes.includes(key as SettingsSupportedFieldType) &&
|
!excludedFieldTypes.includes(key as SettingsFieldType) &&
|
||||||
config.label.toLowerCase().includes(searchQuery.toLowerCase()),
|
config.label.toLowerCase().includes(searchQuery.toLowerCase()),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -95,7 +94,7 @@ export const SettingsDataModelFieldTypeSelect = ({
|
|||||||
const { resetDefaultValueField: resetSelectDefaultValueField } =
|
const { resetDefaultValueField: resetSelectDefaultValueField } =
|
||||||
useSelectSettingsFormInitialValues({ fieldMetadataItem });
|
useSelectSettingsFormInitialValues({ fieldMetadataItem });
|
||||||
|
|
||||||
const resetDefaultValueField = (nextValue: SettingsSupportedFieldType) => {
|
const resetDefaultValueField = (nextValue: SettingsFieldType) => {
|
||||||
switch (nextValue) {
|
switch (nextValue) {
|
||||||
case FieldMetadataType.Boolean:
|
case FieldMetadataType.Boolean:
|
||||||
resetBooleanDefaultValueField();
|
resetBooleanDefaultValueField();
|
||||||
@@ -118,7 +117,7 @@ export const SettingsDataModelFieldTypeSelect = ({
|
|||||||
control={control}
|
control={control}
|
||||||
defaultValue={
|
defaultValue={
|
||||||
fieldMetadataItem && fieldMetadataItem.type in fieldTypeConfigs
|
fieldMetadataItem && fieldMetadataItem.type in fieldTypeConfigs
|
||||||
? (fieldMetadataItem.type as SettingsSupportedFieldType)
|
? (fieldMetadataItem.type as SettingsFieldType)
|
||||||
: FieldMetadataType.Text
|
: FieldMetadataType.Text
|
||||||
}
|
}
|
||||||
render={({ field: { onChange } }) => (
|
render={({ field: { onChange } }) => (
|
||||||
@@ -147,10 +146,8 @@ export const SettingsDataModelFieldTypeSelect = ({
|
|||||||
<SettingsCard
|
<SettingsCard
|
||||||
key={key}
|
key={key}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onChange(key as SettingsSupportedFieldType);
|
onChange(key as SettingsFieldType);
|
||||||
resetDefaultValueField(
|
resetDefaultValueField(key as SettingsFieldType);
|
||||||
key as SettingsSupportedFieldType,
|
|
||||||
);
|
|
||||||
onFieldTypeSelect();
|
onFieldTypeSelect();
|
||||||
}}
|
}}
|
||||||
Icon={
|
Icon={
|
||||||
|
|||||||
@@ -16,9 +16,11 @@ export const getCurrencyFieldPreviewValue = ({
|
|||||||
}): FieldCurrencyValue | null => {
|
}): FieldCurrencyValue | null => {
|
||||||
if (fieldMetadataItem.type !== FieldMetadataType.Currency) return null;
|
if (fieldMetadataItem.type !== FieldMetadataType.Currency) return null;
|
||||||
|
|
||||||
const placeholderDefaultValue = getSettingsFieldTypeConfig(
|
const currencyFieldTypeConfig = getSettingsFieldTypeConfig(
|
||||||
FieldMetadataType.Currency,
|
FieldMetadataType.Currency,
|
||||||
).exampleValue;
|
);
|
||||||
|
|
||||||
|
const placeholderDefaultValue = currencyFieldTypeConfig.exampleValue;
|
||||||
|
|
||||||
return currencyFieldDefaultValueSchema
|
return currencyFieldDefaultValueSchema
|
||||||
.transform((value) => ({
|
.transform((value) => ({
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import styled from '@emotion/styled';
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { IconComponent, IconTwentyStar } from 'twenty-ui';
|
import { IconComponent, IconTwentyStar } from 'twenty-ui';
|
||||||
|
|
||||||
import { SettingsSupportedFieldType } from '@/settings/data-model/types/SettingsSupportedFieldType';
|
import { SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType';
|
||||||
import { getSettingsFieldTypeConfig } from '@/settings/data-model/utils/getSettingsFieldTypeConfig';
|
import { getSettingsFieldTypeConfig } from '@/settings/data-model/utils/getSettingsFieldTypeConfig';
|
||||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
@@ -11,11 +11,11 @@ type SettingsObjectFieldDataTypeProps = {
|
|||||||
to?: string;
|
to?: string;
|
||||||
Icon?: IconComponent;
|
Icon?: IconComponent;
|
||||||
label?: string;
|
label?: string;
|
||||||
value: SettingsSupportedFieldType;
|
value: SettingsFieldType;
|
||||||
};
|
};
|
||||||
|
|
||||||
const StyledDataType = styled.div<{
|
const StyledDataType = styled.div<{
|
||||||
value: SettingsSupportedFieldType;
|
value: SettingsFieldType;
|
||||||
to?: string;
|
to?: string;
|
||||||
}>`
|
}>`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { FieldType } from '@/settings/data-model/types/FieldType';
|
||||||
|
import { PickLiteral } from '~/types/PickLiteral';
|
||||||
|
|
||||||
|
// TODO: add to future fullstack shared package
|
||||||
|
export const COMPOSITE_FIELD_TYPES = [
|
||||||
|
'CURRENCY',
|
||||||
|
'EMAILS',
|
||||||
|
'LINK',
|
||||||
|
'LINKS',
|
||||||
|
'ADDRESS',
|
||||||
|
'PHONES',
|
||||||
|
'FULL_NAME',
|
||||||
|
'ACTOR',
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
type CompositeFieldTypeBaseLiteral = (typeof COMPOSITE_FIELD_TYPES)[number];
|
||||||
|
|
||||||
|
export type CompositeFieldType = PickLiteral<
|
||||||
|
FieldType,
|
||||||
|
CompositeFieldTypeBaseLiteral
|
||||||
|
>;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
|
export type FieldType = `${FieldMetadataType}`;
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { CompositeFieldType } from '@/settings/data-model/types/CompositeFieldType';
|
||||||
|
import { FieldType } from '@/settings/data-model/types/FieldType';
|
||||||
|
import { ExcludeLiteral } from '~/types/ExcludeLiteral';
|
||||||
|
|
||||||
|
export type NonCompositeFieldType = ExcludeLiteral<
|
||||||
|
FieldType,
|
||||||
|
CompositeFieldType
|
||||||
|
>;
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { CompositeFieldType } from '@/settings/data-model/types/CompositeFieldType';
|
||||||
|
import { SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType';
|
||||||
|
import { PickLiteral } from '~/types/PickLiteral';
|
||||||
|
|
||||||
|
export type SettingsCompositeFieldType = PickLiteral<
|
||||||
|
SettingsFieldType,
|
||||||
|
CompositeFieldType
|
||||||
|
>;
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { FieldType } from '@/settings/data-model/types/FieldType';
|
||||||
|
import { PickLiteral } from '~/types/PickLiteral';
|
||||||
|
|
||||||
|
export type SettingsExcludedFieldType = PickLiteral<
|
||||||
|
FieldType,
|
||||||
|
'POSITION' | 'TS_VECTOR'
|
||||||
|
>;
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { FieldType } from '@/settings/data-model/types/FieldType';
|
||||||
|
import { SettingsExcludedFieldType } from '@/settings/data-model/types/SettingsExcludedFieldType';
|
||||||
|
import { ExcludeLiteral } from '~/types/ExcludeLiteral';
|
||||||
|
|
||||||
|
export type SettingsFieldType = ExcludeLiteral<
|
||||||
|
FieldType,
|
||||||
|
SettingsExcludedFieldType
|
||||||
|
>;
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { NonCompositeFieldType } from '@/settings/data-model/types/NonCompositeFieldType';
|
||||||
|
import { SettingsExcludedFieldType } from '@/settings/data-model/types/SettingsExcludedFieldType';
|
||||||
|
import { ExcludeLiteral } from '~/types/ExcludeLiteral';
|
||||||
|
|
||||||
|
export type SettingsNonCompositeFieldType = ExcludeLiteral<
|
||||||
|
NonCompositeFieldType,
|
||||||
|
SettingsExcludedFieldType
|
||||||
|
>;
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
|
||||||
|
|
||||||
export type SettingsSupportedFieldType = Exclude<
|
|
||||||
FieldMetadataType,
|
|
||||||
FieldMetadataType.Position | FieldMetadataType.TsVector
|
|
||||||
>;
|
|
||||||
@@ -1,13 +1,6 @@
|
|||||||
import { SETTINGS_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsFieldTypeConfigs';
|
import { SETTINGS_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsFieldTypeConfigs';
|
||||||
import { SettingsSupportedFieldType } from '@/settings/data-model/types/SettingsSupportedFieldType';
|
import { SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType';
|
||||||
import { isFieldTypeSupportedInSettings } from '@/settings/data-model/utils/isFieldTypeSupportedInSettings';
|
|
||||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
|
||||||
|
|
||||||
export const getSettingsFieldTypeConfig = <T extends FieldMetadataType>(
|
export const getSettingsFieldTypeConfig = (fieldType: SettingsFieldType) => {
|
||||||
fieldType: T,
|
return SETTINGS_FIELD_TYPE_CONFIGS[fieldType as SettingsFieldType];
|
||||||
) =>
|
};
|
||||||
(isFieldTypeSupportedInSettings(fieldType)
|
|
||||||
? SETTINGS_FIELD_TYPE_CONFIGS[fieldType]
|
|
||||||
: undefined) as T extends SettingsSupportedFieldType
|
|
||||||
? (typeof SETTINGS_FIELD_TYPE_CONFIGS)[T]
|
|
||||||
: undefined;
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { SETTINGS_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsFieldTypeConfigs';
|
import { SETTINGS_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsFieldTypeConfigs';
|
||||||
import { SettingsSupportedFieldType } from '@/settings/data-model/types/SettingsSupportedFieldType';
|
import { FieldType } from '@/settings/data-model/types/FieldType';
|
||||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
import { SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType';
|
||||||
|
|
||||||
export const isFieldTypeSupportedInSettings = (
|
export const isFieldTypeSupportedInSettings = (
|
||||||
fieldType: FieldMetadataType,
|
fieldType: FieldType,
|
||||||
): fieldType is SettingsSupportedFieldType =>
|
): fieldType is SettingsFieldType => fieldType in SETTINGS_FIELD_TYPE_CONFIGS;
|
||||||
fieldType in SETTINGS_FIELD_TYPE_CONFIGS;
|
|
||||||
|
|||||||
@@ -106,15 +106,18 @@ export const useUpsertCombinedViewFilters = (viewBarComponentId?: string) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const newValue = [
|
||||||
|
...unsavedToUpsertViewFilters,
|
||||||
|
{
|
||||||
|
...upsertedFilter,
|
||||||
|
id: upsertedFilter.id,
|
||||||
|
__typename: 'ViewFilter',
|
||||||
|
} satisfies ViewFilter,
|
||||||
|
] satisfies ViewFilter[];
|
||||||
|
|
||||||
set(
|
set(
|
||||||
unsavedToUpsertViewFiltersCallbackState({ viewId: currentViewId }),
|
unsavedToUpsertViewFiltersCallbackState({ viewId: currentViewId }),
|
||||||
[
|
newValue,
|
||||||
...unsavedToUpsertViewFilters,
|
|
||||||
{
|
|
||||||
...upsertedFilter,
|
|
||||||
__typename: 'ViewFilter',
|
|
||||||
} satisfies ViewFilter,
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ const baseDefinition = {
|
|||||||
fieldMetadataId: '05731f68-6e7a-4903-8374-c0b6a9063482',
|
fieldMetadataId: '05731f68-6e7a-4903-8374-c0b6a9063482',
|
||||||
label: 'label',
|
label: 'label',
|
||||||
iconName: 'iconName',
|
iconName: 'iconName',
|
||||||
|
fieldName: 'fieldName',
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('mapViewSortsToSorts', () => {
|
describe('mapViewSortsToSorts', () => {
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
|
|
||||||
import { hasSubMenuFilter } from '@/object-record/object-filter-dropdown/utils/hasSubMenuFilter';
|
|
||||||
import { ViewFilter } from '../types/ViewFilter';
|
|
||||||
|
|
||||||
export const getFilterDefinitionForViewFilter = (
|
|
||||||
viewFilter: ViewFilter,
|
|
||||||
availableFilterDefinition: FilterDefinition,
|
|
||||||
): FilterDefinition => {
|
|
||||||
return {
|
|
||||||
...availableFilterDefinition,
|
|
||||||
subFieldType:
|
|
||||||
hasSubMenuFilter(availableFilterDefinition.type) &&
|
|
||||||
viewFilter.definition?.type !== availableFilterDefinition.type
|
|
||||||
? viewFilter.definition?.type
|
|
||||||
: undefined,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -2,7 +2,6 @@ import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
|
|||||||
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
|
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
import { getFilterDefinitionForViewFilter } from '@/views/utils/getFilterDefinitionForViewFilter';
|
|
||||||
import { ViewFilter } from '../types/ViewFilter';
|
import { ViewFilter } from '../types/ViewFilter';
|
||||||
|
|
||||||
export const mapViewFiltersToFilters = (
|
export const mapViewFiltersToFilters = (
|
||||||
@@ -24,10 +23,7 @@ export const mapViewFiltersToFilters = (
|
|||||||
value: viewFilter.value,
|
value: viewFilter.value,
|
||||||
displayValue: viewFilter.displayValue,
|
displayValue: viewFilter.displayValue,
|
||||||
operand: viewFilter.operand,
|
operand: viewFilter.operand,
|
||||||
definition: getFilterDefinitionForViewFilter(
|
definition: viewFilter.definition ?? availableFilterDefinition,
|
||||||
viewFilter,
|
|
||||||
availableFilterDefinition,
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter(isDefined);
|
.filter(isDefined);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
|
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
|
||||||
import { FilterType } from '@/object-record/object-filter-dropdown/types/FilterType';
|
import { FilterableFieldType } from '@/object-record/object-filter-dropdown/types/FilterableFieldType';
|
||||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||||
import { resolveNumberViewFilterValue } from '@/views/utils/view-filter-value/resolveNumberViewFilterValue';
|
import { resolveNumberViewFilterValue } from '@/views/utils/view-filter-value/resolveNumberViewFilterValue';
|
||||||
import {
|
import {
|
||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
} from './resolveDateViewFilterValue';
|
} from './resolveDateViewFilterValue';
|
||||||
|
|
||||||
type ResolvedFilterValue<
|
type ResolvedFilterValue<
|
||||||
T extends FilterType,
|
T extends FilterableFieldType,
|
||||||
O extends ViewFilterOperand,
|
O extends ViewFilterOperand,
|
||||||
> = T extends 'DATE' | 'DATE_TIME'
|
> = T extends 'DATE' | 'DATE_TIME'
|
||||||
? ResolvedDateViewFilterValue<O>
|
? ResolvedDateViewFilterValue<O>
|
||||||
@@ -16,16 +16,16 @@ type ResolvedFilterValue<
|
|||||||
? ReturnType<typeof resolveNumberViewFilterValue>
|
? ReturnType<typeof resolveNumberViewFilterValue>
|
||||||
: string;
|
: string;
|
||||||
|
|
||||||
type PartialFilter<T extends FilterType, O extends ViewFilterOperand> = Pick<
|
type PartialFilter<
|
||||||
Filter,
|
T extends FilterableFieldType,
|
||||||
'value'
|
O extends ViewFilterOperand,
|
||||||
> & {
|
> = Pick<Filter, 'value'> & {
|
||||||
definition: { type: T };
|
definition: { type: T };
|
||||||
operand: O;
|
operand: O;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const resolveFilterValue = <
|
export const resolveFilterValue = <
|
||||||
T extends FilterType,
|
T extends FilterableFieldType,
|
||||||
O extends ViewFilterOperand,
|
O extends ViewFilterOperand,
|
||||||
>(
|
>(
|
||||||
filter: PartialFilter<T, O>,
|
filter: PartialFilter<T, O>,
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import { SettingsDataModelFieldDescriptionForm } from '@/settings/data-model/fie
|
|||||||
import { SettingsDataModelFieldIconLabelForm } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldIconLabelForm';
|
import { SettingsDataModelFieldIconLabelForm } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldIconLabelForm';
|
||||||
import { SettingsDataModelFieldSettingsFormCard } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard';
|
import { SettingsDataModelFieldSettingsFormCard } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard';
|
||||||
import { settingsFieldFormSchema } from '@/settings/data-model/fields/forms/validation-schemas/settingsFieldFormSchema';
|
import { settingsFieldFormSchema } from '@/settings/data-model/fields/forms/validation-schemas/settingsFieldFormSchema';
|
||||||
import { SettingsSupportedFieldType } from '@/settings/data-model/types/SettingsSupportedFieldType';
|
import { SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType';
|
||||||
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
|
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
|
||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
import { SettingsPath } from '@/types/SettingsPath';
|
import { SettingsPath } from '@/types/SettingsPath';
|
||||||
@@ -42,9 +42,11 @@ import { Section } from '@/ui/layout/section/components/Section';
|
|||||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
|
//TODO: fix this type
|
||||||
type SettingsDataModelFieldEditFormValues = z.infer<
|
type SettingsDataModelFieldEditFormValues = z.infer<
|
||||||
ReturnType<typeof settingsFieldFormSchema>
|
ReturnType<typeof settingsFieldFormSchema>
|
||||||
>;
|
> &
|
||||||
|
any;
|
||||||
|
|
||||||
const canPersistFieldMetadataItemUpdate = (
|
const canPersistFieldMetadataItemUpdate = (
|
||||||
fieldMetadataItem: FieldMetadataItem,
|
fieldMetadataItem: FieldMetadataItem,
|
||||||
@@ -94,7 +96,7 @@ export const SettingsObjectFieldEdit = () => {
|
|||||||
resolver: zodResolver(settingsFieldFormSchema()),
|
resolver: zodResolver(settingsFieldFormSchema()),
|
||||||
values: {
|
values: {
|
||||||
icon: fieldMetadataItem?.icon ?? 'Icon',
|
icon: fieldMetadataItem?.icon ?? 'Icon',
|
||||||
type: fieldMetadataItem?.type as SettingsSupportedFieldType,
|
type: fieldMetadataItem?.type as SettingsFieldType,
|
||||||
label: fieldMetadataItem?.label ?? '',
|
label: fieldMetadataItem?.label ?? '',
|
||||||
description: fieldMetadataItem?.description,
|
description: fieldMetadataItem?.description,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { SettingsDataModelFieldIconLabelForm } from '@/settings/data-model/field
|
|||||||
import { SettingsDataModelFieldSettingsFormCard } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard';
|
import { SettingsDataModelFieldSettingsFormCard } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard';
|
||||||
import { SettingsDataModelFieldTypeSelect } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldTypeSelect';
|
import { SettingsDataModelFieldTypeSelect } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldTypeSelect';
|
||||||
import { settingsFieldFormSchema } from '@/settings/data-model/fields/forms/validation-schemas/settingsFieldFormSchema';
|
import { settingsFieldFormSchema } from '@/settings/data-model/fields/forms/validation-schemas/settingsFieldFormSchema';
|
||||||
import { SettingsSupportedFieldType } from '@/settings/data-model/types/SettingsSupportedFieldType';
|
import { SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType';
|
||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
@@ -34,9 +34,11 @@ import { FieldMetadataType } from '~/generated-metadata/graphql';
|
|||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||||
|
|
||||||
|
// TODO: fix this type
|
||||||
type SettingsDataModelNewFieldFormValues = z.infer<
|
type SettingsDataModelNewFieldFormValues = z.infer<
|
||||||
ReturnType<typeof settingsFieldFormSchema>
|
ReturnType<typeof settingsFieldFormSchema>
|
||||||
>;
|
> &
|
||||||
|
any;
|
||||||
|
|
||||||
const StyledH1Title = styled(H1Title)`
|
const StyledH1Title = styled(H1Title)`
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
@@ -46,7 +48,7 @@ export const SettingsObjectNewFieldStep2 = () => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { objectSlug = '' } = useParams();
|
const { objectSlug = '' } = useParams();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const fieldType = searchParams.get('fieldType') as SettingsSupportedFieldType;
|
const fieldType = searchParams.get('fieldType') as SettingsFieldType;
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueSnackBar } = useSnackBar();
|
||||||
|
|
||||||
const [isConfigureStep, setIsConfigureStep] = useState(false);
|
const [isConfigureStep, setIsConfigureStep] = useState(false);
|
||||||
@@ -159,7 +161,7 @@ export const SettingsObjectNewFieldStep2 = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const excludedFieldTypes: SettingsSupportedFieldType[] = (
|
const excludedFieldTypes: SettingsFieldType[] = (
|
||||||
[
|
[
|
||||||
FieldMetadataType.Link,
|
FieldMetadataType.Link,
|
||||||
FieldMetadataType.Numeric,
|
FieldMetadataType.Numeric,
|
||||||
@@ -226,7 +228,7 @@ export const SettingsObjectNewFieldStep2 = () => {
|
|||||||
<SettingsDataModelFieldTypeSelect
|
<SettingsDataModelFieldTypeSelect
|
||||||
excludedFieldTypes={excludedFieldTypes}
|
excludedFieldTypes={excludedFieldTypes}
|
||||||
fieldMetadataItem={{
|
fieldMetadataItem={{
|
||||||
type: fieldType,
|
type: fieldType as FieldMetadataType,
|
||||||
}}
|
}}
|
||||||
onFieldTypeSelect={() => setIsConfigureStep(true)}
|
onFieldTypeSelect={() => setIsConfigureStep(true)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata';
|
import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata';
|
||||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
|
import { FieldType } from '@/settings/data-model/types/FieldType';
|
||||||
|
import { SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType';
|
||||||
import { getFieldIdentifierType } from '@/settings/data-model/utils/getFieldIdentifierType';
|
import { getFieldIdentifierType } from '@/settings/data-model/utils/getFieldIdentifierType';
|
||||||
import { getSettingsFieldTypeConfig } from '@/settings/data-model/utils/getSettingsFieldTypeConfig';
|
import { getSettingsFieldTypeConfig } from '@/settings/data-model/utils/getSettingsFieldTypeConfig';
|
||||||
|
import { isFieldTypeSupportedInSettings } from '@/settings/data-model/utils/isFieldTypeSupportedInSettings';
|
||||||
import { SettingsObjectDetailTableItem } from '~/pages/settings/data-model/types/SettingsObjectDetailTableItem';
|
import { SettingsObjectDetailTableItem } from '~/pages/settings/data-model/types/SettingsObjectDetailTableItem';
|
||||||
import { getSettingsObjectFieldType } from '~/pages/settings/data-model/utils/getSettingsObjectFieldType';
|
import { getSettingsObjectFieldType } from '~/pages/settings/data-model/utils/getSettingsObjectFieldType';
|
||||||
|
|
||||||
@@ -29,13 +32,17 @@ export const useMapFieldMetadataItemToSettingsObjectDetailTableItem = (
|
|||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const fieldMetadataType = fieldMetadataItem.type as FieldType;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fieldMetadataItem,
|
fieldMetadataItem,
|
||||||
fieldType: fieldType ?? '',
|
fieldType: fieldType ?? '',
|
||||||
dataType:
|
dataType:
|
||||||
relationObjectMetadataItem?.labelPlural ??
|
(relationObjectMetadataItem?.labelPlural ??
|
||||||
getSettingsFieldTypeConfig(fieldMetadataItem.type)?.label ??
|
isFieldTypeSupportedInSettings(fieldMetadataType))
|
||||||
'',
|
? getSettingsFieldTypeConfig(fieldMetadataType as SettingsFieldType)
|
||||||
|
?.label
|
||||||
|
: '',
|
||||||
label: fieldMetadataItem.label,
|
label: fieldMetadataItem.label,
|
||||||
identifierType: identifierType,
|
identifierType: identifierType,
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
|
|||||||
1
packages/twenty-front/src/types/ExcludeLiteral.ts
Normal file
1
packages/twenty-front/src/types/ExcludeLiteral.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export type ExcludeLiteral<T, U extends T> = T extends U ? never : T;
|
||||||
1
packages/twenty-front/src/types/PickLiteral.ts
Normal file
1
packages/twenty-front/src/types/PickLiteral.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export type PickLiteral<T, U extends T> = U;
|
||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
WorkspaceMigrationExceptionCode,
|
WorkspaceMigrationExceptionCode,
|
||||||
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.exception';
|
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.exception';
|
||||||
|
|
||||||
|
// TODO: could we export this to GraphQL ?
|
||||||
export type CompositeFieldMetadataType =
|
export type CompositeFieldMetadataType =
|
||||||
| FieldMetadataType.ADDRESS
|
| FieldMetadataType.ADDRESS
|
||||||
| FieldMetadataType.CURRENCY
|
| FieldMetadataType.CURRENCY
|
||||||
|
|||||||
Reference in New Issue
Block a user