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: (
+
+ ),
+ },
];