TWNTY-6808 - Ability to Filter by Creation Source (#7078)
### Description - Ability to Filter by Creation Source ### Demo LOOM: <https://www.loom.com/share/dba9c3d37a4242fe90f977b1babffbde?sid=59b07c51-d245-43cc-bb38-7d898ef72878> ### Refs #6808 Fixes #6808 --------- Co-authored-by: gitstart-twenty <gitstart-twenty@users.noreply.github.com> Co-authored-by: gitstart-twenty <140154534+gitstart-twenty@users.noreply.github.com> Co-authored-by: bosiraphael <raphael.bosi@gmail.com>
This commit is contained in:
committed by
GitHub
parent
2cd3219636
commit
35788af351
@ -1,9 +1,10 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { Key } from 'ts-key-enum';
|
||||
import { Avatar } from 'twenty-ui';
|
||||
import { AvatarChip } from 'twenty-ui';
|
||||
|
||||
import { SelectableRecord } from '@/object-record/select/types/SelectableRecord';
|
||||
import { SelectableItem } from '@/object-record/select/types/SelectableItem';
|
||||
import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
@ -14,26 +15,36 @@ import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||
import { MenuItemMultiSelectAvatar } from '@/ui/navigation/menu-item/components/MenuItemMultiSelectAvatar';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
|
||||
export const MultipleRecordSelectDropdown = ({
|
||||
const StyledAvatarChip = styled(AvatarChip)`
|
||||
&.avatar-icon-container {
|
||||
color: ${({ theme }) => theme.font.color.secondary};
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
font-size: ${({ theme }) => theme.font.size.md};
|
||||
}
|
||||
`;
|
||||
|
||||
export const MultipleSelectDropdown = ({
|
||||
selectableListId,
|
||||
hotkeyScope,
|
||||
recordsToSelect,
|
||||
loadingRecords,
|
||||
filteredSelectedRecords,
|
||||
itemsToSelect,
|
||||
loadingItems,
|
||||
filteredSelectedItems,
|
||||
onChange,
|
||||
searchFilter,
|
||||
}: {
|
||||
selectableListId: string;
|
||||
hotkeyScope: string;
|
||||
recordsToSelect: SelectableRecord[];
|
||||
filteredSelectedRecords: SelectableRecord[];
|
||||
selectedRecords: SelectableRecord[];
|
||||
itemsToSelect: SelectableItem[];
|
||||
filteredSelectedItems: SelectableItem[];
|
||||
selectedItems: SelectableItem[];
|
||||
searchFilter: string;
|
||||
onChange: (
|
||||
changedRecordToSelect: SelectableRecord,
|
||||
changedItemToSelect: SelectableItem,
|
||||
newSelectedValue: boolean,
|
||||
) => void;
|
||||
loadingRecords: boolean;
|
||||
loadingItems: boolean;
|
||||
}) => {
|
||||
const { closeDropdown } = useDropdown();
|
||||
const { selectedItemIdState } = useSelectableListStates({
|
||||
@ -44,32 +55,32 @@ export const MultipleRecordSelectDropdown = ({
|
||||
|
||||
const selectedItemId = useRecoilValue(selectedItemIdState);
|
||||
|
||||
const handleRecordSelectChange = (
|
||||
recordToSelect: SelectableRecord,
|
||||
const handleItemSelectChange = (
|
||||
itemToSelect: SelectableItem,
|
||||
newSelectedValue: boolean,
|
||||
) => {
|
||||
onChange(
|
||||
{
|
||||
...recordToSelect,
|
||||
...itemToSelect,
|
||||
isSelected: newSelectedValue,
|
||||
},
|
||||
newSelectedValue,
|
||||
);
|
||||
};
|
||||
|
||||
const [recordsInDropdown, setRecordInDropdown] = useState([
|
||||
...(filteredSelectedRecords ?? []),
|
||||
...(recordsToSelect ?? []),
|
||||
const [itemsInDropdown, setItemInDropdown] = useState([
|
||||
...(filteredSelectedItems ?? []),
|
||||
...(itemsToSelect ?? []),
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loadingRecords) {
|
||||
setRecordInDropdown([
|
||||
...(filteredSelectedRecords ?? []),
|
||||
...(recordsToSelect ?? []),
|
||||
if (!loadingItems) {
|
||||
setItemInDropdown([
|
||||
...(filteredSelectedItems ?? []),
|
||||
...(itemsToSelect ?? []),
|
||||
]);
|
||||
}
|
||||
}, [recordsToSelect, filteredSelectedRecords, loadingRecords]);
|
||||
}, [itemsToSelect, filteredSelectedItems, loadingItems]);
|
||||
|
||||
useScopedHotkeys(
|
||||
[Key.Escape],
|
||||
@ -82,12 +93,12 @@ export const MultipleRecordSelectDropdown = ({
|
||||
);
|
||||
|
||||
const showNoResult =
|
||||
recordsToSelect?.length === 0 &&
|
||||
itemsToSelect?.length === 0 &&
|
||||
searchFilter !== '' &&
|
||||
filteredSelectedRecords?.length === 0 &&
|
||||
!loadingRecords;
|
||||
filteredSelectedItems?.length === 0 &&
|
||||
!loadingItems;
|
||||
|
||||
const selectableItemIds = recordsInDropdown.map((record) => record.id);
|
||||
const selectableItemIds = itemsInDropdown.map((item) => item.id);
|
||||
|
||||
return (
|
||||
<SelectableList
|
||||
@ -95,45 +106,46 @@ export const MultipleRecordSelectDropdown = ({
|
||||
selectableItemIdArray={selectableItemIds}
|
||||
hotkeyScope={hotkeyScope}
|
||||
onEnter={(itemId) => {
|
||||
const record = recordsInDropdown.findIndex(
|
||||
const item = itemsInDropdown.findIndex(
|
||||
(entity) => entity.id === itemId,
|
||||
);
|
||||
const recordIsSelectedInDropwdown = filteredSelectedRecords.find(
|
||||
const itemIsSelectedInDropwdown = filteredSelectedItems.find(
|
||||
(entity) => entity.id === itemId,
|
||||
);
|
||||
handleRecordSelectChange(
|
||||
recordsInDropdown[record],
|
||||
!recordIsSelectedInDropwdown,
|
||||
handleItemSelectChange(
|
||||
itemsInDropdown[item],
|
||||
!itemIsSelectedInDropwdown,
|
||||
);
|
||||
resetSelectedItem();
|
||||
}}
|
||||
>
|
||||
<DropdownMenuItemsContainer hasMaxHeight>
|
||||
{recordsInDropdown?.map((record) => {
|
||||
{itemsInDropdown?.map((item) => {
|
||||
return (
|
||||
<MenuItemMultiSelectAvatar
|
||||
key={record.id}
|
||||
selected={record.isSelected}
|
||||
isKeySelected={record.id === selectedItemId}
|
||||
key={item.id}
|
||||
selected={item.isSelected}
|
||||
isKeySelected={item.id === selectedItemId}
|
||||
onSelectChange={(newCheckedValue) => {
|
||||
resetSelectedItem();
|
||||
handleRecordSelectChange(record, newCheckedValue);
|
||||
handleItemSelectChange(item, newCheckedValue);
|
||||
}}
|
||||
avatar={
|
||||
<Avatar
|
||||
avatarUrl={record.avatarUrl}
|
||||
placeholderColorSeed={record.id}
|
||||
placeholder={record.name}
|
||||
size="md"
|
||||
type={record.avatarType ?? 'rounded'}
|
||||
<StyledAvatarChip
|
||||
className="avatar-icon-container"
|
||||
name={item.name}
|
||||
avatarUrl={item.avatarUrl}
|
||||
LeftIcon={item.AvatarIcon}
|
||||
avatarType={item.avatarType}
|
||||
isIconInverted={item.isIconInverted}
|
||||
placeholderColorSeed={item.id}
|
||||
/>
|
||||
}
|
||||
text={record.name}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{showNoResult && <MenuItem text="No result" />}
|
||||
{loadingRecords && <DropdownMenuSkeletonItem />}
|
||||
{loadingItems && <DropdownMenuSkeletonItem />}
|
||||
</DropdownMenuItemsContainer>
|
||||
</SelectableList>
|
||||
);
|
||||
@ -5,7 +5,7 @@ import { useMapToObjectRecordIdentifier } from '@/object-metadata/hooks/useMapTo
|
||||
|
||||
import { DEFAULT_SEARCH_REQUEST_LIMIT } from '@/object-record/constants/DefaultSearchRequestLimit';
|
||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||
import { SelectableRecord } from '@/object-record/select/types/SelectableRecord';
|
||||
import { SelectableItem } from '@/object-record/select/types/SelectableItem';
|
||||
import { getObjectFilterFields } from '@/object-record/select/utils/getObjectFilterFields';
|
||||
import { makeAndFilterVariables } from '@/object-record/utils/makeAndFilterVariables';
|
||||
import { makeOrFilterVariables } from '@/object-record/utils/makeOrFilterVariables';
|
||||
@ -109,19 +109,19 @@ export const useRecordsForSelect = ({
|
||||
.map((record) => ({
|
||||
...record,
|
||||
isSelected: true,
|
||||
})) as SelectableRecord[],
|
||||
})) as SelectableItem[],
|
||||
filteredSelectedRecords: filteredSelectedRecordsData
|
||||
.map(mapToObjectRecordIdentifier)
|
||||
.map((record) => ({
|
||||
...record,
|
||||
isSelected: true,
|
||||
})) as SelectableRecord[],
|
||||
})) as SelectableItem[],
|
||||
recordsToSelect: recordsToSelectData
|
||||
.map(mapToObjectRecordIdentifier)
|
||||
.map((record) => ({
|
||||
...record,
|
||||
isSelected: false,
|
||||
})) as SelectableRecord[],
|
||||
})) as SelectableItem[],
|
||||
loading:
|
||||
recordsToSelectLoading ||
|
||||
filteredSelectedRecordsLoading ||
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
import { AvatarType, IconComponent } from 'twenty-ui';
|
||||
|
||||
export type SelectableItem<T = object> = T & {
|
||||
id: string;
|
||||
name: string;
|
||||
avatarUrl?: string;
|
||||
avatarType?: AvatarType;
|
||||
AvatarIcon?: IconComponent;
|
||||
isSelected: boolean;
|
||||
isIconInverted?: boolean;
|
||||
};
|
||||
@ -1,10 +0,0 @@
|
||||
import { AvatarType } from 'twenty-ui';
|
||||
|
||||
export type SelectableRecord = {
|
||||
id: string;
|
||||
name: string;
|
||||
avatarUrl?: string;
|
||||
avatarType?: AvatarType;
|
||||
record: any;
|
||||
isSelected: boolean;
|
||||
};
|
||||
Reference in New Issue
Block a user