Fix activity target bug (#3390)

This commit is contained in:
Charles Bochet 2024-01-11 20:18:54 +01:00 committed by GitHub
parent 2c8c22a979
commit 99247fb689
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 157 additions and 211 deletions

View File

@ -18,6 +18,7 @@ export const ActivityTargetChips = ({
<StyledContainer>
{activityTargetObjectRecords?.map((activityTargetObjectRecord) => (
<RecordChip
key={activityTargetObjectRecord.targetObjectRecord.id}
record={activityTargetObjectRecord.targetObjectRecord}
objectNameSingular={
activityTargetObjectRecord.targetObjectMetadataItem.nameSingular

View File

@ -46,9 +46,7 @@ export const ActivityTargetsInlineCell = ({
activityTargetObjectRecords={activityTargetObjectRecords}
/>
}
isDisplayModeContentEmpty={
activity?.activityTargets?.edges?.length === 0
}
isDisplayModeContentEmpty={activityTargetObjectRecords.length === 0}
/>
</RecoilScope>
);

View File

@ -2,8 +2,8 @@ import { useEffect, useState } from 'react';
import { ObjectFilterDropdownId } from '@/object-record/object-filter-dropdown/constants/ObjectFilterDropdownId';
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
import { EntitiesForMultipleEntitySelect } from '@/object-record/relation-picker/components/MultipleEntitySelect';
import { SingleEntitySelectMenuItems } from '@/object-record/relation-picker/components/SingleEntitySelectMenuItems';
import { EntitiesForMultipleEntitySelect } from '@/object-record/relation-picker/types/EntitiesForMultipleEntitySelect';
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';

View File

@ -1,126 +0,0 @@
import { useRef } from 'react';
import { isNonEmptyString } from '@sniptt/guards';
import debounce from 'lodash.debounce';
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
import { MenuItemMultiSelectAvatar } from '@/ui/navigation/menu-item/components/MenuItemMultiSelectAvatar';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { Avatar } from '@/users/components/Avatar';
import { EntityForSelect } from '../types/EntityForSelect';
export type EntitiesForMultipleEntitySelect<
CustomEntityForSelect extends EntityForSelect,
> = {
selectedEntities: CustomEntityForSelect[];
filteredSelectedEntities: CustomEntityForSelect[];
entitiesToSelect: CustomEntityForSelect[];
loading: boolean;
};
export const MultipleEntitySelect = <
CustomEntityForSelect extends EntityForSelect,
>({
entities,
onChange,
onSubmit,
onSearchFilterChange,
searchFilter,
value,
}: {
entities: EntitiesForMultipleEntitySelect<CustomEntityForSelect>;
searchFilter: string;
onSearchFilterChange: (newSearchFilter: string) => void;
onChange: (value: Record<string, boolean>) => void;
onCancel?: () => void;
onSubmit?: () => void;
value: Record<string, boolean>;
}) => {
const debouncedSetSearchFilter = debounce(onSearchFilterChange, 100, {
leading: true,
});
const handleFilterChange = (event: React.ChangeEvent<HTMLInputElement>) => {
debouncedSetSearchFilter(event.currentTarget.value);
onSearchFilterChange(event.currentTarget.value);
};
let entitiesInDropdown = [
...(entities.filteredSelectedEntities ?? []),
...(entities.entitiesToSelect ?? []),
];
entitiesInDropdown = entitiesInDropdown.filter((entity) =>
isNonEmptyString(entity.name),
);
const containerRef = useRef<HTMLDivElement>(null);
useListenClickOutside({
refs: [containerRef],
callback: (event) => {
event.stopImmediatePropagation();
event.stopPropagation();
event.preventDefault();
onSubmit?.();
},
});
const selectableItemIds = entitiesInDropdown.map((entity) => entity.id);
return (
<DropdownMenu ref={containerRef} data-select-disable>
<DropdownMenuSearchInput
value={searchFilter}
onChange={handleFilterChange}
autoFocus
/>
<DropdownMenuSeparator />
<DropdownMenuItemsContainer hasMaxHeight>
<SelectableList
selectableListId="multiple-entity-select-list"
selectableItemIdArray={selectableItemIds}
hotkeyScope={RelationPickerHotkeyScope.RelationPicker}
onEnter={(_itemId) => {
if (_itemId in value === false || value[_itemId] === false) {
onChange({ ...value, [_itemId]: true });
} else {
onChange({ ...value, [_itemId]: false });
}
}}
>
{entitiesInDropdown?.map((entity) => (
<SelectableItem itemId={entity.id} key={entity.id}>
<MenuItemMultiSelectAvatar
key={entity.id}
selected={value[entity.id]}
onSelectChange={(newCheckedValue) =>
onChange({ ...value, [entity.id]: newCheckedValue })
}
avatar={
<Avatar
avatarUrl={entity.avatarUrl}
colorId={entity.id}
placeholder={entity.name}
size="md"
type={entity.avatarType ?? 'rounded'}
/>
}
text={entity.name}
/>
</SelectableItem>
))}
</SelectableList>
{entitiesInDropdown?.length === 0 && <MenuItem text="No result" />}
</DropdownMenuItemsContainer>
</DropdownMenu>
);
};

View File

@ -0,0 +1,22 @@
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
export const MultipleObjectRecordOnClickOutsideEffect = ({
containerRef,
onClickOutside,
}: {
containerRef: React.RefObject<HTMLDivElement>;
onClickOutside: () => void;
}) => {
useListenClickOutside({
refs: [containerRef],
callback: (event) => {
event.stopImmediatePropagation();
event.stopPropagation();
event.preventDefault();
onClickOutside();
},
});
return <></>;
};

View File

@ -2,8 +2,10 @@ import { useEffect, useMemo, useRef, useState } from 'react';
import styled from '@emotion/styled';
import { isNonEmptyString } from '@sniptt/guards';
import debounce from 'lodash.debounce';
import { v4 } from 'uuid';
import { MultipleObjectRecordOnClickOutsideEffect } from '@/object-record/relation-picker/components/MultipleObjectRecordOnClickOutsideEffect';
import { MultipleObjectRecordSelectItem } from '@/object-record/relation-picker/components/MultipleObjectRecordSelectItem';
import { MultiObjectRecordSelectSelectableListId } from '@/object-record/relation-picker/constants/MultiObjectRecordSelectSelectableListId';
import {
ObjectRecordForSelect,
SelectedObjectRecordId,
@ -17,9 +19,6 @@ import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownM
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
import { MenuItemMultiSelectAvatar } from '@/ui/navigation/menu-item/components/MenuItemMultiSelectAvatar';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { Avatar } from '@/users/components/Avatar';
export const StyledSelectableItem = styled(SelectableItem)`
height: 100%;
@ -116,61 +115,61 @@ export const MultipleObjectRecordSelect = ({
[filteredSelectedObjectRecords, objectRecordsToSelect],
);
useListenClickOutside({
refs: [containerRef],
callback: (event) => {
event.stopImmediatePropagation();
event.stopPropagation();
event.preventDefault();
onSubmit?.(internalSelectedRecords);
},
});
const selectableItemIds = entitiesInDropdown.map(
(entity) => entity.record.id,
);
return (
<DropdownMenu ref={containerRef} data-select-disable>
<DropdownMenuSearchInput
value={searchFilter}
onChange={handleFilterChange}
autoFocus
<>
<MultipleObjectRecordOnClickOutsideEffect
containerRef={containerRef}
onClickOutside={() => {
onSubmit?.(internalSelectedRecords);
}}
/>
<DropdownMenuSeparator />
<DropdownMenuItemsContainer hasMaxHeight>
{loading ? (
<MenuItem text="Loading..." />
) : (
<>
<SelectableList
selectableListId="multiple-entity-select-list"
selectableItemIdArray={selectableItemIds}
hotkeyScope={RelationPickerHotkeyScope.RelationPicker}
onEnter={(recordId) => {
const recordIsSelected = internalSelectedRecords?.some(
(selectedRecord) => selectedRecord.record.id === recordId,
);
const correspondingRecordForSelect = entitiesInDropdown?.find(
(entity) => entity.record.id === recordId,
);
if (correspondingRecordForSelect) {
handleSelectChange(
correspondingRecordForSelect,
!recordIsSelected,
<DropdownMenu ref={containerRef} data-select-disable>
<DropdownMenuSearchInput
value={searchFilter}
onChange={handleFilterChange}
autoFocus
/>
<DropdownMenuSeparator />
<DropdownMenuItemsContainer hasMaxHeight>
{loading ? (
<MenuItem text="Loading..." />
) : (
<>
<SelectableList
selectableListId={MultiObjectRecordSelectSelectableListId}
selectableItemIdArray={selectableItemIds}
hotkeyScope={RelationPickerHotkeyScope.RelationPicker}
onEnter={(recordId) => {
const recordIsSelected = internalSelectedRecords?.some(
(selectedRecord) => selectedRecord.record.id === recordId,
);
}
}}
>
{entitiesInDropdown?.map((objectRecordForSelect) => (
<StyledSelectableItem
itemId={objectRecordForSelect.record.id}
key={objectRecordForSelect.record.id + v4()}
>
<MenuItemMultiSelectAvatar
const correspondingRecordForSelect = entitiesInDropdown?.find(
(entity) => entity.record.id === recordId,
);
if (correspondingRecordForSelect) {
handleSelectChange(
correspondingRecordForSelect,
!recordIsSelected,
);
}
}}
>
{entitiesInDropdown?.map((objectRecordForSelect) => (
<MultipleObjectRecordSelectItem
key={objectRecordForSelect.record.id}
objectRecordForSelect={objectRecordForSelect}
onSelectedChange={(newSelectedValue) =>
handleSelectChange(
objectRecordForSelect,
newSelectedValue,
)
}
selected={internalSelectedRecords?.some(
(selectedRecord) => {
return (
@ -179,34 +178,16 @@ export const MultipleObjectRecordSelect = ({
);
},
)}
onSelectChange={(newCheckedValue) =>
handleSelectChange(objectRecordForSelect, newCheckedValue)
}
avatar={
<Avatar
avatarUrl={
objectRecordForSelect.recordIdentifier.avatarUrl
}
colorId={objectRecordForSelect.record.id}
placeholder={
objectRecordForSelect.recordIdentifier.name
}
size="md"
type={
objectRecordForSelect.recordIdentifier.avatarType ??
'rounded'
}
/>
}
text={objectRecordForSelect.recordIdentifier.name}
/>
</StyledSelectableItem>
))}
</SelectableList>
{entitiesInDropdown?.length === 0 && <MenuItem text="No result" />}
</>
)}
</DropdownMenuItemsContainer>
</DropdownMenu>
))}
</SelectableList>
{entitiesInDropdown?.length === 0 && (
<MenuItem text="No result" />
)}
</>
)}
</DropdownMenuItemsContainer>
</DropdownMenu>
</>
);
};

View File

@ -0,0 +1,58 @@
import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
import { v4 } from 'uuid';
import { MultiObjectRecordSelectSelectableListId } from '@/object-record/relation-picker/constants/MultiObjectRecordSelectSelectableListId';
import { ObjectRecordForSelect } from '@/object-record/relation-picker/hooks/useMultiObjectSearch';
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { MenuItemMultiSelectAvatar } from '@/ui/navigation/menu-item/components/MenuItemMultiSelectAvatar';
import { Avatar } from '@/users/components/Avatar';
export const StyledSelectableItem = styled(SelectableItem)`
height: 100%;
width: 100%;
`;
export const MultipleObjectRecordSelectItem = ({
objectRecordForSelect,
onSelectedChange,
selected,
}: {
objectRecordForSelect: ObjectRecordForSelect;
onSelectedChange?: (selected: boolean) => void;
selected: boolean;
}) => {
const { isSelectedItemIdSelector } = useSelectableList(
MultiObjectRecordSelectSelectableListId,
);
const isSelectedByKeyboard = useRecoilValue(
isSelectedItemIdSelector(objectRecordForSelect.record.id),
);
return (
<StyledSelectableItem
itemId={objectRecordForSelect.record.id}
key={objectRecordForSelect.record.id + v4()}
>
<MenuItemMultiSelectAvatar
selected={selected}
onSelectChange={onSelectedChange}
isKeySelected={isSelectedByKeyboard}
avatar={
<Avatar
avatarUrl={objectRecordForSelect.recordIdentifier.avatarUrl}
colorId={objectRecordForSelect.record.id}
placeholder={objectRecordForSelect.recordIdentifier.name}
size="md"
type={
objectRecordForSelect.recordIdentifier.avatarType ?? 'rounded'
}
/>
}
text={objectRecordForSelect.recordIdentifier.name}
/>
</StyledSelectableItem>
);
};

View File

@ -0,0 +1,2 @@
export const MultiObjectRecordSelectSelectableListId =
'multi-object-record-select-selectable-list';

View File

@ -0,0 +1,10 @@
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect';
export type EntitiesForMultipleEntitySelect<
CustomEntityForSelect extends EntityForSelect,
> = {
selectedEntities: CustomEntityForSelect[];
filteredSelectedEntities: CustomEntityForSelect[];
entitiesToSelect: CustomEntityForSelect[];
loading: boolean;
};

View File

@ -2,7 +2,7 @@ import { isNonEmptyString } from '@sniptt/guards';
import { OrderBy } from '@/object-metadata/types/OrderBy';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { EntitiesForMultipleEntitySelect } from '@/object-record/relation-picker/components/MultipleEntitySelect';
import { EntitiesForMultipleEntitySelect } from '@/object-record/relation-picker/types/EntitiesForMultipleEntitySelect';
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect';
import { assertNotNull } from '~/utils/assert';
import { isDefined } from '~/utils/isDefined';