Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/generic editable cell chip #982

Merged
merged 12 commits into from
Jul 28, 2023
3 changes: 3 additions & 0 deletions front/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ import { AppInternalHooks } from '~/sync-hooks/AppInternalHooks';

import { SignInUp } from './pages/auth/SignInUp';

// TEMP FEATURE FLAG FOR VIEW FIELDS
export const ACTIVATE_VIEW_FIELDS = false;

export function App() {
return (
<>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { IconBuildingSkyscraper } from '@tabler/icons-react';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@/ui/icon


import { Entity } from '@/ui/relation-picker/types/EntityTypeForSelect';
import {
ViewFieldChipMetadata,
ViewFieldDefinition,
} from '@/ui/table/types/ViewField';

export const companyViewFields: ViewFieldDefinition<unknown>[] = [
{
columnLabel: 'Name',
columnIcon: <IconBuildingSkyscraper size={16} />,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use iconSize from theme

columnSize: 150,
type: 'chip',
columnOrder: 1,
metadata: {
urlFieldName: 'domainName',
contentFieldName: 'name',
relationType: Entity.Company,
},
} as ViewFieldDefinition<ViewFieldChipMetadata>,
];
54 changes: 54 additions & 0 deletions front/src/modules/companies/table/components/CompanyTableV2.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { useCallback, useMemo, useState } from 'react';

import { companyViewFields } from '@/companies/constants/companyFieldMetadataArray';
import { CompaniesSelectedSortType, defaultOrderBy } from '@/companies/queries';
import { GenericEntityTableData } from '@/people/components/GenericEntityTableData';
import { reduceSortsToOrderBy } from '@/ui/filter-n-sort/helpers';
import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState';
import { turnFilterIntoWhereClause } from '@/ui/filter-n-sort/utils/turnFilterIntoWhereClause';
import { IconList } from '@/ui/icon';
import { useRecoilScopedValue } from '@/ui/recoil-scope/hooks/useRecoilScopedValue';
import { EntityTable } from '@/ui/table/components/EntityTableV2';
import { TableContext } from '@/ui/table/states/TableContext';
import {
CompanyOrderByWithRelationInput,
useGetCompaniesQuery,
useUpdateOneCompanyMutation,
} from '~/generated/graphql';
import { companiesFilters } from '~/pages/companies/companies-filters';
import { availableSorts } from '~/pages/companies/companies-sorts';

export function CompanyTable() {
const [orderBy, setOrderBy] =
useState<CompanyOrderByWithRelationInput[]>(defaultOrderBy);

const updateSorts = useCallback((sorts: Array<CompaniesSelectedSortType>) => {
setOrderBy(sorts.length ? reduceSortsToOrderBy(sorts) : defaultOrderBy);
}, []);

const filters = useRecoilScopedValue(filtersScopedState, TableContext);

const whereFilters = useMemo(() => {
return { AND: filters.map(turnFilterIntoWhereClause) };
}, [filters]) as any;

return (
<>
<GenericEntityTableData
getRequestResultKey="companies"
useGetRequest={useGetCompaniesQuery}
orderBy={orderBy}
whereFilters={whereFilters}
viewFields={companyViewFields}
filterDefinitionArray={companiesFilters}
/>
<EntityTable
viewName="All Companies"
viewIcon={<IconList size={16} />}
availableSorts={availableSorts}
onSortsUpdate={updateSorts}
useUpdateEntityMutation={useUpdateOneCompanyMutation}
/>
</>
);
}
11 changes: 7 additions & 4 deletions front/src/modules/people/components/GenericEntityTableData.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { EntityFieldMetadata } from '@/ui/table/types/EntityFieldMetadata';
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';
Expand All @@ -8,13 +9,15 @@ export function GenericEntityTableData({
getRequestResultKey,
orderBy = defaultOrderBy,
whereFilters,
fieldMetadataArray,
viewFields,
filterDefinitionArray,
}: {
useGetRequest: any;
getRequestResultKey: string;
orderBy?: any;
whereFilters?: any;
fieldMetadataArray: EntityFieldMetadata[];
viewFields: ViewFieldDefinition<unknown>[];
filterDefinitionArray: FilterDefinition[];
}) {
const setEntityTableData = useSetEntityTableData();

Expand All @@ -23,7 +26,7 @@ export function GenericEntityTableData({
onCompleted: (data: any) => {
const entities = data[getRequestResultKey] ?? [];

setEntityTableData(entities, fieldMetadataArray);
setEntityTableData(entities, viewFields, filterDefinitionArray);
},
});

Expand Down
47 changes: 33 additions & 14 deletions front/src/modules/people/constants/peopleFieldMetadataArray.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,48 @@ import {
} from '@tabler/icons-react';

import { Entity } from '@/ui/relation-picker/types/EntityTypeForSelect';
import { EntityFieldMetadata } from '@/ui/table/types/EntityFieldMetadata';
import {
ViewFieldDefinition,
ViewFieldRelationMetadata,
ViewFieldTextMetadata,
} from '@/ui/table/types/ViewField';

export const peopleFieldMetadataArray: EntityFieldMetadata[] = [
export const peopleViewFields: ViewFieldDefinition<unknown>[] = [
{
fieldName: 'city',
label: 'City',
icon: <IconMap size={16} />,
id: 'city',
columnLabel: 'City',
columnIcon: <IconMap size={16} />,
columnSize: 150,
type: 'text',
},
columnOrder: 1,
metadata: {
fieldName: 'city',
placeHolder: 'City',
},
} as ViewFieldDefinition<ViewFieldTextMetadata>,
{
fieldName: 'jobTitle',
label: 'Job title',
icon: <IconBriefcase size={16} />,
id: 'jobTitle',
columnLabel: 'Job title',
columnIcon: <IconBriefcase size={16} />,
columnSize: 150,
type: 'text',
},
columnOrder: 2,
metadata: {
fieldName: 'jobTitle',
placeHolder: 'Job title',
},
} as ViewFieldDefinition<ViewFieldTextMetadata>,
{
fieldName: 'company',
label: 'Company',
icon: <IconBuildingSkyscraper size={16} />,
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>,
];
21 changes: 6 additions & 15 deletions front/src/modules/people/hooks/useSetEntityTableData.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { useRecoilCallback } from 'recoil';

import { FilterDefinition } from '@/ui/filter-n-sort/types/FilterDefinition';
import { entityFieldMetadataArrayState } from '@/ui/table/states/entityFieldMetadataArrayState';
import { tableEntitiesFamilyState } from '@/ui/table/states/tableEntitiesFamilyState';
import { EntityFieldMetadata } from '@/ui/table/types/EntityFieldMetadata';
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';
Expand All @@ -22,7 +22,8 @@ export function useSetEntityTableData() {
({ set, snapshot }) =>
<T extends { id: string }>(
newEntityArray: T[],
entityFieldMetadataArray: EntityFieldMetadata[],
viewFields: ViewFieldDefinition<unknown>[],
filters: FilterDefinition[],
) => {
for (const entity of newEntityArray) {
const currentEntity = snapshot
Expand All @@ -47,23 +48,13 @@ export function useSetEntityTableData() {
resetTableRowSelection();

set(entityTableDimensionsState, {
numberOfColumns: entityFieldMetadataArray.length,
numberOfColumns: viewFields.length,
numberOfRows: entityIds.length,
});

const filters = entityFieldMetadataArray.map(
(fieldMetadata) =>
({
field: fieldMetadata.fieldName,
icon: fieldMetadata.filterIcon,
label: fieldMetadata.label,
type: fieldMetadata.type,
} as FilterDefinition),
);

set(availableFiltersScopedState(tableContextScopeId), filters);

set(entityFieldMetadataArrayState, entityFieldMetadataArray);
set(viewFieldsState, viewFields);

set(isFetchingEntityTableDataState, false);
},
Expand Down
39 changes: 27 additions & 12 deletions front/src/modules/people/hooks/useUpdateEntityField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,38 @@ 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';
import { viewFieldsState } from '@/ui/table/states/viewFieldsState';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move this front/src/modules/people/hooks/useUpdateEntityField.ts to @/ui/table

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 entityFieldMetadataArray = useRecoilValue(
entityFieldMetadataArrayState,
);
const viewFields = useRecoilValue(viewFieldsState);

return function updatePeopleField(
currentEntityId: string,
fieldName: string,
viewFieldId: string,
newFieldValue: unknown,
) {
const fieldMetadata = entityFieldMetadataArray.find(
(metadata) => metadata.fieldName === fieldName,
const viewField = viewFields.find(
(metadata) => metadata.id === viewFieldId,
);

if (!fieldMetadata) {
throw new Error(`Field metadata not found for field ${fieldName}`);
if (!viewField) {
throw new Error(`View field not found for id ${viewFieldId}`);
}

if (fieldMetadata.type === 'relation') {
// 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: {
Expand All @@ -53,11 +57,22 @@ export function useUpdateEntityField() {
},
});
}
} else {
} 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: { [fieldName]: newFieldValue },
data: { [viewField.metadata.fieldName]: newContent },
},
});
}
Expand Down
6 changes: 4 additions & 2 deletions front/src/modules/people/table/components/PeopleTableV2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useCallback, useMemo, useState } from 'react';

import { defaultOrderBy } from '@/companies/queries';
import { GenericEntityTableData } from '@/people/components/GenericEntityTableData';
import { peopleFieldMetadataArray } from '@/people/constants/peopleFieldMetadataArray';
import { peopleViewFields } from '@/people/constants/peopleFieldMetadataArray';
import { PeopleSelectedSortType } from '@/people/queries';
import { reduceSortsToOrderBy } from '@/ui/filter-n-sort/helpers';
import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState';
Expand All @@ -16,6 +16,7 @@ import {
useGetPeopleQuery,
useUpdateOnePersonMutation,
} from '~/generated/graphql';
import { peopleFilters } from '~/pages/people/people-filters';
import { availableSorts } from '~/pages/people/people-sorts';

export function PeopleTable() {
Expand All @@ -39,7 +40,8 @@ export function PeopleTable() {
useGetRequest={useGetPeopleQuery}
orderBy={orderBy}
whereFilters={whereFilters}
fieldMetadataArray={peopleFieldMetadataArray}
viewFields={peopleViewFields}
filterDefinitionArray={peopleFilters}
/>
<EntityTable
viewName="All People"
Expand Down
6 changes: 3 additions & 3 deletions front/src/modules/ui/table/components/EntityTableCellV2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { RecoilScope } from '../../recoil-scope/components/RecoilScope';
import { useCurrentRowSelected } from '../hooks/useCurrentRowSelected';
import { ColumnIndexContext } from '../states/ColumnIndexContext';
import { contextMenuPositionState } from '../states/contextMenuPositionState';
import { EntityFieldMetadataContext } from '../states/EntityFieldMetadataContext';
import { ViewFieldContext } from '../states/ViewFieldContext';

export function EntityTableCell({ cellIndex }: { cellIndex: number }) {
const setContextMenuPosition = useSetRecoilState(contextMenuPositionState);
Expand All @@ -25,7 +25,7 @@ export function EntityTableCell({ cellIndex }: { cellIndex: number }) {
});
}

const entityFieldMetadata = useContext(EntityFieldMetadataContext);
const entityFieldMetadata = useContext(ViewFieldContext);

if (!entityFieldMetadata) {
return null;
Expand All @@ -42,7 +42,7 @@ export function EntityTableCell({ cellIndex }: { cellIndex: number }) {
maxWidth: entityFieldMetadata.columnSize,
}}
>
<GenericEditableCell entityFieldMetadata={entityFieldMetadata} />
<GenericEditableCell fieldDefinition={entityFieldMetadata} />
</td>
</ColumnIndexContext.Provider>
</RecoilScope>
Expand Down
Loading