mirror of
https://github.com/twentyhq/twenty.git
synced 2024-12-18 17:12:53 +03:00
Added generic relation cell (#969)
* Added generic relation cell * Deactivated debug * Added default warning * Put back display component * Removed unused types
This commit is contained in:
parent
3b796ee68c
commit
f4b8a3decb
60
front/src/modules/companies/components/CompanyPickerCell.tsx
Normal file
60
front/src/modules/companies/components/CompanyPickerCell.tsx
Normal file
@ -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 (
|
||||||
|
<SingleEntitySelect
|
||||||
|
onCreate={createModeEnabled ? handleCreate : undefined}
|
||||||
|
onCancel={onCancel}
|
||||||
|
onEntitySelected={handleEntitySelected}
|
||||||
|
entities={{
|
||||||
|
entitiesToSelect: companies.entitiesToSelect,
|
||||||
|
selectedEntity: companies.selectedEntities[0],
|
||||||
|
loading: companies.loading,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
@ -1,12 +1,8 @@
|
|||||||
import { Key } from 'ts-key-enum';
|
|
||||||
|
|
||||||
import { useFilteredSearchCompanyQuery } from '@/companies/queries';
|
import { useFilteredSearchCompanyQuery } from '@/companies/queries';
|
||||||
import { useScopedHotkeys } from '@/ui/hotkey/hooks/useScopedHotkeys';
|
|
||||||
import { useSetHotkeyScope } from '@/ui/hotkey/hooks/useSetHotkeyScope';
|
import { useSetHotkeyScope } from '@/ui/hotkey/hooks/useSetHotkeyScope';
|
||||||
import { useRecoilScopedState } from '@/ui/recoil-scope/hooks/useRecoilScopedState';
|
import { useRecoilScopedState } from '@/ui/recoil-scope/hooks/useRecoilScopedState';
|
||||||
import { SingleEntitySelect } from '@/ui/relation-picker/components/SingleEntitySelect';
|
import { SingleEntitySelect } from '@/ui/relation-picker/components/SingleEntitySelect';
|
||||||
import { relationPickerSearchFilterScopedState } from '@/ui/relation-picker/states/relationPickerSearchFilterScopedState';
|
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 { useEditableCell } from '@/ui/table/editable-cell/hooks/useEditableCell';
|
||||||
import { isCreateModeScopedState } from '@/ui/table/editable-cell/states/isCreateModeScopedState';
|
import { isCreateModeScopedState } from '@/ui/table/editable-cell/states/isCreateModeScopedState';
|
||||||
import { TableHotkeyScope } from '@/ui/table/types/TableHotkeyScope';
|
import { TableHotkeyScope } from '@/ui/table/types/TableHotkeyScope';
|
||||||
@ -63,13 +59,6 @@ export function PeopleCompanyPicker({ people }: OwnProps) {
|
|||||||
addToScopeStack(TableHotkeyScope.CellDoubleTextInput);
|
addToScopeStack(TableHotkeyScope.CellDoubleTextInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
useScopedHotkeys(
|
|
||||||
Key.Escape,
|
|
||||||
() => closeEditableCell(),
|
|
||||||
RelationPickerHotkeyScope.RelationPicker,
|
|
||||||
[closeEditableCell],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SingleEntitySelect
|
<SingleEntitySelect
|
||||||
onCreate={handleCreate}
|
onCreate={handleCreate}
|
||||||
|
55
front/src/modules/people/components/PeoplePicker.tsx
Normal file
55
front/src/modules/people/components/PeoplePicker.tsx
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
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 { useSearchPeopleQuery } from '~/generated/graphql';
|
||||||
|
|
||||||
|
export type OwnProps = {
|
||||||
|
personId: string;
|
||||||
|
onSubmit: (newPersonId: string | null) => 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 (
|
||||||
|
<SingleEntitySelect
|
||||||
|
onEntitySelected={handleEntitySelected}
|
||||||
|
onCancel={onCancel}
|
||||||
|
entities={{
|
||||||
|
loading: people.loading,
|
||||||
|
entitiesToSelect: people.entitiesToSelect,
|
||||||
|
selectedEntity: people.selectedEntities[0],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
@ -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';
|
import { EntityFieldMetadata } from '@/ui/table/types/EntityFieldMetadata';
|
||||||
|
|
||||||
export const peopleFieldMetadataArray: EntityFieldMetadata[] = [
|
export const peopleFieldMetadataArray: EntityFieldMetadata[] = [
|
||||||
@ -17,4 +22,12 @@ export const peopleFieldMetadataArray: EntityFieldMetadata[] = [
|
|||||||
columnSize: 150,
|
columnSize: 150,
|
||||||
type: 'text',
|
type: 'text',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'company',
|
||||||
|
label: 'Company',
|
||||||
|
icon: <IconBuildingSkyscraper size={16} />,
|
||||||
|
columnSize: 150,
|
||||||
|
type: 'relation',
|
||||||
|
relationType: Entity.Company,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
65
front/src/modules/people/hooks/useUpdateEntityField.ts
Normal file
65
front/src/modules/people/hooks/useUpdateEntityField.ts
Normal file
@ -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 },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@ -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 },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
@ -3,7 +3,6 @@ import { useCallback, useMemo, useState } from 'react';
|
|||||||
import { defaultOrderBy } from '@/companies/queries';
|
import { defaultOrderBy } from '@/companies/queries';
|
||||||
import { GenericEntityTableData } from '@/people/components/GenericEntityTableData';
|
import { GenericEntityTableData } from '@/people/components/GenericEntityTableData';
|
||||||
import { peopleFieldMetadataArray } from '@/people/constants/peopleFieldMetadataArray';
|
import { peopleFieldMetadataArray } from '@/people/constants/peopleFieldMetadataArray';
|
||||||
import { useUpdatePeopleField } from '@/people/hooks/useUpdatePeopleField';
|
|
||||||
import { PeopleSelectedSortType } from '@/people/queries';
|
import { PeopleSelectedSortType } from '@/people/queries';
|
||||||
import { reduceSortsToOrderBy } from '@/ui/filter-n-sort/helpers';
|
import { reduceSortsToOrderBy } from '@/ui/filter-n-sort/helpers';
|
||||||
import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState';
|
import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState';
|
||||||
@ -15,6 +14,7 @@ import { TableContext } from '@/ui/table/states/TableContext';
|
|||||||
import {
|
import {
|
||||||
PersonOrderByWithRelationInput,
|
PersonOrderByWithRelationInput,
|
||||||
useGetPeopleQuery,
|
useGetPeopleQuery,
|
||||||
|
useUpdateOnePersonMutation,
|
||||||
} from '~/generated/graphql';
|
} from '~/generated/graphql';
|
||||||
import { availableSorts } from '~/pages/people/people-sorts';
|
import { availableSorts } from '~/pages/people/people-sorts';
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ export function PeopleTable() {
|
|||||||
viewIcon={<IconList size={16} />}
|
viewIcon={<IconList size={16} />}
|
||||||
availableSorts={availableSorts}
|
availableSorts={availableSorts}
|
||||||
onSortsUpdate={updateSorts}
|
onSortsUpdate={updateSorts}
|
||||||
useUpdateField={useUpdatePeopleField}
|
useUpdateEntityMutation={useUpdateOnePersonMutation}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -3,6 +3,8 @@ import { useRecoilCallback } from 'recoil';
|
|||||||
|
|
||||||
import { internalHotkeysEnabledScopesState } from '../states/internal/internalHotkeysEnabledScopesState';
|
import { internalHotkeysEnabledScopesState } from '../states/internal/internalHotkeysEnabledScopesState';
|
||||||
|
|
||||||
|
const DEBUG_HOTKEY_SCOPE = false;
|
||||||
|
|
||||||
export function useScopedHotkeyCallback() {
|
export function useScopedHotkeyCallback() {
|
||||||
return useRecoilCallback(
|
return useRecoilCallback(
|
||||||
({ snapshot }) =>
|
({ snapshot }) =>
|
||||||
@ -24,9 +26,31 @@ export function useScopedHotkeyCallback() {
|
|||||||
.valueOrThrow();
|
.valueOrThrow();
|
||||||
|
|
||||||
if (!currentHotkeyScopes.includes(scope)) {
|
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;
|
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) {
|
if (preventDefault) {
|
||||||
keyboardEvent.stopPropagation();
|
keyboardEvent.stopPropagation();
|
||||||
keyboardEvent.preventDefault();
|
keyboardEvent.preventDefault();
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { useSetRecoilState } from 'recoil';
|
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 { RecoilScope } from '../../recoil-scope/components/RecoilScope';
|
||||||
import { useCurrentRowSelected } from '../hooks/useCurrentRowSelected';
|
import { useCurrentRowSelected } from '../hooks/useCurrentRowSelected';
|
||||||
|
@ -6,9 +6,8 @@ import { useListenClickOutside } from '@/ui/hooks/useListenClickOutside';
|
|||||||
|
|
||||||
import { useLeaveTableFocus } from '../hooks/useLeaveTableFocus';
|
import { useLeaveTableFocus } from '../hooks/useLeaveTableFocus';
|
||||||
import { useMapKeyboardToSoftFocus } from '../hooks/useMapKeyboardToSoftFocus';
|
import { useMapKeyboardToSoftFocus } from '../hooks/useMapKeyboardToSoftFocus';
|
||||||
import { EntityUpdateFieldHookContext } from '../states/EntityUpdateFieldHookContext';
|
import { EntityUpdateMutationHookContext } from '../states/EntityUpdateMutationHookContext';
|
||||||
import { TableHeader } from '../table-header/components/TableHeader';
|
import { TableHeader } from '../table-header/components/TableHeader';
|
||||||
import { EntityUpdateFieldHook } from '../types/CellUpdateFieldHook';
|
|
||||||
|
|
||||||
import { EntityTableBody } from './EntityTableBodyV2';
|
import { EntityTableBody } from './EntityTableBodyV2';
|
||||||
import { EntityTableHeader } from './EntityTableHeaderV2';
|
import { EntityTableHeader } from './EntityTableHeaderV2';
|
||||||
@ -90,7 +89,7 @@ type OwnProps<SortField> = {
|
|||||||
availableSorts?: Array<SortType<SortField>>;
|
availableSorts?: Array<SortType<SortField>>;
|
||||||
onSortsUpdate?: (sorts: Array<SelectedSortType<SortField>>) => void;
|
onSortsUpdate?: (sorts: Array<SelectedSortType<SortField>>) => void;
|
||||||
onRowSelectionChange?: (rowSelection: string[]) => void;
|
onRowSelectionChange?: (rowSelection: string[]) => void;
|
||||||
useUpdateField: EntityUpdateFieldHook;
|
useUpdateEntityMutation: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function EntityTable<SortField>({
|
export function EntityTable<SortField>({
|
||||||
@ -98,7 +97,7 @@ export function EntityTable<SortField>({
|
|||||||
viewIcon,
|
viewIcon,
|
||||||
availableSorts,
|
availableSorts,
|
||||||
onSortsUpdate,
|
onSortsUpdate,
|
||||||
useUpdateField,
|
useUpdateEntityMutation,
|
||||||
}: OwnProps<SortField>) {
|
}: OwnProps<SortField>) {
|
||||||
const tableBodyRef = React.useRef<HTMLDivElement>(null);
|
const tableBodyRef = React.useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@ -114,7 +113,7 @@ export function EntityTable<SortField>({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EntityUpdateFieldHookContext.Provider value={useUpdateField}>
|
<EntityUpdateMutationHookContext.Provider value={useUpdateEntityMutation}>
|
||||||
<StyledTableWithHeader>
|
<StyledTableWithHeader>
|
||||||
<StyledTableContainer ref={tableBodyRef}>
|
<StyledTableContainer ref={tableBodyRef}>
|
||||||
<TableHeader
|
<TableHeader
|
||||||
@ -131,6 +130,6 @@ export function EntityTable<SortField>({
|
|||||||
</StyledTableWrapper>
|
</StyledTableWrapper>
|
||||||
</StyledTableContainer>
|
</StyledTableContainer>
|
||||||
</StyledTableWithHeader>
|
</StyledTableWithHeader>
|
||||||
</EntityUpdateFieldHookContext.Provider>
|
</EntityUpdateMutationHookContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { EntityFieldMetadata } from '@/ui/table/types/EntityFieldMetadata';
|
import { EntityFieldMetadata } from '@/ui/table/types/EntityFieldMetadata';
|
||||||
|
|
||||||
|
import { GenericEditableRelationCell } from './GenericEditableRelationCell';
|
||||||
import { GenericEditableTextCell } from './GenericEditableTextCell';
|
import { GenericEditableTextCell } from './GenericEditableTextCell';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
@ -16,8 +17,15 @@ export function GenericEditableCell({ entityFieldMetadata }: OwnProps) {
|
|||||||
editModeHorizontalAlign="left"
|
editModeHorizontalAlign="left"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
case 'relation': {
|
||||||
|
return (
|
||||||
|
<GenericEditableRelationCell fieldMetadata={entityFieldMetadata} />
|
||||||
|
);
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
|
console.warn(
|
||||||
|
`Unknown field type: ${entityFieldMetadata.type} in GenericEditableCell`,
|
||||||
|
);
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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 (
|
||||||
|
<EditableCell
|
||||||
|
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||||
|
editHotkeyScope={{ scope: RelationPickerHotkeyScope.RelationPicker }}
|
||||||
|
editModeContent={
|
||||||
|
<GenericEditableRelationCellEditMode fieldMetadata={fieldMetadata} />
|
||||||
|
}
|
||||||
|
nonEditModeContent={
|
||||||
|
<GenericEditableRelationCellDisplayMode
|
||||||
|
fieldMetadata={fieldMetadata}
|
||||||
|
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||||
|
placeholder={placeholder}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
></EditableCell>
|
||||||
|
);
|
||||||
|
}
|
@ -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<any | null>(
|
||||||
|
tableEntityFieldFamilySelector({
|
||||||
|
entityId: currentRowEntityId ?? '',
|
||||||
|
fieldName: fieldMetadata.fieldName,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
switch (fieldMetadata.relationType) {
|
||||||
|
case Entity.Company: {
|
||||||
|
return (
|
||||||
|
<CompanyChip
|
||||||
|
id={fieldValue?.id ?? ''}
|
||||||
|
name={fieldValue?.name ?? ''}
|
||||||
|
pictureUrl={getLogoUrlFromDomainName(fieldValue?.domainName)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
console.warn(
|
||||||
|
`Unknown relation type: "${fieldMetadata.relationType}" in GenericEditableRelationCellEditMode`,
|
||||||
|
);
|
||||||
|
return <> </>;
|
||||||
|
}
|
||||||
|
}
|
@ -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<any | null>(
|
||||||
|
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 (
|
||||||
|
<CompanyPickerCell
|
||||||
|
companyId={fieldValueEntity?.id ?? null}
|
||||||
|
onSubmit={handleEntitySubmit}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
console.warn(
|
||||||
|
`Unknown relation type: "${fieldMetadata.relationType}" in GenericEditableRelationCellEditMode`,
|
||||||
|
);
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
|
|
||||||
|
import { useUpdateEntityField } from '@/people/hooks/useUpdateEntityField';
|
||||||
import { InplaceInputTextEditMode } from '@/ui/inplace-input/components/InplaceInputTextEditMode';
|
import { InplaceInputTextEditMode } from '@/ui/inplace-input/components/InplaceInputTextEditMode';
|
||||||
import { useEntityUpdateFieldHook } from '@/ui/table/hooks/useCellUpdateFieldHook';
|
|
||||||
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
|
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
|
||||||
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
|
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
|
||||||
|
|
||||||
@ -23,8 +23,7 @@ export function GenericEditableTextCellEditMode({
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const useUpdateField = useEntityUpdateFieldHook();
|
const updateField = useUpdateEntityField();
|
||||||
const updateField = useUpdateField?.();
|
|
||||||
|
|
||||||
function handleSubmit(newText: string) {
|
function handleSubmit(newText: string) {
|
||||||
if (newText === fieldValue) return;
|
if (newText === fieldValue) return;
|
@ -1,7 +0,0 @@
|
|||||||
import { useContext } from 'react';
|
|
||||||
|
|
||||||
import { EntityUpdateFieldHookContext } from '../states/EntityUpdateFieldHookContext';
|
|
||||||
|
|
||||||
export function useEntityUpdateFieldHook() {
|
|
||||||
return useContext(EntityUpdateFieldHookContext);
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
import { createContext } from 'react';
|
|
||||||
|
|
||||||
import { EntityUpdateFieldHook } from '../types/CellUpdateFieldHook';
|
|
||||||
|
|
||||||
export const EntityUpdateFieldHookContext =
|
|
||||||
createContext<EntityUpdateFieldHook | null>(null);
|
|
@ -0,0 +1,3 @@
|
|||||||
|
import { createContext } from 'react';
|
||||||
|
|
||||||
|
export const EntityUpdateMutationHookContext = createContext<any | null>(null);
|
@ -1,10 +1,6 @@
|
|||||||
export type EntityFieldType =
|
import { Entity } from '@/ui/relation-picker/types/EntityTypeForSelect';
|
||||||
| 'text'
|
|
||||||
| 'number'
|
export type EntityFieldType = 'text' | 'relation';
|
||||||
| 'date'
|
|
||||||
| 'select'
|
|
||||||
| 'checkbox'
|
|
||||||
| 'icon';
|
|
||||||
|
|
||||||
export type EntityFieldMetadata = {
|
export type EntityFieldMetadata = {
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
@ -13,4 +9,5 @@ export type EntityFieldMetadata = {
|
|||||||
icon: JSX.Element;
|
icon: JSX.Element;
|
||||||
columnSize: number;
|
columnSize: number;
|
||||||
filterIcon?: JSX.Element;
|
filterIcon?: JSX.Element;
|
||||||
|
relationType?: Entity; // TODO: condition this type with type === "relation"
|
||||||
};
|
};
|
||||||
|
56
front/src/modules/users/components/UserPicker.tsx
Normal file
56
front/src/modules/users/components/UserPicker.tsx
Normal file
@ -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 (
|
||||||
|
<SingleEntitySelect
|
||||||
|
onEntitySelected={handleEntitySelected}
|
||||||
|
onCancel={onCancel}
|
||||||
|
entities={{
|
||||||
|
loading: users.loading,
|
||||||
|
entitiesToSelect: users.entitiesToSelect,
|
||||||
|
selectedEntity: users.selectedEntities[0],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user