diff --git a/front/src/modules/companies/components/CompanyBoardCard.tsx b/front/src/modules/companies/components/CompanyBoardCard.tsx index b9941a304..cc1002849 100644 --- a/front/src/modules/companies/components/CompanyBoardCard.tsx +++ b/front/src/modules/companies/components/CompanyBoardCard.tsx @@ -4,6 +4,7 @@ import styled from '@emotion/styled'; import { useRecoilState } from 'recoil'; import { companyProgressesFamilyState } from '@/companies/states/companyProgressesFamilyState'; +import { PipelineProgressPointOfContactEditableField } from '@/pipeline/editable-field/components/PipelineProgressPointOfContactEditableField'; import { ProbabilityEditableField } from '@/pipeline/editable-field/components/ProbabilityEditableField'; import { GET_PIPELINE_PROGRESS, GET_PIPELINES } from '@/pipeline/queries'; import { BoardCardContext } from '@/pipeline/states/BoardCardContext'; @@ -167,12 +168,15 @@ export function CompanyBoardCard() { } value={pipelineProgress.probability} - onSubmit={(value) => + onSubmit={(value) => { handleCardUpdate({ ...pipelineProgress, probability: value, - }) - } + }); + }} + /> + diff --git a/front/src/modules/people/components/FilterDropdownPeopleSearchSelect.tsx b/front/src/modules/people/components/FilterDropdownPeopleSearchSelect.tsx new file mode 100644 index 000000000..7e079cae3 --- /dev/null +++ b/front/src/modules/people/components/FilterDropdownPeopleSearchSelect.tsx @@ -0,0 +1,38 @@ +import { Context } from 'react'; + +import { useFilteredSearchPeopleQuery } from '@/people/queries'; +import { FilterDropdownEntitySearchSelect } from '@/ui/filter-n-sort/components/FilterDropdownEntitySearchSelect'; +import { filterDropdownSearchInputScopedState } from '@/ui/filter-n-sort/states/filterDropdownSearchInputScopedState'; +import { filterDropdownSelectedEntityIdScopedState } from '@/ui/filter-n-sort/states/filterDropdownSelectedEntityIdScopedState'; +import { useRecoilScopedState } from '@/ui/recoil-scope/hooks/useRecoilScopedState'; +import { useRecoilScopedValue } from '@/ui/recoil-scope/hooks/useRecoilScopedValue'; + +export function FilterDropdownPeopleSearchSelect({ + context, +}: { + context: Context; +}) { + const filterDropdownSearchInput = useRecoilScopedValue( + filterDropdownSearchInputScopedState, + context, + ); + + const [filterDropdownSelectedEntityId] = useRecoilScopedState( + filterDropdownSelectedEntityIdScopedState, + context, + ); + + const peopleForSelect = useFilteredSearchPeopleQuery({ + searchFilter: filterDropdownSearchInput, + selectedIds: filterDropdownSelectedEntityId + ? [filterDropdownSelectedEntityId] + : [], + }); + + return ( + + ); +} diff --git a/front/src/modules/pipeline/components/PipelineProgressPointOfContactPicker.tsx b/front/src/modules/pipeline/components/PipelineProgressPointOfContactPicker.tsx new file mode 100644 index 000000000..05cd2d7df --- /dev/null +++ b/front/src/modules/pipeline/components/PipelineProgressPointOfContactPicker.tsx @@ -0,0 +1,87 @@ +import { getOperationName } from '@apollo/client/utilities'; +import { Key } from 'ts-key-enum'; + +import { useFilteredSearchPeopleQuery } from '@/people/queries'; +import { useScopedHotkeys } from '@/ui/hotkey/hooks/useScopedHotkeys'; +import { useRecoilScopedState } from '@/ui/recoil-scope/hooks/useRecoilScopedState'; +import { SingleEntitySelect } from '@/ui/relation-picker/components/SingleEntitySelect'; +import { relationPickerSearchFilterScopedState } from '@/ui/relation-picker/states/relationPickerSearchFilterScopedState'; +import { RelationPickerHotkeyScope } from '@/ui/relation-picker/types/RelationPickerHotkeyScope'; +import { isCreateModeScopedState } from '@/ui/table/editable-cell/states/isCreateModeScopedState'; +import { + Person, + PipelineProgress, + useUpdateOnePipelineProgressMutation, +} from '~/generated/graphql'; + +import { EntityForSelect } from '../../ui/relation-picker/types/EntityForSelect'; +import { GET_PIPELINE_PROGRESS, GET_PIPELINES } from '../queries'; + +export type OwnProps = { + pipelineProgress: Pick & { + pointOfContact?: Pick | null; + }; + onSubmit?: () => void; + onCancel?: () => void; +}; + +export function PipelineProgressPointOfContactPicker({ + pipelineProgress, + onSubmit, + onCancel, +}: OwnProps) { + const [, setIsCreating] = useRecoilScopedState(isCreateModeScopedState); + + const [searchFilter] = useRecoilScopedState( + relationPickerSearchFilterScopedState, + ); + const [updatePipelineProgress] = useUpdateOnePipelineProgressMutation(); + + const people = useFilteredSearchPeopleQuery({ + searchFilter, + selectedIds: pipelineProgress.pointOfContact?.id + ? [pipelineProgress.pointOfContact.id] + : [], + }); + + async function handleEntitySelected(entity: EntityForSelect) { + await updatePipelineProgress({ + variables: { + ...pipelineProgress, + pointOfContactId: entity.id, + }, + refetchQueries: [ + getOperationName(GET_PIPELINE_PROGRESS) ?? '', + getOperationName(GET_PIPELINES) ?? '', + ], + }); + + onSubmit?.(); + } + + function handleCreate() { + setIsCreating(true); + onSubmit?.(); + } + + useScopedHotkeys( + Key.Escape, + () => { + onCancel && onCancel(); + }, + RelationPickerHotkeyScope.RelationPicker, + [], + ); + + return ( + + ); +} diff --git a/front/src/modules/pipeline/editable-field/components/PipelineProgressPointOfContactEditableField.tsx b/front/src/modules/pipeline/editable-field/components/PipelineProgressPointOfContactEditableField.tsx new file mode 100644 index 000000000..1867aaf74 --- /dev/null +++ b/front/src/modules/pipeline/editable-field/components/PipelineProgressPointOfContactEditableField.tsx @@ -0,0 +1,51 @@ +import { PersonChip } from '@/people/components/PersonChip'; +import { EditableField } from '@/ui/editable-field/components/EditableField'; +import { FieldContext } from '@/ui/editable-field/states/FieldContext'; +import { IconUser } from '@/ui/icon'; +import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope'; +import { RelationPickerHotkeyScope } from '@/ui/relation-picker/types/RelationPickerHotkeyScope'; +import { Person, PipelineProgress } from '~/generated/graphql'; + +import { PipelineProgressPointOfContactPickerFieldEditMode } from './PipelineProgressPointOfContactPickerFieldEditMode'; + +type OwnProps = { + pipelineProgress: Pick & { + pointOfContact?: Pick | null; + }; +}; + +export function PipelineProgressPointOfContactEditableField({ + pipelineProgress, +}: OwnProps) { + return ( + + + } + editModeContent={ + + } + displayModeContent={ + pipelineProgress.pointOfContact ? ( + + ) : ( + <> + ) + } + isDisplayModeContentEmpty={!pipelineProgress.pointOfContact} + /> + + + ); +} diff --git a/front/src/modules/pipeline/editable-field/components/PipelineProgressPointOfContactPickerFieldEditMode.tsx b/front/src/modules/pipeline/editable-field/components/PipelineProgressPointOfContactPickerFieldEditMode.tsx new file mode 100644 index 000000000..f04078942 --- /dev/null +++ b/front/src/modules/pipeline/editable-field/components/PipelineProgressPointOfContactPickerFieldEditMode.tsx @@ -0,0 +1,47 @@ +import styled from '@emotion/styled'; + +import { PipelineProgressPointOfContactPicker } from '@/pipeline/components/PipelineProgressPointOfContactPicker'; +import { useEditableField } from '@/ui/editable-field/hooks/useEditableField'; +import { Person, PipelineProgress } from '~/generated/graphql'; + +const PipelineProgressPointOfContactPickerContainer = styled.div` + left: 24px; + position: absolute; + top: -8px; +`; + +export type OwnProps = { + pipelineProgress: Pick & { + pointOfContact?: Pick | null; + }; + onSubmit?: () => void; + onCancel?: () => void; +}; + +export function PipelineProgressPointOfContactPickerFieldEditMode({ + pipelineProgress, + onSubmit, + onCancel, +}: OwnProps) { + const { closeEditableField } = useEditableField(); + + function handleSubmit() { + closeEditableField(); + onSubmit?.(); + } + + function handleCancel() { + closeEditableField(); + onCancel?.(); + } + + return ( + + + + ); +} diff --git a/front/src/modules/pipeline/editable-field/components/ProbabilityEditableField.tsx b/front/src/modules/pipeline/editable-field/components/ProbabilityEditableField.tsx index 9c021943a..1aebc3056 100644 --- a/front/src/modules/pipeline/editable-field/components/ProbabilityEditableField.tsx +++ b/front/src/modules/pipeline/editable-field/components/ProbabilityEditableField.tsx @@ -1,5 +1,3 @@ -import { useEffect, useState } from 'react'; - import { EditableField } from '@/ui/editable-field/components/EditableField'; import { FieldContext } from '@/ui/editable-field/states/FieldContext'; import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope'; @@ -13,57 +11,14 @@ type OwnProps = { }; export function ProbabilityEditableField({ icon, value, onSubmit }: OwnProps) { - const [internalValue, setInternalValue] = useState(value); - - useEffect(() => { - setInternalValue(value); - }, [value]); - - async function handleChange(newValue: number) { - setInternalValue(newValue); - } - - async function handleSubmit() { - if (!internalValue) return; - - try { - const numberValue = internalValue; - - if (isNaN(numberValue)) { - throw new Error('Not a number'); - } - - if (numberValue < 0 || numberValue > 100) { - throw new Error('Not a probability'); - } - - onSubmit?.(numberValue); - - setInternalValue(numberValue); - } catch { - handleCancel(); - } - } - - async function handleCancel() { - setInternalValue(value); - } - return ( { - handleChange(newValue); - }} - /> + } /> diff --git a/front/src/modules/pipeline/editable-field/components/ProbabilityFieldEditMode.tsx b/front/src/modules/pipeline/editable-field/components/ProbabilityFieldEditMode.tsx index 5c47c1b27..ec74aa5a7 100644 --- a/front/src/modules/pipeline/editable-field/components/ProbabilityFieldEditMode.tsx +++ b/front/src/modules/pipeline/editable-field/components/ProbabilityFieldEditMode.tsx @@ -90,6 +90,7 @@ export function ProbabilityFieldEditMode({ value, onChange }: OwnProps) { {PROBABILITY_VALUES.map((probability, i) => ( handleChange(probability.value)} onMouseEnter={() => setNextProbabilityIndex(i)} onMouseLeave={() => setNextProbabilityIndex(null)} diff --git a/front/src/pages/opportunities/opportunities-filters.tsx b/front/src/pages/opportunities/opportunities-filters.tsx index 7874e5859..4d5d21fcf 100644 --- a/front/src/pages/opportunities/opportunities-filters.tsx +++ b/front/src/pages/opportunities/opportunities-filters.tsx @@ -5,10 +5,13 @@ import { IconBuildingSkyscraper, IconCalendarEvent, IconCurrencyDollar, + IconUser, } from '@/ui/icon/index'; import { icon } from '@/ui/themes/icon'; import { PipelineProgress } from '~/generated/graphql'; +import { FilterDropdownPeopleSearchSelect } from '../../modules/people/components/FilterDropdownPeopleSearchSelect'; + export const opportunitiesFilters: FilterDefinitionByEntity[] = [ { @@ -34,4 +37,13 @@ export const opportunitiesFilters: FilterDefinitionByEntity[] ), }, + { + field: 'pointOfContactId', + label: 'Point of contact', + icon: , + type: 'entity', + entitySelectComponent: ( + + ), + }, ];