mirror of
https://github.com/lingble/twenty.git
synced 2025-11-02 13:47: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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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>
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
<ObjectFilterSelectSubMenu />
|
<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) {
|
|
||||||
setCurrentSubMenu(filterDefinition.type);
|
|
||||||
setCurrentParentFilterDefinition(filterDefinition);
|
|
||||||
} else {
|
|
||||||
selectFilter({ filterDefinition });
|
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,13 +431,25 @@ export const turnObjectDropdownFilterIntoQueryFilter = (
|
|||||||
correspondingField.name,
|
correspondingField.name,
|
||||||
['primaryLinkLabel', 'primaryLinkUrl'],
|
['primaryLinkLabel', 'primaryLinkUrl'],
|
||||||
);
|
);
|
||||||
|
|
||||||
switch (rawUIFilter.operand) {
|
switch (rawUIFilter.operand) {
|
||||||
case ViewFilterOperand.Contains:
|
case ViewFilterOperand.Contains:
|
||||||
|
if (!isCompositeFieldFiter) {
|
||||||
objectRecordFilters.push({
|
objectRecordFilters.push({
|
||||||
or: linksFilters,
|
or: linksFilters,
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
objectRecordFilters.push({
|
||||||
|
[correspondingField.name]: {
|
||||||
|
[compositeFieldName]: {
|
||||||
|
ilike: `%${rawUIFilter.value}%`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case ViewFilterOperand.DoesNotContain:
|
case ViewFilterOperand.DoesNotContain:
|
||||||
|
if (!isCompositeFieldFiter) {
|
||||||
objectRecordFilters.push({
|
objectRecordFilters.push({
|
||||||
and: linksFilters.map((filter) => {
|
and: linksFilters.map((filter) => {
|
||||||
return {
|
return {
|
||||||
@@ -632,6 +457,17 @@ export const turnObjectDropdownFilterIntoQueryFilter = (
|
|||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
} 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,11 +493,22 @@ export const turnObjectDropdownFilterIntoQueryFilter = (
|
|||||||
);
|
);
|
||||||
switch (rawUIFilter.operand) {
|
switch (rawUIFilter.operand) {
|
||||||
case ViewFilterOperand.Contains:
|
case ViewFilterOperand.Contains:
|
||||||
|
if (!isCompositeFieldFiter) {
|
||||||
objectRecordFilters.push({
|
objectRecordFilters.push({
|
||||||
or: fullNameFilters,
|
or: fullNameFilters,
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
objectRecordFilters.push({
|
||||||
|
[correspondingField.name]: {
|
||||||
|
[compositeFieldName]: {
|
||||||
|
ilike: `%${rawUIFilter.value}%`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case ViewFilterOperand.DoesNotContain:
|
case ViewFilterOperand.DoesNotContain:
|
||||||
|
if (!isCompositeFieldFiter) {
|
||||||
objectRecordFilters.push({
|
objectRecordFilters.push({
|
||||||
and: fullNameFilters.map((filter) => {
|
and: fullNameFilters.map((filter) => {
|
||||||
return {
|
return {
|
||||||
@@ -669,6 +516,17 @@ export const turnObjectDropdownFilterIntoQueryFilter = (
|
|||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
} 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,6 +547,7 @@ export const turnObjectDropdownFilterIntoQueryFilter = (
|
|||||||
case 'ADDRESS':
|
case 'ADDRESS':
|
||||||
switch (rawUIFilter.operand) {
|
switch (rawUIFilter.operand) {
|
||||||
case ViewFilterOperand.Contains:
|
case ViewFilterOperand.Contains:
|
||||||
|
if (!isCompositeFieldFiter) {
|
||||||
objectRecordFilters.push({
|
objectRecordFilters.push({
|
||||||
or: [
|
or: [
|
||||||
{
|
{
|
||||||
@@ -735,8 +594,18 @@ export const turnObjectDropdownFilterIntoQueryFilter = (
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
objectRecordFilters.push({
|
||||||
|
[correspondingField.name]: {
|
||||||
|
[compositeFieldName]: {
|
||||||
|
ilike: `%${rawUIFilter.value}%`,
|
||||||
|
} as AddressFilter,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case ViewFilterOperand.DoesNotContain:
|
case ViewFilterOperand.DoesNotContain:
|
||||||
|
if (!isCompositeFieldFiter) {
|
||||||
objectRecordFilters.push({
|
objectRecordFilters.push({
|
||||||
and: [
|
and: [
|
||||||
{
|
{
|
||||||
@@ -768,6 +637,17 @@ export const turnObjectDropdownFilterIntoQueryFilter = (
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
} 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,10 +716,9 @@ 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({
|
||||||
@@ -856,21 +735,14 @@ export const turnObjectDropdownFilterIntoQueryFilter = (
|
|||||||
objectRecordFilters.push({
|
objectRecordFilters.push({
|
||||||
not: {
|
not: {
|
||||||
[correspondingField.name]: {
|
[correspondingField.name]: {
|
||||||
[rawUIFilter.definition.subFieldType.toLowerCase()]: {
|
source: {
|
||||||
in: parsedRecordIds,
|
in: parsedRecordIds,
|
||||||
} as RelationFilter,
|
} as RelationFilter,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
|
||||||
throw new Error(
|
|
||||||
`Unknown operand ${rawUIFilter.operand} for ${rawUIFilter.definition.subFieldType} filter`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
set(
|
const newValue = [
|
||||||
unsavedToUpsertViewFiltersCallbackState({ viewId: currentViewId }),
|
|
||||||
[
|
|
||||||
...unsavedToUpsertViewFilters,
|
...unsavedToUpsertViewFilters,
|
||||||
{
|
{
|
||||||
...upsertedFilter,
|
...upsertedFilter,
|
||||||
|
id: upsertedFilter.id,
|
||||||
__typename: 'ViewFilter',
|
__typename: 'ViewFilter',
|
||||||
} satisfies ViewFilter,
|
} satisfies ViewFilter,
|
||||||
],
|
] satisfies ViewFilter[];
|
||||||
|
|
||||||
|
set(
|
||||||
|
unsavedToUpsertViewFiltersCallbackState({ viewId: currentViewId }),
|
||||||
|
newValue,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -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