Feat/generic editable cell all types (#987)
* Added generic relation cell * Deactivated debug * Added default warning * Put back display component * Removed unused types * wip * Renamed to view field * Use new view field structure to have chip working * Finished * Added a temp feature flag * Added double text chip cell * Ok * Finished tables * Fixed icon size * Fixed bug on date field * Use icon index * Fix * Fixed naming * Fix * removed file from merge * Fixed tests * Coverage
This commit is contained in:
@ -1,34 +0,0 @@
|
||||
import { FilterDefinition } from '@/ui/filter-n-sort/types/FilterDefinition';
|
||||
import { ViewFieldDefinition } from '@/ui/table/types/ViewField';
|
||||
|
||||
import { useSetEntityTableData } from '../hooks/useSetEntityTableData';
|
||||
import { defaultOrderBy } from '../queries';
|
||||
|
||||
export function GenericEntityTableData({
|
||||
useGetRequest,
|
||||
getRequestResultKey,
|
||||
orderBy = defaultOrderBy,
|
||||
whereFilters,
|
||||
viewFields,
|
||||
filterDefinitionArray,
|
||||
}: {
|
||||
useGetRequest: any;
|
||||
getRequestResultKey: string;
|
||||
orderBy?: any;
|
||||
whereFilters?: any;
|
||||
viewFields: ViewFieldDefinition<unknown>[];
|
||||
filterDefinitionArray: FilterDefinition[];
|
||||
}) {
|
||||
const setEntityTableData = useSetEntityTableData();
|
||||
|
||||
useGetRequest({
|
||||
variables: { orderBy, where: whereFilters },
|
||||
onCompleted: (data: any) => {
|
||||
const entities = data[getRequestResultKey] ?? [];
|
||||
|
||||
setEntityTableData(entities, viewFields, filterDefinitionArray);
|
||||
},
|
||||
});
|
||||
|
||||
return <></>;
|
||||
}
|
||||
@ -1,52 +0,0 @@
|
||||
import {
|
||||
IconBriefcase,
|
||||
IconBuildingSkyscraper,
|
||||
IconMap,
|
||||
} from '@tabler/icons-react';
|
||||
|
||||
import { Entity } from '@/ui/relation-picker/types/EntityTypeForSelect';
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldRelationMetadata,
|
||||
ViewFieldTextMetadata,
|
||||
} from '@/ui/table/types/ViewField';
|
||||
|
||||
export const peopleViewFields: ViewFieldDefinition<unknown>[] = [
|
||||
{
|
||||
id: 'city',
|
||||
columnLabel: 'City',
|
||||
columnIcon: <IconMap size={16} />,
|
||||
columnSize: 150,
|
||||
type: 'text',
|
||||
columnOrder: 1,
|
||||
metadata: {
|
||||
fieldName: 'city',
|
||||
placeHolder: 'City',
|
||||
},
|
||||
} as ViewFieldDefinition<ViewFieldTextMetadata>,
|
||||
{
|
||||
id: 'jobTitle',
|
||||
columnLabel: 'Job title',
|
||||
columnIcon: <IconBriefcase size={16} />,
|
||||
columnSize: 150,
|
||||
type: 'text',
|
||||
columnOrder: 2,
|
||||
metadata: {
|
||||
fieldName: 'jobTitle',
|
||||
placeHolder: 'Job title',
|
||||
},
|
||||
} as ViewFieldDefinition<ViewFieldTextMetadata>,
|
||||
{
|
||||
id: 'company',
|
||||
columnLabel: 'Company',
|
||||
columnIcon: <IconBuildingSkyscraper size={16} />,
|
||||
columnSize: 150,
|
||||
type: 'relation',
|
||||
relationType: Entity.Company,
|
||||
columnOrder: 3,
|
||||
metadata: {
|
||||
fieldName: 'company',
|
||||
relationType: Entity.Company,
|
||||
},
|
||||
} as ViewFieldDefinition<ViewFieldRelationMetadata>,
|
||||
];
|
||||
122
front/src/modules/people/constants/peopleViewFields.tsx
Normal file
122
front/src/modules/people/constants/peopleViewFields.tsx
Normal file
@ -0,0 +1,122 @@
|
||||
import {
|
||||
IconBrandLinkedin,
|
||||
IconBriefcase,
|
||||
IconBuildingSkyscraper,
|
||||
IconCalendarEvent,
|
||||
IconMail,
|
||||
IconMap,
|
||||
IconPhone,
|
||||
IconUser,
|
||||
} from '@/ui/icon/index';
|
||||
import { Entity } from '@/ui/relation-picker/types/EntityTypeForSelect';
|
||||
import {
|
||||
ViewFieldDateMetadata,
|
||||
ViewFieldDefinition,
|
||||
ViewFieldDoubleTextChipMetadata,
|
||||
ViewFieldMetadata,
|
||||
ViewFieldPhoneMetadata,
|
||||
ViewFieldRelationMetadata,
|
||||
ViewFieldTextMetadata,
|
||||
ViewFieldURLMetadata,
|
||||
} from '@/ui/table/types/ViewField';
|
||||
|
||||
export const peopleViewFields: ViewFieldDefinition<ViewFieldMetadata>[] = [
|
||||
{
|
||||
id: 'displayName',
|
||||
columnLabel: 'People',
|
||||
columnIcon: <IconUser />,
|
||||
columnSize: 210,
|
||||
columnOrder: 1,
|
||||
metadata: {
|
||||
type: 'double-text-chip',
|
||||
firstValueFieldName: 'firstName',
|
||||
secondValueFieldName: 'lastName',
|
||||
firstValuePlaceholder: 'First name',
|
||||
secondValuePlaceholder: 'Last name',
|
||||
entityType: Entity.Person,
|
||||
},
|
||||
} satisfies ViewFieldDefinition<ViewFieldDoubleTextChipMetadata>,
|
||||
{
|
||||
id: 'email',
|
||||
columnLabel: 'Email',
|
||||
columnIcon: <IconMail />,
|
||||
columnSize: 150,
|
||||
columnOrder: 2,
|
||||
metadata: {
|
||||
type: 'text',
|
||||
fieldName: 'email',
|
||||
placeHolder: 'Email',
|
||||
},
|
||||
} satisfies ViewFieldDefinition<ViewFieldTextMetadata>,
|
||||
{
|
||||
id: 'company',
|
||||
columnLabel: 'Company',
|
||||
columnIcon: <IconBuildingSkyscraper />,
|
||||
columnSize: 150,
|
||||
columnOrder: 3,
|
||||
metadata: {
|
||||
type: 'relation',
|
||||
fieldName: 'company',
|
||||
relationType: Entity.Company,
|
||||
},
|
||||
} satisfies ViewFieldDefinition<ViewFieldRelationMetadata>,
|
||||
{
|
||||
id: 'phone',
|
||||
columnLabel: 'Phone',
|
||||
columnIcon: <IconPhone />,
|
||||
columnSize: 150,
|
||||
columnOrder: 4,
|
||||
metadata: {
|
||||
type: 'phone',
|
||||
fieldName: 'phone',
|
||||
placeHolder: 'Phone',
|
||||
},
|
||||
} satisfies ViewFieldDefinition<ViewFieldPhoneMetadata>,
|
||||
{
|
||||
id: 'createdAt',
|
||||
columnLabel: 'Creation',
|
||||
columnIcon: <IconCalendarEvent />,
|
||||
columnSize: 150,
|
||||
columnOrder: 5,
|
||||
metadata: {
|
||||
type: 'date',
|
||||
fieldName: 'createdAt',
|
||||
},
|
||||
} satisfies ViewFieldDefinition<ViewFieldDateMetadata>,
|
||||
{
|
||||
id: 'city',
|
||||
columnLabel: 'City',
|
||||
columnIcon: <IconMap />,
|
||||
columnSize: 150,
|
||||
columnOrder: 6,
|
||||
metadata: {
|
||||
type: 'text',
|
||||
fieldName: 'city',
|
||||
placeHolder: 'City',
|
||||
},
|
||||
} satisfies ViewFieldDefinition<ViewFieldTextMetadata>,
|
||||
{
|
||||
id: 'jobTitle',
|
||||
columnLabel: 'Job title',
|
||||
columnIcon: <IconBriefcase />,
|
||||
columnSize: 150,
|
||||
columnOrder: 7,
|
||||
metadata: {
|
||||
type: 'text',
|
||||
fieldName: 'jobTitle',
|
||||
placeHolder: 'Job title',
|
||||
},
|
||||
} satisfies ViewFieldDefinition<ViewFieldTextMetadata>,
|
||||
{
|
||||
id: 'linkedin',
|
||||
columnLabel: 'LinkedIn',
|
||||
columnIcon: <IconBrandLinkedin />,
|
||||
columnSize: 150,
|
||||
columnOrder: 8,
|
||||
metadata: {
|
||||
type: 'url',
|
||||
fieldName: 'linkedinUrl',
|
||||
placeHolder: 'LinkedIn',
|
||||
},
|
||||
} satisfies ViewFieldDefinition<ViewFieldURLMetadata>,
|
||||
];
|
||||
@ -1,63 +0,0 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { FilterDefinition } from '@/ui/filter-n-sort/types/FilterDefinition';
|
||||
import { tableEntitiesFamilyState } from '@/ui/table/states/tableEntitiesFamilyState';
|
||||
import { viewFieldsState } from '@/ui/table/states/viewFieldsState';
|
||||
import { ViewFieldDefinition } from '@/ui/table/types/ViewField';
|
||||
|
||||
import { availableFiltersScopedState } from '../../ui/filter-n-sort/states/availableFiltersScopedState';
|
||||
import { useContextScopeId } from '../../ui/recoil-scope/hooks/useContextScopeId';
|
||||
import { useResetTableRowSelection } from '../../ui/table/hooks/useResetTableRowSelection';
|
||||
import { entityTableDimensionsState } from '../../ui/table/states/entityTableDimensionsState';
|
||||
import { isFetchingEntityTableDataState } from '../../ui/table/states/isFetchingEntityTableDataState';
|
||||
import { TableContext } from '../../ui/table/states/TableContext';
|
||||
import { tableRowIdsState } from '../../ui/table/states/tableRowIdsState';
|
||||
|
||||
export function useSetEntityTableData() {
|
||||
const resetTableRowSelection = useResetTableRowSelection();
|
||||
|
||||
const tableContextScopeId = useContextScopeId(TableContext);
|
||||
|
||||
return useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
<T extends { id: string }>(
|
||||
newEntityArray: T[],
|
||||
viewFields: ViewFieldDefinition<unknown>[],
|
||||
filters: FilterDefinition[],
|
||||
) => {
|
||||
for (const entity of newEntityArray) {
|
||||
const currentEntity = snapshot
|
||||
.getLoadable(tableEntitiesFamilyState(entity.id))
|
||||
.valueOrThrow();
|
||||
|
||||
if (JSON.stringify(currentEntity) !== JSON.stringify(entity)) {
|
||||
set(tableEntitiesFamilyState(entity.id), entity);
|
||||
}
|
||||
}
|
||||
|
||||
const entityIds = newEntityArray.map((entity) => entity.id);
|
||||
|
||||
set(tableRowIdsState, (currentRowIds) => {
|
||||
if (JSON.stringify(currentRowIds) !== JSON.stringify(entityIds)) {
|
||||
return entityIds;
|
||||
}
|
||||
|
||||
return currentRowIds;
|
||||
});
|
||||
|
||||
resetTableRowSelection();
|
||||
|
||||
set(entityTableDimensionsState, {
|
||||
numberOfColumns: viewFields.length,
|
||||
numberOfRows: entityIds.length,
|
||||
});
|
||||
|
||||
set(availableFiltersScopedState(tableContextScopeId), filters);
|
||||
|
||||
set(viewFieldsState, viewFields);
|
||||
|
||||
set(isFetchingEntityTableDataState, false);
|
||||
},
|
||||
[],
|
||||
);
|
||||
}
|
||||
@ -1,80 +0,0 @@
|
||||
import { useContext } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { EntityForSelect } from '@/ui/relation-picker/types/EntityForSelect';
|
||||
import { EntityUpdateMutationHookContext } from '@/ui/table/states/EntityUpdateMutationHookContext';
|
||||
import { viewFieldsState } from '@/ui/table/states/viewFieldsState';
|
||||
import { isViewFieldChip } from '@/ui/table/types/guards/isViewFieldChip';
|
||||
import { isViewFieldRelation } from '@/ui/table/types/guards/isViewFieldRelation';
|
||||
import { isViewFieldText } from '@/ui/table/types/guards/isViewFieldText';
|
||||
|
||||
export function useUpdateEntityField() {
|
||||
const useUpdateEntityMutation = useContext(EntityUpdateMutationHookContext);
|
||||
|
||||
const [updateEntity] = useUpdateEntityMutation();
|
||||
|
||||
const viewFields = useRecoilValue(viewFieldsState);
|
||||
|
||||
return function updatePeopleField(
|
||||
currentEntityId: string,
|
||||
viewFieldId: string,
|
||||
newFieldValue: unknown,
|
||||
) {
|
||||
const viewField = viewFields.find(
|
||||
(metadata) => metadata.id === viewFieldId,
|
||||
);
|
||||
|
||||
if (!viewField) {
|
||||
throw new Error(`View field not found for id ${viewFieldId}`);
|
||||
}
|
||||
|
||||
// TODO: improve type narrowing here with validation maybe ? Also validate the newFieldValue with linked type guards
|
||||
if (isViewFieldRelation(viewField)) {
|
||||
const newSelectedEntity = newFieldValue as EntityForSelect | null;
|
||||
|
||||
const fieldName = viewField.metadata.fieldName;
|
||||
|
||||
if (!newSelectedEntity) {
|
||||
updateEntity({
|
||||
variables: {
|
||||
where: { id: currentEntityId },
|
||||
data: {
|
||||
[fieldName]: {
|
||||
disconnect: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
} else {
|
||||
updateEntity({
|
||||
variables: {
|
||||
where: { id: currentEntityId },
|
||||
data: {
|
||||
[fieldName]: {
|
||||
connect: { id: newSelectedEntity.id },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
} else if (isViewFieldChip(viewField)) {
|
||||
const newContent = newFieldValue as string;
|
||||
|
||||
updateEntity({
|
||||
variables: {
|
||||
where: { id: currentEntityId },
|
||||
data: { [viewField.metadata.contentFieldName]: newContent },
|
||||
},
|
||||
});
|
||||
} else if (isViewFieldText(viewField)) {
|
||||
const newContent = newFieldValue as string;
|
||||
|
||||
updateEntity({
|
||||
variables: {
|
||||
where: { id: currentEntityId },
|
||||
data: { [viewField.metadata.fieldName]: newContent },
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -1,8 +1,7 @@
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { defaultOrderBy } from '@/companies/queries';
|
||||
import { GenericEntityTableData } from '@/people/components/GenericEntityTableData';
|
||||
import { peopleViewFields } from '@/people/constants/peopleFieldMetadataArray';
|
||||
import { peopleViewFields } from '@/people/constants/peopleViewFields';
|
||||
import { PeopleSelectedSortType } from '@/people/queries';
|
||||
import { reduceSortsToOrderBy } from '@/ui/filter-n-sort/helpers';
|
||||
import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState';
|
||||
@ -10,6 +9,7 @@ import { turnFilterIntoWhereClause } from '@/ui/filter-n-sort/utils/turnFilterIn
|
||||
import { IconList } from '@/ui/icon';
|
||||
import { useRecoilScopedValue } from '@/ui/recoil-scope/hooks/useRecoilScopedValue';
|
||||
import { EntityTable } from '@/ui/table/components/EntityTableV2';
|
||||
import { GenericEntityTableData } from '@/ui/table/components/GenericEntityTableData';
|
||||
import { TableContext } from '@/ui/table/states/TableContext';
|
||||
import {
|
||||
PersonOrderByWithRelationInput,
|
||||
|
||||
Reference in New Issue
Block a user