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:
gitstart-twenty 2024-01-11 20:26:11 +01:00 committed by GitHub
parent 99247fb689
commit 299bed511f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 231 additions and 56 deletions

View File

@ -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>
);
};

View File

@ -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 { FieldDefinition } from '@/object-record/field/types/FieldDefinition';
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 { useRelationPicker } from '@/object-record/relation-picker/hooks/useRelationPicker';
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 { IconForbid } from '@/ui/display/icon';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { isDefined } from '~/utils/isDefined';
export type RelationPickerProps = {
recordId?: string;
@ -35,10 +42,24 @@ export const RelationPicker = ({
searchQuery,
} = useRelationPicker();
const [showAddNewDropdown, setShowAddNewDropdown] = useState(false);
const { setHotkeyScopeAndMemorizePreviousScope } = usePreviousHotkeyScope();
useEffect(() => {
setRelationPickerSearchFilter(initialSearchFilter ?? '');
}, [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 } =
useObjectNameSingularFromPlural({
objectNamePlural:
@ -69,16 +90,41 @@ export const RelationPicker = ({
const handleEntitySelected = (selectedEntity: any | null | undefined) =>
onSubmit(selectedEntity ?? null);
const entitiesToSelect = entities.entitiesToSelect.filter((entity) =>
weAreInOpportunitiesPageCard ? entity.record.companyId === companyId : true,
);
const weAreAddingNewPerson =
weAreInOpportunitiesPageCard && showAddNewDropdown && companyId;
return (
<SingleEntitySelect
EmptyIcon={IconForbid}
emptyLabel={'No ' + fieldDefinition.label}
entitiesToSelect={entities.entitiesToSelect}
loading={entities.loading}
onCancel={onCancel}
onEntitySelected={handleEntitySelected}
selectedEntity={entities.selectedEntities[0]}
width={width}
/>
<>
{!weAreAddingNewPerson ? (
<SingleEntitySelect
EmptyIcon={IconForbid}
emptyLabel={'No ' + fieldDefinition.label}
entitiesToSelect={entitiesToSelect}
loading={entities.loading}
onCancel={onCancel}
onEntitySelected={handleEntitySelected}
selectedEntity={entities.selectedEntities[0]}
width={width}
onCreate={() => {
if (weAreInOpportunitiesPageCard) {
setShowAddNewDropdown(true);
setHotkeyScopeAndMemorizePreviousScope(
RelationPickerHotkeyScope.AddNew,
);
}
}}
/>
) : (
<AddPersonToCompany
companyId={companyId}
onEntitySelected={handleEntitySelected}
closeDropdown={() => setShowAddNewDropdown(false)}
/>
)}
</>
);
};

View File

@ -30,7 +30,14 @@ export const SingleEntitySelect = ({
refs: [containerRef],
callback: (event) => {
event.stopImmediatePropagation();
onCancel?.();
const weAreNotInAnHTMLInput = !(
event.target instanceof HTMLInputElement &&
event.target.tagName === 'INPUT'
);
if (weAreNotInAnHTMLInput && onCancel) {
onCancel();
}
},
});

View File

@ -1,7 +1,8 @@
import { useRef } from 'react';
import { useContext, useRef } from 'react';
import { isNonEmptyString } from '@sniptt/guards';
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 { IconPlus } from '@/ui/display/icon';
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 { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { assertNotNull } from '~/utils/assert';
import { isDefined } from '~/utils/isDefined';
import { EntityForSelect } from '../types/EntityForSelect';
import { RelationPickerHotkeyScope } from '../types/RelationPickerHotkeyScope';
@ -69,6 +71,12 @@ export const SingleEntitySelectMenuItems = ({
const selectableItemIds = entitiesInDropdown.map((entity) => entity.id);
const boardCardId = useContext(BoardCardIdContext);
const weAreInOpportunitiesPageCard = isDefined(boardCardId);
const hideSearchResults =
weAreInOpportunitiesPageCard && !entitiesInDropdown.length;
return (
<div ref={containerRef}>
<SelectableList
@ -86,33 +94,39 @@ export const SingleEntitySelectMenuItems = ({
}
}}
>
<DropdownMenuItemsContainer hasMaxHeight>
{loading ? (
<DropdownMenuSkeletonItem />
) : entitiesInDropdown.length === 0 && !isAllEntitySelectShown ? (
<MenuItem text="No result" />
) : (
<>
{isAllEntitySelectShown &&
selectAllLabel &&
onAllEntitySelected && (
<MenuItemSelect
key="select-all"
onClick={() => onAllEntitySelected()}
LeftIcon={SelectAllIcon}
text={selectAllLabel}
selected={!!isAllEntitySelected}
/>
)}
{emptyLabel && (
<MenuItemSelect
key="select-none"
onClick={() => onEntitySelected()}
LeftIcon={EmptyIcon}
text={emptyLabel}
selected={!selectedEntity}
/>
{!hideSearchResults && (
<>
<DropdownMenuItemsContainer hasMaxHeight>
{loading ? (
<DropdownMenuSkeletonItem />
) : entitiesInDropdown.length === 0 && !isAllEntitySelectShown ? (
<MenuItem text="No result" />
) : (
<>
{isAllEntitySelectShown &&
selectAllLabel &&
onAllEntitySelected && (
<MenuItemSelect
key="select-all"
onClick={() => onAllEntitySelected()}
LeftIcon={SelectAllIcon}
text={selectAllLabel}
selected={!!isAllEntitySelected}
/>
)}
{emptyLabel && (
<MenuItemSelect
key="select-none"
onClick={() => onEntitySelected()}
LeftIcon={EmptyIcon}
text={emptyLabel}
selected={!selectedEntity}
/>
)}
</>
)}
</DropdownMenuItemsContainer>
<DropdownMenuItemsContainer hasMaxHeight>
{entitiesInDropdown?.map((entity) => (
<SelectableMenuItemSelect
key={entity.id}
@ -121,21 +135,19 @@ export const SingleEntitySelectMenuItems = ({
selectedEntity={selectedEntity}
/>
))}
</>
)}
</DropdownMenuItemsContainer>
{showCreateButton && (
<>
<DropdownMenuItemsContainer hasMaxHeight>
<DropdownMenuSeparator />
<CreateNewButton
onClick={onCreate}
LeftIcon={IconPlus}
text="Add New"
/>
</DropdownMenuItemsContainer>
</>
)}
{(hideSearchResults || showCreateButton) && !loading && (
<DropdownMenuItemsContainer hasMaxHeight>
{entitiesToSelect.length > 0 && <DropdownMenuSeparator />}
<CreateNewButton
onClick={onCreate}
LeftIcon={IconPlus}
text="Add New"
/>
</DropdownMenuItemsContainer>
)}
</SelectableList>
</div>
);

View File

@ -1,3 +1,6 @@
import { useContext } from 'react';
import { BoardCardIdContext } from '@/object-record/record-board/contexts/BoardCardIdContext';
import {
SingleEntitySelectMenuItems,
SingleEntitySelectMenuItemsProps,
@ -35,13 +38,20 @@ export const SingleEntitySelectMenuItemsWithSearch = ({
const showCreateButton = isDefined(onCreate) && searchFilter !== '';
const boardCardId = useContext(BoardCardIdContext);
const weAreInOpportunitiesPageCard = isDefined(boardCardId);
const hideSearchInput =
weAreInOpportunitiesPageCard && !entitiesToSelect.length && !selectedEntity;
return (
<>
<DropdownMenuSearchInput
value={searchFilter}
onChange={handleSearchFilterChange}
autoFocus
/>
{!hideSearchInput && (
<DropdownMenuSearchInput
value={searchFilter}
onChange={handleSearchFilterChange}
autoFocus
/>
)}
<DropdownMenuSeparator />
<SingleEntitySelectMenuItems
{...{

View File

@ -1,3 +1,4 @@
export enum RelationPickerHotkeyScope {
RelationPicker = 'relation-picker',
AddNew = 'add-new',
}