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:
@ -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 (
|
||||
<SingleEntitySelect
|
||||
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';
|
||||
|
||||
export const peopleFieldMetadataArray: EntityFieldMetadata[] = [
|
||||
@ -17,4 +22,12 @@ export const peopleFieldMetadataArray: EntityFieldMetadata[] = [
|
||||
columnSize: 150,
|
||||
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 },
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
import { EntityFieldMetadata } from '@/ui/table/types/EntityFieldMetadata';
|
||||
|
||||
import { GenericEditableTextCell } from './GenericEditableTextCell';
|
||||
|
||||
type OwnProps = {
|
||||
entityFieldMetadata: EntityFieldMetadata;
|
||||
};
|
||||
|
||||
export function GenericEditableCell({ entityFieldMetadata }: OwnProps) {
|
||||
switch (entityFieldMetadata.type) {
|
||||
case 'text':
|
||||
return (
|
||||
<GenericEditableTextCell
|
||||
fieldName={entityFieldMetadata.fieldName}
|
||||
placeholder={entityFieldMetadata.label}
|
||||
editModeHorizontalAlign="left"
|
||||
/>
|
||||
);
|
||||
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
}
|
||||
@ -1,44 +0,0 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { InplaceInputTextDisplayMode } from '@/ui/display/component/InplaceInputTextDisplayMode';
|
||||
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
|
||||
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
|
||||
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
|
||||
|
||||
import { GenericEditableTextCellEditMode } from './GenericEditableTextCellEditMode';
|
||||
|
||||
type OwnProps = {
|
||||
fieldName: string;
|
||||
editModeHorizontalAlign?: 'left' | 'right';
|
||||
placeholder?: string;
|
||||
};
|
||||
|
||||
export function GenericEditableTextCell({
|
||||
fieldName,
|
||||
editModeHorizontalAlign,
|
||||
placeholder,
|
||||
}: OwnProps) {
|
||||
const currentRowEntityId = useCurrentRowEntityId();
|
||||
|
||||
const fieldValue = useRecoilValue<string>(
|
||||
tableEntityFieldFamilySelector({
|
||||
entityId: currentRowEntityId ?? '',
|
||||
fieldName,
|
||||
}),
|
||||
);
|
||||
|
||||
return (
|
||||
<EditableCell
|
||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||
editModeContent={
|
||||
<GenericEditableTextCellEditMode
|
||||
fieldName={fieldName}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
}
|
||||
nonEditModeContent={
|
||||
<InplaceInputTextDisplayMode>{fieldValue}</InplaceInputTextDisplayMode>
|
||||
}
|
||||
></EditableCell>
|
||||
);
|
||||
}
|
||||
@ -1,47 +0,0 @@
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
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';
|
||||
|
||||
type OwnProps = {
|
||||
fieldName: string;
|
||||
placeholder?: string;
|
||||
};
|
||||
|
||||
export function GenericEditableTextCellEditMode({
|
||||
fieldName,
|
||||
placeholder,
|
||||
}: OwnProps) {
|
||||
const currentRowEntityId = useCurrentRowEntityId();
|
||||
|
||||
const [fieldValue, setFieldValue] = useRecoilState<string>(
|
||||
tableEntityFieldFamilySelector({
|
||||
entityId: currentRowEntityId ?? '',
|
||||
fieldName,
|
||||
}),
|
||||
);
|
||||
|
||||
const useUpdateField = useEntityUpdateFieldHook();
|
||||
const updateField = useUpdateField?.();
|
||||
|
||||
function handleSubmit(newText: string) {
|
||||
if (newText === fieldValue) return;
|
||||
|
||||
setFieldValue(newText);
|
||||
|
||||
if (currentRowEntityId && updateField) {
|
||||
updateField(currentRowEntityId, fieldName, newText);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<InplaceInputTextEditMode
|
||||
placeholder={placeholder ?? ''}
|
||||
autoFocus
|
||||
value={fieldValue ?? ''}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -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={<IconList size={16} />}
|
||||
availableSorts={availableSorts}
|
||||
onSortsUpdate={updateSorts}
|
||||
useUpdateField={useUpdatePeopleField}
|
||||
useUpdateEntityMutation={useUpdateOnePersonMutation}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user