mirror of
https://github.com/twentyhq/twenty.git
synced 2024-11-27 11:03:40 +03:00
Filter the opportunities "Point of contact" field (#3191)
* Filter the opportunities "Point of contact" field Co-authored-by: Toledodev <rafael.toledo@engenharia.ufjf.br> Co-authored-by: Rafael Toledo <87545086+Toledodev@users.noreply.github.com> * Refactor according to review Co-authored-by: v1b3m <vibenjamin6@gmail.com> Co-authored-by: Toledodev <rafael.toledo@engenharia.ufjf.br> Co-authored-by: Rafael Toledo <87545086+Toledodev@users.noreply.github.com> --------- Co-authored-by: gitstart-twenty <gitstart-twenty@users.noreply.github.com> Co-authored-by: Toledodev <rafael.toledo@engenharia.ufjf.br> Co-authored-by: Rafael Toledo <87545086+Toledodev@users.noreply.github.com> Co-authored-by: v1b3m <vibenjamin6@gmail.com> Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
parent
99247fb689
commit
299bed511f
@ -0,0 +1,99 @@
|
|||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
|
import { FieldDoubleText } from '@/object-record/field/types/FieldDoubleText';
|
||||||
|
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
|
||||||
|
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect';
|
||||||
|
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
|
||||||
|
import { Person } from '@/people/types/Person';
|
||||||
|
import { DoubleTextInput } from '@/ui/field/input/components/DoubleTextInput';
|
||||||
|
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||||
|
|
||||||
|
export const StyledInputContainer = styled.div`
|
||||||
|
background-color: transparent;
|
||||||
|
box-shadow: ${({ theme }) => theme.boxShadow.strong};
|
||||||
|
display: flex;
|
||||||
|
gap: ${({ theme }) => theme.spacing(0.5)};
|
||||||
|
width: ${({ theme }) => theme.spacing(62.5)};
|
||||||
|
& input,
|
||||||
|
div {
|
||||||
|
background-color: ${({ theme }) => theme.background.primary};
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
border-radius: ${({ theme }) => theme.spacing(1)};
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
display: flex;
|
||||||
|
flex-grow: 1;
|
||||||
|
padding: ${({ theme }) => theme.spacing(2)};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
type AddPersonToCompanyProps = {
|
||||||
|
companyId: string;
|
||||||
|
onEntitySelected: (entity?: EntityForSelect | undefined) => void;
|
||||||
|
closeDropdown?: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AddPersonToCompany = ({
|
||||||
|
companyId,
|
||||||
|
onEntitySelected,
|
||||||
|
closeDropdown,
|
||||||
|
}: AddPersonToCompanyProps) => {
|
||||||
|
const { goBackToPreviousHotkeyScope } = usePreviousHotkeyScope();
|
||||||
|
|
||||||
|
const handleEscape = () => {
|
||||||
|
goBackToPreviousHotkeyScope();
|
||||||
|
closeDropdown?.();
|
||||||
|
};
|
||||||
|
|
||||||
|
const { createOneRecord: createPerson } = useCreateOneRecord<Person>({
|
||||||
|
objectNameSingular: 'person',
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleCreatePerson = async ({
|
||||||
|
firstValue,
|
||||||
|
secondValue,
|
||||||
|
}: FieldDoubleText) => {
|
||||||
|
if (!firstValue && !secondValue) return;
|
||||||
|
|
||||||
|
const person = await createPerson({
|
||||||
|
companyId,
|
||||||
|
id: v4(),
|
||||||
|
name: {
|
||||||
|
firstName: firstValue,
|
||||||
|
lastName: secondValue,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (person) {
|
||||||
|
const entityForSelect: EntityForSelect = {
|
||||||
|
id: person.id,
|
||||||
|
name: person.name?.firstName ?? '',
|
||||||
|
avatarUrl: person.avatarUrl ?? '',
|
||||||
|
avatarType: 'rounded',
|
||||||
|
record: person,
|
||||||
|
};
|
||||||
|
onEntitySelected(entityForSelect);
|
||||||
|
}
|
||||||
|
goBackToPreviousHotkeyScope();
|
||||||
|
closeDropdown?.();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledInputContainer>
|
||||||
|
<DoubleTextInput
|
||||||
|
firstValue=""
|
||||||
|
secondValue=""
|
||||||
|
firstValuePlaceholder="First Name"
|
||||||
|
secondValuePlaceholder="Last Name"
|
||||||
|
onClickOutside={handleEscape}
|
||||||
|
onEnter={handleCreatePerson}
|
||||||
|
onEscape={handleEscape}
|
||||||
|
hotkeyScope={RelationPickerHotkeyScope.AddNew}
|
||||||
|
/>
|
||||||
|
</StyledInputContainer>
|
||||||
|
);
|
||||||
|
};
|
@ -1,13 +1,20 @@
|
|||||||
import { useEffect } from 'react';
|
import { useContext, useEffect, useState } from 'react';
|
||||||
|
import { useRecoilState } from 'recoil';
|
||||||
|
|
||||||
|
import { AddPersonToCompany } from '@/companies/components/AddPersonToCompany';
|
||||||
|
import { companyProgressesFamilyState } from '@/companies/states/companyProgressesFamilyState';
|
||||||
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
|
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
|
||||||
import { FieldDefinition } from '@/object-record/field/types/FieldDefinition';
|
import { FieldDefinition } from '@/object-record/field/types/FieldDefinition';
|
||||||
import { FieldRelationMetadata } from '@/object-record/field/types/FieldMetadata';
|
import { FieldRelationMetadata } from '@/object-record/field/types/FieldMetadata';
|
||||||
|
import { BoardCardIdContext } from '@/object-record/record-board/contexts/BoardCardIdContext';
|
||||||
import { SingleEntitySelect } from '@/object-record/relation-picker/components/SingleEntitySelect';
|
import { SingleEntitySelect } from '@/object-record/relation-picker/components/SingleEntitySelect';
|
||||||
import { useRelationPicker } from '@/object-record/relation-picker/hooks/useRelationPicker';
|
import { useRelationPicker } from '@/object-record/relation-picker/hooks/useRelationPicker';
|
||||||
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect';
|
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect';
|
||||||
|
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
|
||||||
import { useFilteredSearchEntityQuery } from '@/search/hooks/useFilteredSearchEntityQuery';
|
import { useFilteredSearchEntityQuery } from '@/search/hooks/useFilteredSearchEntityQuery';
|
||||||
import { IconForbid } from '@/ui/display/icon';
|
import { IconForbid } from '@/ui/display/icon';
|
||||||
|
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||||
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
export type RelationPickerProps = {
|
export type RelationPickerProps = {
|
||||||
recordId?: string;
|
recordId?: string;
|
||||||
@ -35,10 +42,24 @@ export const RelationPicker = ({
|
|||||||
searchQuery,
|
searchQuery,
|
||||||
} = useRelationPicker();
|
} = useRelationPicker();
|
||||||
|
|
||||||
|
const [showAddNewDropdown, setShowAddNewDropdown] = useState(false);
|
||||||
|
|
||||||
|
const { setHotkeyScopeAndMemorizePreviousScope } = usePreviousHotkeyScope();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setRelationPickerSearchFilter(initialSearchFilter ?? '');
|
setRelationPickerSearchFilter(initialSearchFilter ?? '');
|
||||||
}, [initialSearchFilter, setRelationPickerSearchFilter]);
|
}, [initialSearchFilter, setRelationPickerSearchFilter]);
|
||||||
|
|
||||||
|
const boardCardId = useContext(BoardCardIdContext);
|
||||||
|
const weAreInOpportunitiesPageCard = isDefined(boardCardId);
|
||||||
|
|
||||||
|
const [companyProgress] = useRecoilState(
|
||||||
|
companyProgressesFamilyState(boardCardId ?? ''),
|
||||||
|
);
|
||||||
|
|
||||||
|
const { company } = companyProgress ?? {};
|
||||||
|
const companyId = company?.id;
|
||||||
|
|
||||||
const { objectNameSingular: relationObjectNameSingular } =
|
const { objectNameSingular: relationObjectNameSingular } =
|
||||||
useObjectNameSingularFromPlural({
|
useObjectNameSingularFromPlural({
|
||||||
objectNamePlural:
|
objectNamePlural:
|
||||||
@ -69,16 +90,41 @@ export const RelationPicker = ({
|
|||||||
const handleEntitySelected = (selectedEntity: any | null | undefined) =>
|
const handleEntitySelected = (selectedEntity: any | null | undefined) =>
|
||||||
onSubmit(selectedEntity ?? null);
|
onSubmit(selectedEntity ?? null);
|
||||||
|
|
||||||
|
const entitiesToSelect = entities.entitiesToSelect.filter((entity) =>
|
||||||
|
weAreInOpportunitiesPageCard ? entity.record.companyId === companyId : true,
|
||||||
|
);
|
||||||
|
|
||||||
|
const weAreAddingNewPerson =
|
||||||
|
weAreInOpportunitiesPageCard && showAddNewDropdown && companyId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SingleEntitySelect
|
<>
|
||||||
EmptyIcon={IconForbid}
|
{!weAreAddingNewPerson ? (
|
||||||
emptyLabel={'No ' + fieldDefinition.label}
|
<SingleEntitySelect
|
||||||
entitiesToSelect={entities.entitiesToSelect}
|
EmptyIcon={IconForbid}
|
||||||
loading={entities.loading}
|
emptyLabel={'No ' + fieldDefinition.label}
|
||||||
onCancel={onCancel}
|
entitiesToSelect={entitiesToSelect}
|
||||||
onEntitySelected={handleEntitySelected}
|
loading={entities.loading}
|
||||||
selectedEntity={entities.selectedEntities[0]}
|
onCancel={onCancel}
|
||||||
width={width}
|
onEntitySelected={handleEntitySelected}
|
||||||
/>
|
selectedEntity={entities.selectedEntities[0]}
|
||||||
|
width={width}
|
||||||
|
onCreate={() => {
|
||||||
|
if (weAreInOpportunitiesPageCard) {
|
||||||
|
setShowAddNewDropdown(true);
|
||||||
|
setHotkeyScopeAndMemorizePreviousScope(
|
||||||
|
RelationPickerHotkeyScope.AddNew,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<AddPersonToCompany
|
||||||
|
companyId={companyId}
|
||||||
|
onEntitySelected={handleEntitySelected}
|
||||||
|
closeDropdown={() => setShowAddNewDropdown(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -30,7 +30,14 @@ export const SingleEntitySelect = ({
|
|||||||
refs: [containerRef],
|
refs: [containerRef],
|
||||||
callback: (event) => {
|
callback: (event) => {
|
||||||
event.stopImmediatePropagation();
|
event.stopImmediatePropagation();
|
||||||
onCancel?.();
|
|
||||||
|
const weAreNotInAnHTMLInput = !(
|
||||||
|
event.target instanceof HTMLInputElement &&
|
||||||
|
event.target.tagName === 'INPUT'
|
||||||
|
);
|
||||||
|
if (weAreNotInAnHTMLInput && onCancel) {
|
||||||
|
onCancel();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { useRef } from 'react';
|
import { useContext, useRef } from 'react';
|
||||||
import { isNonEmptyString } from '@sniptt/guards';
|
import { isNonEmptyString } from '@sniptt/guards';
|
||||||
import { Key } from 'ts-key-enum';
|
import { Key } from 'ts-key-enum';
|
||||||
|
|
||||||
|
import { BoardCardIdContext } from '@/object-record/record-board/contexts/BoardCardIdContext';
|
||||||
import { SelectableMenuItemSelect } from '@/object-record/relation-picker/components/SelectableMenuItemSelect';
|
import { SelectableMenuItemSelect } from '@/object-record/relation-picker/components/SelectableMenuItemSelect';
|
||||||
import { IconPlus } from '@/ui/display/icon';
|
import { IconPlus } from '@/ui/display/icon';
|
||||||
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
|
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
|
||||||
@ -14,6 +15,7 @@ import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
|||||||
import { MenuItemSelect } from '@/ui/navigation/menu-item/components/MenuItemSelect';
|
import { MenuItemSelect } from '@/ui/navigation/menu-item/components/MenuItemSelect';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
import { assertNotNull } from '~/utils/assert';
|
import { assertNotNull } from '~/utils/assert';
|
||||||
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
import { EntityForSelect } from '../types/EntityForSelect';
|
import { EntityForSelect } from '../types/EntityForSelect';
|
||||||
import { RelationPickerHotkeyScope } from '../types/RelationPickerHotkeyScope';
|
import { RelationPickerHotkeyScope } from '../types/RelationPickerHotkeyScope';
|
||||||
@ -69,6 +71,12 @@ export const SingleEntitySelectMenuItems = ({
|
|||||||
|
|
||||||
const selectableItemIds = entitiesInDropdown.map((entity) => entity.id);
|
const selectableItemIds = entitiesInDropdown.map((entity) => entity.id);
|
||||||
|
|
||||||
|
const boardCardId = useContext(BoardCardIdContext);
|
||||||
|
const weAreInOpportunitiesPageCard = isDefined(boardCardId);
|
||||||
|
|
||||||
|
const hideSearchResults =
|
||||||
|
weAreInOpportunitiesPageCard && !entitiesInDropdown.length;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={containerRef}>
|
<div ref={containerRef}>
|
||||||
<SelectableList
|
<SelectableList
|
||||||
@ -86,33 +94,39 @@ export const SingleEntitySelectMenuItems = ({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DropdownMenuItemsContainer hasMaxHeight>
|
{!hideSearchResults && (
|
||||||
{loading ? (
|
<>
|
||||||
<DropdownMenuSkeletonItem />
|
<DropdownMenuItemsContainer hasMaxHeight>
|
||||||
) : entitiesInDropdown.length === 0 && !isAllEntitySelectShown ? (
|
{loading ? (
|
||||||
<MenuItem text="No result" />
|
<DropdownMenuSkeletonItem />
|
||||||
) : (
|
) : entitiesInDropdown.length === 0 && !isAllEntitySelectShown ? (
|
||||||
<>
|
<MenuItem text="No result" />
|
||||||
{isAllEntitySelectShown &&
|
) : (
|
||||||
selectAllLabel &&
|
<>
|
||||||
onAllEntitySelected && (
|
{isAllEntitySelectShown &&
|
||||||
<MenuItemSelect
|
selectAllLabel &&
|
||||||
key="select-all"
|
onAllEntitySelected && (
|
||||||
onClick={() => onAllEntitySelected()}
|
<MenuItemSelect
|
||||||
LeftIcon={SelectAllIcon}
|
key="select-all"
|
||||||
text={selectAllLabel}
|
onClick={() => onAllEntitySelected()}
|
||||||
selected={!!isAllEntitySelected}
|
LeftIcon={SelectAllIcon}
|
||||||
/>
|
text={selectAllLabel}
|
||||||
)}
|
selected={!!isAllEntitySelected}
|
||||||
{emptyLabel && (
|
/>
|
||||||
<MenuItemSelect
|
)}
|
||||||
key="select-none"
|
{emptyLabel && (
|
||||||
onClick={() => onEntitySelected()}
|
<MenuItemSelect
|
||||||
LeftIcon={EmptyIcon}
|
key="select-none"
|
||||||
text={emptyLabel}
|
onClick={() => onEntitySelected()}
|
||||||
selected={!selectedEntity}
|
LeftIcon={EmptyIcon}
|
||||||
/>
|
text={emptyLabel}
|
||||||
|
selected={!selectedEntity}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
</DropdownMenuItemsContainer>
|
||||||
|
<DropdownMenuItemsContainer hasMaxHeight>
|
||||||
{entitiesInDropdown?.map((entity) => (
|
{entitiesInDropdown?.map((entity) => (
|
||||||
<SelectableMenuItemSelect
|
<SelectableMenuItemSelect
|
||||||
key={entity.id}
|
key={entity.id}
|
||||||
@ -121,21 +135,19 @@ export const SingleEntitySelectMenuItems = ({
|
|||||||
selectedEntity={selectedEntity}
|
selectedEntity={selectedEntity}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</DropdownMenuItemsContainer>
|
|
||||||
{showCreateButton && (
|
|
||||||
<>
|
|
||||||
<DropdownMenuItemsContainer hasMaxHeight>
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<CreateNewButton
|
|
||||||
onClick={onCreate}
|
|
||||||
LeftIcon={IconPlus}
|
|
||||||
text="Add New"
|
|
||||||
/>
|
|
||||||
</DropdownMenuItemsContainer>
|
</DropdownMenuItemsContainer>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{(hideSearchResults || showCreateButton) && !loading && (
|
||||||
|
<DropdownMenuItemsContainer hasMaxHeight>
|
||||||
|
{entitiesToSelect.length > 0 && <DropdownMenuSeparator />}
|
||||||
|
<CreateNewButton
|
||||||
|
onClick={onCreate}
|
||||||
|
LeftIcon={IconPlus}
|
||||||
|
text="Add New"
|
||||||
|
/>
|
||||||
|
</DropdownMenuItemsContainer>
|
||||||
|
)}
|
||||||
</SelectableList>
|
</SelectableList>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
import { useContext } from 'react';
|
||||||
|
|
||||||
|
import { BoardCardIdContext } from '@/object-record/record-board/contexts/BoardCardIdContext';
|
||||||
import {
|
import {
|
||||||
SingleEntitySelectMenuItems,
|
SingleEntitySelectMenuItems,
|
||||||
SingleEntitySelectMenuItemsProps,
|
SingleEntitySelectMenuItemsProps,
|
||||||
@ -35,13 +38,20 @@ export const SingleEntitySelectMenuItemsWithSearch = ({
|
|||||||
|
|
||||||
const showCreateButton = isDefined(onCreate) && searchFilter !== '';
|
const showCreateButton = isDefined(onCreate) && searchFilter !== '';
|
||||||
|
|
||||||
|
const boardCardId = useContext(BoardCardIdContext);
|
||||||
|
const weAreInOpportunitiesPageCard = isDefined(boardCardId);
|
||||||
|
const hideSearchInput =
|
||||||
|
weAreInOpportunitiesPageCard && !entitiesToSelect.length && !selectedEntity;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DropdownMenuSearchInput
|
{!hideSearchInput && (
|
||||||
value={searchFilter}
|
<DropdownMenuSearchInput
|
||||||
onChange={handleSearchFilterChange}
|
value={searchFilter}
|
||||||
autoFocus
|
onChange={handleSearchFilterChange}
|
||||||
/>
|
autoFocus
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<SingleEntitySelectMenuItems
|
<SingleEntitySelectMenuItems
|
||||||
{...{
|
{...{
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
export enum RelationPickerHotkeyScope {
|
export enum RelationPickerHotkeyScope {
|
||||||
RelationPicker = 'relation-picker',
|
RelationPicker = 'relation-picker',
|
||||||
|
AddNew = 'add-new',
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user