diff --git a/front/src/modules/companies/components/CompanyPickerCell.tsx b/front/src/modules/companies/components/CompanyPickerCell.tsx
new file mode 100644
index 0000000000..03c02df853
--- /dev/null
+++ b/front/src/modules/companies/components/CompanyPickerCell.tsx
@@ -0,0 +1,60 @@
+import { useFilteredSearchCompanyQuery } from '@/companies/queries';
+import { useSetHotkeyScope } from '@/ui/hotkey/hooks/useSetHotkeyScope';
+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 { isCreateModeScopedState } from '@/ui/table/editable-cell/states/isCreateModeScopedState';
+import { TableHotkeyScope } from '@/ui/table/types/TableHotkeyScope';
+
+import { EntityForSelect } from '../../ui/relation-picker/types/EntityForSelect';
+
+export type OwnProps = {
+ companyId: string | null;
+ onSubmit: (newCompany: EntityForSelect | null) => void;
+ onCancel?: () => void;
+ createModeEnabled?: boolean;
+};
+
+export function CompanyPickerCell({
+ companyId,
+ onSubmit,
+ onCancel,
+ createModeEnabled,
+}: OwnProps) {
+ const [, setIsCreating] = useRecoilScopedState(isCreateModeScopedState);
+
+ const [searchFilter] = useRecoilScopedState(
+ relationPickerSearchFilterScopedState,
+ );
+
+ const setHotkeyScope = useSetHotkeyScope();
+
+ const companies = useFilteredSearchCompanyQuery({
+ searchFilter,
+ selectedIds: [companyId ?? ''],
+ });
+
+ async function handleEntitySelected(
+ entity: EntityForSelect | null | undefined,
+ ) {
+ onSubmit(entity ?? null);
+ }
+
+ function handleCreate() {
+ setIsCreating(true);
+ setHotkeyScope(TableHotkeyScope.CellDoubleTextInput);
+ }
+
+ return (
+
+ );
+}
diff --git a/front/src/modules/people/components/PeopleCompanyPicker.tsx b/front/src/modules/people/components/PeopleCompanyPicker.tsx
index 0c96fa8437..bdc3e54465 100644
--- a/front/src/modules/people/components/PeopleCompanyPicker.tsx
+++ b/front/src/modules/people/components/PeopleCompanyPicker.tsx
@@ -1,12 +1,8 @@
-import { Key } from 'ts-key-enum';
-
import { useFilteredSearchCompanyQuery } from '@/companies/queries';
-import { useScopedHotkeys } from '@/ui/hotkey/hooks/useScopedHotkeys';
import { useSetHotkeyScope } from '@/ui/hotkey/hooks/useSetHotkeyScope';
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 { useEditableCell } from '@/ui/table/editable-cell/hooks/useEditableCell';
import { isCreateModeScopedState } from '@/ui/table/editable-cell/states/isCreateModeScopedState';
import { TableHotkeyScope } from '@/ui/table/types/TableHotkeyScope';
@@ -63,13 +59,6 @@ export function PeopleCompanyPicker({ people }: OwnProps) {
addToScopeStack(TableHotkeyScope.CellDoubleTextInput);
}
- useScopedHotkeys(
- Key.Escape,
- () => closeEditableCell(),
- RelationPickerHotkeyScope.RelationPicker,
- [closeEditableCell],
- );
-
return (
void;
+ onCancel?: () => void;
+};
+
+type PersonForSelect = EntityForSelect & {
+ entityType: Entity.Person;
+};
+
+export function PeoplePicker({ personId, onSubmit, onCancel }: OwnProps) {
+ const [searchFilter] = useRecoilScopedState(
+ relationPickerSearchFilterScopedState,
+ );
+
+ const people = useFilteredSearchEntityQuery({
+ queryHook: useSearchPeopleQuery,
+ selectedIds: [personId],
+ searchFilter: searchFilter,
+ mappingFunction: (person) => ({
+ entityType: Entity.Person,
+ id: person.id,
+ name: person.firstName + ' ' + person.lastName,
+ avatarType: 'rounded',
+ }),
+ orderByField: 'firstName',
+ searchOnFields: ['firstName', 'lastName'],
+ });
+
+ async function handleEntitySelected(
+ selectedPerson: PersonForSelect | null | undefined,
+ ) {
+ onSubmit(selectedPerson?.id ?? null);
+ }
+
+ return (
+
+ );
+}
diff --git a/front/src/modules/people/constants/peopleFieldMetadataArray.tsx b/front/src/modules/people/constants/peopleFieldMetadataArray.tsx
index 9549a6f9ab..3f21bf769f 100644
--- a/front/src/modules/people/constants/peopleFieldMetadataArray.tsx
+++ b/front/src/modules/people/constants/peopleFieldMetadataArray.tsx
@@ -1,5 +1,10 @@
-import { IconBriefcase, IconMap } from '@tabler/icons-react';
+import {
+ IconBriefcase,
+ IconBuildingSkyscraper,
+ IconMap,
+} from '@tabler/icons-react';
+import { Entity } from '@/ui/relation-picker/types/EntityTypeForSelect';
import { EntityFieldMetadata } from '@/ui/table/types/EntityFieldMetadata';
export const peopleFieldMetadataArray: EntityFieldMetadata[] = [
@@ -17,4 +22,12 @@ export const peopleFieldMetadataArray: EntityFieldMetadata[] = [
columnSize: 150,
type: 'text',
},
+ {
+ fieldName: 'company',
+ label: 'Company',
+ icon: ,
+ columnSize: 150,
+ type: 'relation',
+ relationType: Entity.Company,
+ },
];
diff --git a/front/src/modules/people/hooks/useUpdateEntityField.ts b/front/src/modules/people/hooks/useUpdateEntityField.ts
new file mode 100644
index 0000000000..1affa08dc3
--- /dev/null
+++ b/front/src/modules/people/hooks/useUpdateEntityField.ts
@@ -0,0 +1,65 @@
+import { useContext } from 'react';
+import { useRecoilValue } from 'recoil';
+
+import { EntityForSelect } from '@/ui/relation-picker/types/EntityForSelect';
+import { entityFieldMetadataArrayState } from '@/ui/table/states/entityFieldMetadataArrayState';
+import { EntityUpdateMutationHookContext } from '@/ui/table/states/EntityUpdateMutationHookContext';
+
+export function useUpdateEntityField() {
+ const useUpdateEntityMutation = useContext(EntityUpdateMutationHookContext);
+
+ const [updateEntity] = useUpdateEntityMutation();
+
+ const entityFieldMetadataArray = useRecoilValue(
+ entityFieldMetadataArrayState,
+ );
+
+ return function updatePeopleField(
+ currentEntityId: string,
+ fieldName: string,
+ newFieldValue: unknown,
+ ) {
+ const fieldMetadata = entityFieldMetadataArray.find(
+ (metadata) => metadata.fieldName === fieldName,
+ );
+
+ if (!fieldMetadata) {
+ throw new Error(`Field metadata not found for field ${fieldName}`);
+ }
+
+ if (fieldMetadata.type === 'relation') {
+ const newSelectedEntity = newFieldValue as EntityForSelect | null;
+
+ if (!newSelectedEntity) {
+ updateEntity({
+ variables: {
+ where: { id: currentEntityId },
+ data: {
+ [fieldName]: {
+ disconnect: true,
+ },
+ },
+ },
+ });
+ } else {
+ updateEntity({
+ variables: {
+ where: { id: currentEntityId },
+ data: {
+ [fieldName]: {
+ connect: { id: newSelectedEntity.id },
+ },
+ },
+ },
+ });
+ }
+ } else {
+ updateEntity({
+ variables: {
+ where: { id: currentEntityId },
+ data: { [fieldName]: newFieldValue },
+ },
+ });
+ }
+ };
+}
diff --git a/front/src/modules/people/hooks/useUpdatePeopleField.ts b/front/src/modules/people/hooks/useUpdatePeopleField.ts
deleted file mode 100644
index eaaa3b372b..0000000000
--- a/front/src/modules/people/hooks/useUpdatePeopleField.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { useUpdateOnePersonMutation } from '~/generated/graphql';
-
-export function useUpdatePeopleField() {
- const [updatePeople] = useUpdateOnePersonMutation();
-
- return function updatePeopleField(
- peopleId: string,
- fieldName: string,
- fieldValue: unknown,
- ) {
- updatePeople({
- variables: {
- where: { id: peopleId },
- data: { [fieldName]: fieldValue },
- },
- });
- };
-}
diff --git a/front/src/modules/people/table/components/PeopleTableV2.tsx b/front/src/modules/people/table/components/PeopleTableV2.tsx
index fad160abde..50a972e594 100644
--- a/front/src/modules/people/table/components/PeopleTableV2.tsx
+++ b/front/src/modules/people/table/components/PeopleTableV2.tsx
@@ -3,7 +3,6 @@ import { useCallback, useMemo, useState } from 'react';
import { defaultOrderBy } from '@/companies/queries';
import { GenericEntityTableData } from '@/people/components/GenericEntityTableData';
import { peopleFieldMetadataArray } from '@/people/constants/peopleFieldMetadataArray';
-import { useUpdatePeopleField } from '@/people/hooks/useUpdatePeopleField';
import { PeopleSelectedSortType } from '@/people/queries';
import { reduceSortsToOrderBy } from '@/ui/filter-n-sort/helpers';
import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState';
@@ -15,6 +14,7 @@ import { TableContext } from '@/ui/table/states/TableContext';
import {
PersonOrderByWithRelationInput,
useGetPeopleQuery,
+ useUpdateOnePersonMutation,
} from '~/generated/graphql';
import { availableSorts } from '~/pages/people/people-sorts';
@@ -46,7 +46,7 @@ export function PeopleTable() {
viewIcon={}
availableSorts={availableSorts}
onSortsUpdate={updateSorts}
- useUpdateField={useUpdatePeopleField}
+ useUpdateEntityMutation={useUpdateOnePersonMutation}
/>
>
);
diff --git a/front/src/modules/ui/hotkey/hooks/useScopedHotkeyCallback.ts b/front/src/modules/ui/hotkey/hooks/useScopedHotkeyCallback.ts
index 656903121d..d73f70d5b3 100644
--- a/front/src/modules/ui/hotkey/hooks/useScopedHotkeyCallback.ts
+++ b/front/src/modules/ui/hotkey/hooks/useScopedHotkeyCallback.ts
@@ -3,6 +3,8 @@ import { useRecoilCallback } from 'recoil';
import { internalHotkeysEnabledScopesState } from '../states/internal/internalHotkeysEnabledScopesState';
+const DEBUG_HOTKEY_SCOPE = false;
+
export function useScopedHotkeyCallback() {
return useRecoilCallback(
({ snapshot }) =>
@@ -24,9 +26,31 @@ export function useScopedHotkeyCallback() {
.valueOrThrow();
if (!currentHotkeyScopes.includes(scope)) {
+ if (DEBUG_HOTKEY_SCOPE) {
+ console.debug(
+ `%cI can't call hotkey (${
+ hotkeysEvent.keys
+ }) because I'm in scope [${scope}] and the active scopes are : [${currentHotkeyScopes.join(
+ ', ',
+ )}]`,
+ 'color: gray; ',
+ );
+ }
+
return;
}
+ if (DEBUG_HOTKEY_SCOPE) {
+ console.debug(
+ `%cI can call hotkey (${
+ hotkeysEvent.keys
+ }) because I'm in scope [${scope}] and the active scopes are : [${currentHotkeyScopes.join(
+ ', ',
+ )}]`,
+ 'color: green;',
+ );
+ }
+
if (preventDefault) {
keyboardEvent.stopPropagation();
keyboardEvent.preventDefault();
diff --git a/front/src/modules/ui/table/components/EntityTableCellV2.tsx b/front/src/modules/ui/table/components/EntityTableCellV2.tsx
index 52e95a5593..3f7979749e 100644
--- a/front/src/modules/ui/table/components/EntityTableCellV2.tsx
+++ b/front/src/modules/ui/table/components/EntityTableCellV2.tsx
@@ -1,7 +1,7 @@
import { useContext } from 'react';
import { useSetRecoilState } from 'recoil';
-import { GenericEditableCell } from '@/people/table/components/GenericEditableCell';
+import { GenericEditableCell } from '@/ui/table/components/GenericEditableCell';
import { RecoilScope } from '../../recoil-scope/components/RecoilScope';
import { useCurrentRowSelected } from '../hooks/useCurrentRowSelected';
diff --git a/front/src/modules/ui/table/components/EntityTableV2.tsx b/front/src/modules/ui/table/components/EntityTableV2.tsx
index f34c70f741..21c3413502 100644
--- a/front/src/modules/ui/table/components/EntityTableV2.tsx
+++ b/front/src/modules/ui/table/components/EntityTableV2.tsx
@@ -6,9 +6,8 @@ import { useListenClickOutside } from '@/ui/hooks/useListenClickOutside';
import { useLeaveTableFocus } from '../hooks/useLeaveTableFocus';
import { useMapKeyboardToSoftFocus } from '../hooks/useMapKeyboardToSoftFocus';
-import { EntityUpdateFieldHookContext } from '../states/EntityUpdateFieldHookContext';
+import { EntityUpdateMutationHookContext } from '../states/EntityUpdateMutationHookContext';
import { TableHeader } from '../table-header/components/TableHeader';
-import { EntityUpdateFieldHook } from '../types/CellUpdateFieldHook';
import { EntityTableBody } from './EntityTableBodyV2';
import { EntityTableHeader } from './EntityTableHeaderV2';
@@ -90,7 +89,7 @@ type OwnProps = {
availableSorts?: Array>;
onSortsUpdate?: (sorts: Array>) => void;
onRowSelectionChange?: (rowSelection: string[]) => void;
- useUpdateField: EntityUpdateFieldHook;
+ useUpdateEntityMutation: any;
};
export function EntityTable({
@@ -98,7 +97,7 @@ export function EntityTable({
viewIcon,
availableSorts,
onSortsUpdate,
- useUpdateField,
+ useUpdateEntityMutation,
}: OwnProps) {
const tableBodyRef = React.useRef(null);
@@ -114,7 +113,7 @@ export function EntityTable({
});
return (
-
+
({
-
+
);
}
diff --git a/front/src/modules/people/table/components/GenericEditableCell.tsx b/front/src/modules/ui/table/components/GenericEditableCell.tsx
similarity index 65%
rename from front/src/modules/people/table/components/GenericEditableCell.tsx
rename to front/src/modules/ui/table/components/GenericEditableCell.tsx
index 2b8a6fff0d..839244d36a 100644
--- a/front/src/modules/people/table/components/GenericEditableCell.tsx
+++ b/front/src/modules/ui/table/components/GenericEditableCell.tsx
@@ -1,5 +1,6 @@
import { EntityFieldMetadata } from '@/ui/table/types/EntityFieldMetadata';
+import { GenericEditableRelationCell } from './GenericEditableRelationCell';
import { GenericEditableTextCell } from './GenericEditableTextCell';
type OwnProps = {
@@ -16,8 +17,15 @@ export function GenericEditableCell({ entityFieldMetadata }: OwnProps) {
editModeHorizontalAlign="left"
/>
);
-
+ case 'relation': {
+ return (
+
+ );
+ }
default:
+ console.warn(
+ `Unknown field type: ${entityFieldMetadata.type} in GenericEditableCell`,
+ );
return <>>;
}
}
diff --git a/front/src/modules/ui/table/components/GenericEditableRelationCell.tsx b/front/src/modules/ui/table/components/GenericEditableRelationCell.tsx
new file mode 100644
index 0000000000..9e5d0876ec
--- /dev/null
+++ b/front/src/modules/ui/table/components/GenericEditableRelationCell.tsx
@@ -0,0 +1,35 @@
+import { RelationPickerHotkeyScope } from '@/ui/relation-picker/types/RelationPickerHotkeyScope';
+import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
+import { EntityFieldMetadata } from '@/ui/table/types/EntityFieldMetadata';
+
+import { GenericEditableRelationCellDisplayMode } from './GenericEditableRelationCellDisplayMode';
+import { GenericEditableRelationCellEditMode } from './GenericEditableRelationCellEditMode';
+
+type OwnProps = {
+ fieldMetadata: EntityFieldMetadata;
+ editModeHorizontalAlign?: 'left' | 'right';
+ placeholder?: string;
+};
+
+export function GenericEditableRelationCell({
+ fieldMetadata,
+ editModeHorizontalAlign,
+ placeholder,
+}: OwnProps) {
+ return (
+
+ }
+ nonEditModeContent={
+
+ }
+ >
+ );
+}
diff --git a/front/src/modules/ui/table/components/GenericEditableRelationCellDisplayMode.tsx b/front/src/modules/ui/table/components/GenericEditableRelationCellDisplayMode.tsx
new file mode 100644
index 0000000000..8488516cdb
--- /dev/null
+++ b/front/src/modules/ui/table/components/GenericEditableRelationCellDisplayMode.tsx
@@ -0,0 +1,44 @@
+import { useRecoilValue } from 'recoil';
+
+import { CompanyChip } from '@/companies/components/CompanyChip';
+import { Entity } from '@/ui/relation-picker/types/EntityTypeForSelect';
+import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
+import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
+import { EntityFieldMetadata } from '@/ui/table/types/EntityFieldMetadata';
+import { getLogoUrlFromDomainName } from '~/utils';
+
+type OwnProps = {
+ fieldMetadata: EntityFieldMetadata;
+ editModeHorizontalAlign?: 'left' | 'right';
+ placeholder?: string;
+};
+
+export function GenericEditableRelationCellDisplayMode({
+ fieldMetadata,
+}: OwnProps) {
+ const currentRowEntityId = useCurrentRowEntityId();
+
+ const fieldValue = useRecoilValue(
+ tableEntityFieldFamilySelector({
+ entityId: currentRowEntityId ?? '',
+ fieldName: fieldMetadata.fieldName,
+ }),
+ );
+
+ switch (fieldMetadata.relationType) {
+ case Entity.Company: {
+ return (
+
+ );
+ }
+ default:
+ console.warn(
+ `Unknown relation type: "${fieldMetadata.relationType}" in GenericEditableRelationCellEditMode`,
+ );
+ return <> >;
+ }
+}
diff --git a/front/src/modules/ui/table/components/GenericEditableRelationCellEditMode.tsx b/front/src/modules/ui/table/components/GenericEditableRelationCellEditMode.tsx
new file mode 100644
index 0000000000..5fb2311e0e
--- /dev/null
+++ b/front/src/modules/ui/table/components/GenericEditableRelationCellEditMode.tsx
@@ -0,0 +1,68 @@
+import { useRecoilState } from 'recoil';
+
+import { CompanyPickerCell } from '@/companies/components/CompanyPickerCell';
+import { useUpdateEntityField } from '@/people/hooks/useUpdateEntityField';
+import { EntityForSelect } from '@/ui/relation-picker/types/EntityForSelect';
+import { Entity } from '@/ui/relation-picker/types/EntityTypeForSelect';
+import { useEditableCell } from '@/ui/table/editable-cell/hooks/useEditableCell';
+import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
+import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
+import { EntityFieldMetadata } from '@/ui/table/types/EntityFieldMetadata';
+
+type OwnProps = {
+ fieldMetadata: EntityFieldMetadata;
+};
+
+export function GenericEditableRelationCellEditMode({
+ fieldMetadata,
+}: OwnProps) {
+ const currentRowEntityId = useCurrentRowEntityId();
+
+ const { closeEditableCell } = useEditableCell();
+
+ const [fieldValueEntity] = useRecoilState(
+ tableEntityFieldFamilySelector({
+ entityId: currentRowEntityId ?? '',
+ fieldName: fieldMetadata.fieldName,
+ }),
+ );
+
+ const updateEntityField = useUpdateEntityField();
+
+ function handleEntitySubmit(newFieldEntity: EntityForSelect | null) {
+ if (
+ newFieldEntity?.id !== fieldValueEntity?.id &&
+ currentRowEntityId &&
+ updateEntityField
+ ) {
+ updateEntityField(
+ currentRowEntityId,
+ fieldMetadata.fieldName,
+ newFieldEntity,
+ );
+ }
+
+ closeEditableCell();
+ }
+
+ function handleCancel() {
+ closeEditableCell();
+ }
+
+ switch (fieldMetadata.relationType) {
+ case Entity.Company: {
+ return (
+
+ );
+ }
+ default:
+ console.warn(
+ `Unknown relation type: "${fieldMetadata.relationType}" in GenericEditableRelationCellEditMode`,
+ );
+ return <>>;
+ }
+}
diff --git a/front/src/modules/people/table/components/GenericEditableTextCell.tsx b/front/src/modules/ui/table/components/GenericEditableTextCell.tsx
similarity index 100%
rename from front/src/modules/people/table/components/GenericEditableTextCell.tsx
rename to front/src/modules/ui/table/components/GenericEditableTextCell.tsx
diff --git a/front/src/modules/people/table/components/GenericEditableTextCellEditMode.tsx b/front/src/modules/ui/table/components/GenericEditableTextCellEditMode.tsx
similarity index 86%
rename from front/src/modules/people/table/components/GenericEditableTextCellEditMode.tsx
rename to front/src/modules/ui/table/components/GenericEditableTextCellEditMode.tsx
index 6f0010da9b..18058e9af1 100644
--- a/front/src/modules/people/table/components/GenericEditableTextCellEditMode.tsx
+++ b/front/src/modules/ui/table/components/GenericEditableTextCellEditMode.tsx
@@ -1,7 +1,7 @@
import { useRecoilState } from 'recoil';
+import { useUpdateEntityField } from '@/people/hooks/useUpdateEntityField';
import { InplaceInputTextEditMode } from '@/ui/inplace-input/components/InplaceInputTextEditMode';
-import { useEntityUpdateFieldHook } from '@/ui/table/hooks/useCellUpdateFieldHook';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
@@ -23,8 +23,7 @@ export function GenericEditableTextCellEditMode({
}),
);
- const useUpdateField = useEntityUpdateFieldHook();
- const updateField = useUpdateField?.();
+ const updateField = useUpdateEntityField();
function handleSubmit(newText: string) {
if (newText === fieldValue) return;
diff --git a/front/src/modules/ui/table/hooks/useCellUpdateFieldHook.ts b/front/src/modules/ui/table/hooks/useCellUpdateFieldHook.ts
deleted file mode 100644
index 9b0ba8d54c..0000000000
--- a/front/src/modules/ui/table/hooks/useCellUpdateFieldHook.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { useContext } from 'react';
-
-import { EntityUpdateFieldHookContext } from '../states/EntityUpdateFieldHookContext';
-
-export function useEntityUpdateFieldHook() {
- return useContext(EntityUpdateFieldHookContext);
-}
diff --git a/front/src/modules/ui/table/states/EntityUpdateFieldHookContext.ts b/front/src/modules/ui/table/states/EntityUpdateFieldHookContext.ts
deleted file mode 100644
index 2ed5eaf308..0000000000
--- a/front/src/modules/ui/table/states/EntityUpdateFieldHookContext.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import { createContext } from 'react';
-
-import { EntityUpdateFieldHook } from '../types/CellUpdateFieldHook';
-
-export const EntityUpdateFieldHookContext =
- createContext(null);
diff --git a/front/src/modules/ui/table/states/EntityUpdateMutationHookContext.ts b/front/src/modules/ui/table/states/EntityUpdateMutationHookContext.ts
new file mode 100644
index 0000000000..621130e58d
--- /dev/null
+++ b/front/src/modules/ui/table/states/EntityUpdateMutationHookContext.ts
@@ -0,0 +1,3 @@
+import { createContext } from 'react';
+
+export const EntityUpdateMutationHookContext = createContext(null);
diff --git a/front/src/modules/ui/table/types/EntityFieldMetadata.ts b/front/src/modules/ui/table/types/EntityFieldMetadata.ts
index fa78483deb..4231306ffb 100644
--- a/front/src/modules/ui/table/types/EntityFieldMetadata.ts
+++ b/front/src/modules/ui/table/types/EntityFieldMetadata.ts
@@ -1,10 +1,6 @@
-export type EntityFieldType =
- | 'text'
- | 'number'
- | 'date'
- | 'select'
- | 'checkbox'
- | 'icon';
+import { Entity } from '@/ui/relation-picker/types/EntityTypeForSelect';
+
+export type EntityFieldType = 'text' | 'relation';
export type EntityFieldMetadata = {
fieldName: string;
@@ -13,4 +9,5 @@ export type EntityFieldMetadata = {
icon: JSX.Element;
columnSize: number;
filterIcon?: JSX.Element;
+ relationType?: Entity; // TODO: condition this type with type === "relation"
};
diff --git a/front/src/modules/users/components/UserPicker.tsx b/front/src/modules/users/components/UserPicker.tsx
new file mode 100644
index 0000000000..5ad2b5a568
--- /dev/null
+++ b/front/src/modules/users/components/UserPicker.tsx
@@ -0,0 +1,56 @@
+import { useFilteredSearchEntityQuery } from '@/search/hooks/useFilteredSearchEntityQuery';
+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 { EntityForSelect } from '@/ui/relation-picker/types/EntityForSelect';
+import { Entity } from '@/ui/relation-picker/types/EntityTypeForSelect';
+import { useSearchUserQuery } from '~/generated/graphql';
+
+export type OwnProps = {
+ userId: string;
+ onSubmit: (newUserId: string) => void;
+ onCancel?: () => void;
+};
+
+type UserForSelect = EntityForSelect & {
+ entityType: Entity.User;
+};
+
+export function UserPicker({ userId, onSubmit, onCancel }: OwnProps) {
+ const [searchFilter] = useRecoilScopedState(
+ relationPickerSearchFilterScopedState,
+ );
+
+ const users = useFilteredSearchEntityQuery({
+ queryHook: useSearchUserQuery,
+ selectedIds: [userId],
+ searchFilter: searchFilter,
+ mappingFunction: (user) => ({
+ entityType: Entity.User,
+ id: user.id,
+ name: user.displayName,
+ avatarType: 'rounded',
+ avatarUrl: user.avatarUrl ?? '',
+ }),
+ orderByField: 'firstName',
+ searchOnFields: ['firstName', 'lastName'],
+ });
+
+ async function handleEntitySelected(
+ selectedUser: UserForSelect | null | undefined,
+ ) {
+ onSubmit(selectedUser?.id ?? '');
+ }
+
+ return (
+
+ );
+}