Feat/generic editable cell chip (#982)

* 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
This commit is contained in:
Lucas Bordeau 2023-07-28 20:41:06 +02:00 committed by GitHub
parent d142376ef9
commit afaa962758
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 414 additions and 141 deletions

View File

@ -19,6 +19,9 @@ import { AppInternalHooks } from '~/sync-hooks/AppInternalHooks';
import { SignInUp } from './pages/auth/SignInUp'; import { SignInUp } from './pages/auth/SignInUp';
// TEMP FEATURE FLAG FOR VIEW FIELDS
export const ACTIVATE_VIEW_FIELDS = false;
export function App() { export function App() {
return ( return (
<> <>

View File

@ -0,0 +1,22 @@
import { IconBuildingSkyscraper } from '@tabler/icons-react';
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} />,
columnSize: 150,
type: 'chip',
columnOrder: 1,
metadata: {
urlFieldName: 'domainName',
contentFieldName: 'name',
relationType: Entity.Company,
},
} as ViewFieldDefinition<ViewFieldChipMetadata>,
];

View File

@ -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}
/>
</>
);
}

View File

@ -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 { useSetEntityTableData } from '../hooks/useSetEntityTableData';
import { defaultOrderBy } from '../queries'; import { defaultOrderBy } from '../queries';
@ -8,13 +9,15 @@ export function GenericEntityTableData({
getRequestResultKey, getRequestResultKey,
orderBy = defaultOrderBy, orderBy = defaultOrderBy,
whereFilters, whereFilters,
fieldMetadataArray, viewFields,
filterDefinitionArray,
}: { }: {
useGetRequest: any; useGetRequest: any;
getRequestResultKey: string; getRequestResultKey: string;
orderBy?: any; orderBy?: any;
whereFilters?: any; whereFilters?: any;
fieldMetadataArray: EntityFieldMetadata[]; viewFields: ViewFieldDefinition<unknown>[];
filterDefinitionArray: FilterDefinition[];
}) { }) {
const setEntityTableData = useSetEntityTableData(); const setEntityTableData = useSetEntityTableData();
@ -23,7 +26,7 @@ export function GenericEntityTableData({
onCompleted: (data: any) => { onCompleted: (data: any) => {
const entities = data[getRequestResultKey] ?? []; const entities = data[getRequestResultKey] ?? [];
setEntityTableData(entities, fieldMetadataArray); setEntityTableData(entities, viewFields, filterDefinitionArray);
}, },
}); });

View File

@ -5,29 +5,48 @@ import {
} from '@tabler/icons-react'; } from '@tabler/icons-react';
import { Entity } from '@/ui/relation-picker/types/EntityTypeForSelect'; 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', id: 'city',
label: 'City', columnLabel: 'City',
icon: <IconMap size={16} />, columnIcon: <IconMap size={16} />,
columnSize: 150, columnSize: 150,
type: 'text', type: 'text',
}, columnOrder: 1,
metadata: {
fieldName: 'city',
placeHolder: 'City',
},
} as ViewFieldDefinition<ViewFieldTextMetadata>,
{ {
fieldName: 'jobTitle', id: 'jobTitle',
label: 'Job title', columnLabel: 'Job title',
icon: <IconBriefcase size={16} />, columnIcon: <IconBriefcase size={16} />,
columnSize: 150, columnSize: 150,
type: 'text', type: 'text',
}, columnOrder: 2,
metadata: {
fieldName: 'jobTitle',
placeHolder: 'Job title',
},
} as ViewFieldDefinition<ViewFieldTextMetadata>,
{ {
fieldName: 'company', id: 'company',
label: 'Company', columnLabel: 'Company',
icon: <IconBuildingSkyscraper size={16} />, columnIcon: <IconBuildingSkyscraper size={16} />,
columnSize: 150, columnSize: 150,
type: 'relation', type: 'relation',
relationType: Entity.Company, relationType: Entity.Company,
}, columnOrder: 3,
metadata: {
fieldName: 'company',
relationType: Entity.Company,
},
} as ViewFieldDefinition<ViewFieldRelationMetadata>,
]; ];

View File

@ -1,9 +1,9 @@
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { FilterDefinition } from '@/ui/filter-n-sort/types/FilterDefinition'; import { FilterDefinition } from '@/ui/filter-n-sort/types/FilterDefinition';
import { entityFieldMetadataArrayState } from '@/ui/table/states/entityFieldMetadataArrayState';
import { tableEntitiesFamilyState } from '@/ui/table/states/tableEntitiesFamilyState'; 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 { availableFiltersScopedState } from '../../ui/filter-n-sort/states/availableFiltersScopedState';
import { useContextScopeId } from '../../ui/recoil-scope/hooks/useContextScopeId'; import { useContextScopeId } from '../../ui/recoil-scope/hooks/useContextScopeId';
@ -22,7 +22,8 @@ export function useSetEntityTableData() {
({ set, snapshot }) => ({ set, snapshot }) =>
<T extends { id: string }>( <T extends { id: string }>(
newEntityArray: T[], newEntityArray: T[],
entityFieldMetadataArray: EntityFieldMetadata[], viewFields: ViewFieldDefinition<unknown>[],
filters: FilterDefinition[],
) => { ) => {
for (const entity of newEntityArray) { for (const entity of newEntityArray) {
const currentEntity = snapshot const currentEntity = snapshot
@ -47,23 +48,13 @@ export function useSetEntityTableData() {
resetTableRowSelection(); resetTableRowSelection();
set(entityTableDimensionsState, { set(entityTableDimensionsState, {
numberOfColumns: entityFieldMetadataArray.length, numberOfColumns: viewFields.length,
numberOfRows: entityIds.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(availableFiltersScopedState(tableContextScopeId), filters);
set(entityFieldMetadataArrayState, entityFieldMetadataArray); set(viewFieldsState, viewFields);
set(isFetchingEntityTableDataState, false); set(isFetchingEntityTableDataState, false);
}, },

View File

@ -2,34 +2,38 @@ import { useContext } from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { EntityForSelect } from '@/ui/relation-picker/types/EntityForSelect'; import { EntityForSelect } from '@/ui/relation-picker/types/EntityForSelect';
import { entityFieldMetadataArrayState } from '@/ui/table/states/entityFieldMetadataArrayState';
import { EntityUpdateMutationHookContext } from '@/ui/table/states/EntityUpdateMutationHookContext'; 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() { export function useUpdateEntityField() {
const useUpdateEntityMutation = useContext(EntityUpdateMutationHookContext); const useUpdateEntityMutation = useContext(EntityUpdateMutationHookContext);
const [updateEntity] = useUpdateEntityMutation(); const [updateEntity] = useUpdateEntityMutation();
const entityFieldMetadataArray = useRecoilValue( const viewFields = useRecoilValue(viewFieldsState);
entityFieldMetadataArrayState,
);
return function updatePeopleField( return function updatePeopleField(
currentEntityId: string, currentEntityId: string,
fieldName: string, viewFieldId: string,
newFieldValue: unknown, newFieldValue: unknown,
) { ) {
const fieldMetadata = entityFieldMetadataArray.find( const viewField = viewFields.find(
(metadata) => metadata.fieldName === fieldName, (metadata) => metadata.id === viewFieldId,
); );
if (!fieldMetadata) { if (!viewField) {
throw new Error(`Field metadata not found for field ${fieldName}`); 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 newSelectedEntity = newFieldValue as EntityForSelect | null;
const fieldName = viewField.metadata.fieldName;
if (!newSelectedEntity) { if (!newSelectedEntity) {
updateEntity({ updateEntity({
variables: { variables: {
@ -53,11 +57,22 @@ export function useUpdateEntityField() {
}, },
}); });
} }
} else { } else if (isViewFieldChip(viewField)) {
const newContent = newFieldValue as string;
updateEntity({ updateEntity({
variables: { variables: {
where: { id: currentEntityId }, where: { id: currentEntityId },
data: { [fieldName]: newFieldValue }, data: { [viewField.metadata.contentFieldName]: newContent },
},
});
} else if (isViewFieldText(viewField)) {
const newContent = newFieldValue as string;
updateEntity({
variables: {
where: { id: currentEntityId },
data: { [viewField.metadata.fieldName]: newContent },
}, },
}); });
} }

View File

@ -2,7 +2,7 @@ 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 { peopleViewFields } from '@/people/constants/peopleFieldMetadataArray';
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';
@ -16,6 +16,7 @@ import {
useGetPeopleQuery, useGetPeopleQuery,
useUpdateOnePersonMutation, useUpdateOnePersonMutation,
} from '~/generated/graphql'; } from '~/generated/graphql';
import { peopleFilters } from '~/pages/people/people-filters';
import { availableSorts } from '~/pages/people/people-sorts'; import { availableSorts } from '~/pages/people/people-sorts';
export function PeopleTable() { export function PeopleTable() {
@ -39,7 +40,8 @@ export function PeopleTable() {
useGetRequest={useGetPeopleQuery} useGetRequest={useGetPeopleQuery}
orderBy={orderBy} orderBy={orderBy}
whereFilters={whereFilters} whereFilters={whereFilters}
fieldMetadataArray={peopleFieldMetadataArray} viewFields={peopleViewFields}
filterDefinitionArray={peopleFilters}
/> />
<EntityTable <EntityTable
viewName="All People" viewName="All People"

View File

@ -7,7 +7,7 @@ import { RecoilScope } from '../../recoil-scope/components/RecoilScope';
import { useCurrentRowSelected } from '../hooks/useCurrentRowSelected'; import { useCurrentRowSelected } from '../hooks/useCurrentRowSelected';
import { ColumnIndexContext } from '../states/ColumnIndexContext'; import { ColumnIndexContext } from '../states/ColumnIndexContext';
import { contextMenuPositionState } from '../states/contextMenuPositionState'; import { contextMenuPositionState } from '../states/contextMenuPositionState';
import { EntityFieldMetadataContext } from '../states/EntityFieldMetadataContext'; import { ViewFieldContext } from '../states/ViewFieldContext';
export function EntityTableCell({ cellIndex }: { cellIndex: number }) { export function EntityTableCell({ cellIndex }: { cellIndex: number }) {
const setContextMenuPosition = useSetRecoilState(contextMenuPositionState); const setContextMenuPosition = useSetRecoilState(contextMenuPositionState);
@ -25,7 +25,7 @@ export function EntityTableCell({ cellIndex }: { cellIndex: number }) {
}); });
} }
const entityFieldMetadata = useContext(EntityFieldMetadataContext); const entityFieldMetadata = useContext(ViewFieldContext);
if (!entityFieldMetadata) { if (!entityFieldMetadata) {
return null; return null;
@ -42,7 +42,7 @@ export function EntityTableCell({ cellIndex }: { cellIndex: number }) {
maxWidth: entityFieldMetadata.columnSize, maxWidth: entityFieldMetadata.columnSize,
}} }}
> >
<GenericEditableCell entityFieldMetadata={entityFieldMetadata} /> <GenericEditableCell fieldDefinition={entityFieldMetadata} />
</td> </td>
</ColumnIndexContext.Provider> </ColumnIndexContext.Provider>
</RecoilScope> </RecoilScope>

View File

@ -1,12 +1,12 @@
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { entityFieldMetadataArrayState } from '../states/entityFieldMetadataArrayState'; import { viewFieldsState } from '../states/viewFieldsState';
import { ColumnHead } from './ColumnHead'; import { ColumnHead } from './ColumnHead';
import { SelectAllCheckbox } from './SelectAllCheckbox'; import { SelectAllCheckbox } from './SelectAllCheckbox';
export function EntityTableHeader() { export function EntityTableHeader() {
const fieldMetadataArray = useRecoilValue(entityFieldMetadataArrayState); const viewFields = useRecoilValue(viewFieldsState);
return ( return (
<thead> <thead>
@ -20,18 +20,18 @@ export function EntityTableHeader() {
> >
<SelectAllCheckbox /> <SelectAllCheckbox />
</th> </th>
{fieldMetadataArray.map((fieldMetadata) => ( {viewFields.map((viewField) => (
<th <th
key={fieldMetadata.fieldName.toString()} key={viewField.columnOrder.toString()}
style={{ style={{
width: fieldMetadata.columnSize, width: viewField.columnSize,
minWidth: fieldMetadata.columnSize, minWidth: viewField.columnSize,
maxWidth: fieldMetadata.columnSize, maxWidth: viewField.columnSize,
}} }}
> >
<ColumnHead <ColumnHead
viewName={fieldMetadata.label} viewName={viewField.columnLabel}
viewIcon={fieldMetadata.icon} viewIcon={viewField.columnIcon}
/> />
</th> </th>
))} ))}

View File

@ -1,8 +1,8 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { entityFieldMetadataArrayState } from '../states/entityFieldMetadataArrayState'; import { ViewFieldContext } from '../states/ViewFieldContext';
import { EntityFieldMetadataContext } from '../states/EntityFieldMetadataContext'; import { viewFieldsState } from '../states/viewFieldsState';
import { CheckboxCell } from './CheckboxCell'; import { CheckboxCell } from './CheckboxCell';
import { EntityTableCell } from './EntityTableCellV2'; import { EntityTableCell } from './EntityTableCellV2';
@ -13,9 +13,7 @@ const StyledRow = styled.tr<{ selected: boolean }>`
`; `;
export function EntityTableRow({ rowId }: { rowId: string }) { export function EntityTableRow({ rowId }: { rowId: string }) {
const entityFieldMetadataArray = useRecoilValue( const entityFieldMetadataArray = useRecoilValue(viewFieldsState);
entityFieldMetadataArrayState,
);
return ( return (
<StyledRow data-testid={`row-id-${rowId}`} selected={false}> <StyledRow data-testid={`row-id-${rowId}`} selected={false}>
@ -24,12 +22,12 @@ export function EntityTableRow({ rowId }: { rowId: string }) {
</td> </td>
{entityFieldMetadataArray.map((entityFieldMetadata, columnIndex) => { {entityFieldMetadataArray.map((entityFieldMetadata, columnIndex) => {
return ( return (
<EntityFieldMetadataContext.Provider <ViewFieldContext.Provider
value={entityFieldMetadata} value={entityFieldMetadata}
key={entityFieldMetadata.fieldName} key={entityFieldMetadata.columnOrder}
> >
<EntityTableCell cellIndex={columnIndex} /> <EntityTableCell cellIndex={columnIndex} />
</EntityFieldMetadataContext.Provider> </ViewFieldContext.Provider>
); );
})} })}
<td></td> <td></td>

View File

@ -1,31 +1,38 @@
import { EntityFieldMetadata } from '@/ui/table/types/EntityFieldMetadata'; import { ViewFieldDefinition } from '@/ui/table/types/ViewField';
import { isViewFieldChip } from '../types/guards/isViewFieldChip';
import { isViewFieldRelation } from '../types/guards/isViewFieldRelation';
import { isViewFieldText } from '../types/guards/isViewFieldText';
import { GenericEditableChipCell } from './GenericEditableChipCell';
import { GenericEditableRelationCell } from './GenericEditableRelationCell'; import { GenericEditableRelationCell } from './GenericEditableRelationCell';
import { GenericEditableTextCell } from './GenericEditableTextCell'; import { GenericEditableTextCell } from './GenericEditableTextCell';
type OwnProps = { type OwnProps = {
entityFieldMetadata: EntityFieldMetadata; fieldDefinition: ViewFieldDefinition<unknown>;
}; };
export function GenericEditableCell({ entityFieldMetadata }: OwnProps) { export function GenericEditableCell({ fieldDefinition }: OwnProps) {
switch (entityFieldMetadata.type) { if (isViewFieldText(fieldDefinition)) {
case 'text': return (
return ( <GenericEditableTextCell
<GenericEditableTextCell viewField={fieldDefinition}
fieldName={entityFieldMetadata.fieldName} editModeHorizontalAlign="left"
placeholder={entityFieldMetadata.label} />
editModeHorizontalAlign="left" );
/> } else if (isViewFieldRelation(fieldDefinition)) {
); return <GenericEditableRelationCell fieldDefinition={fieldDefinition} />;
case 'relation': { } else if (isViewFieldChip(fieldDefinition)) {
return ( return (
<GenericEditableRelationCell fieldMetadata={entityFieldMetadata} /> <GenericEditableChipCell
); viewField={fieldDefinition}
} editModeHorizontalAlign="left"
default: />
console.warn( );
`Unknown field type: ${entityFieldMetadata.type} in GenericEditableCell`, } else {
); console.warn(
return <></>; `Unknown field type: ${fieldDefinition.type} in GenericEditableCell`,
);
return <></>;
} }
} }

View File

@ -0,0 +1,34 @@
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
import { ViewFieldChipMetadata, ViewFieldDefinition } from '../types/ViewField';
import { GenericEditableChipCellDisplayMode } from './GenericEditableChipCellDisplayMode';
import { GenericEditableTextCellEditMode } from './GenericEditableTextCellEditMode';
type OwnProps = {
viewField: ViewFieldDefinition<ViewFieldChipMetadata>;
editModeHorizontalAlign?: 'left' | 'right';
placeholder?: string;
};
export function GenericEditableChipCell({
viewField,
editModeHorizontalAlign,
placeholder,
}: OwnProps) {
return (
<EditableCell
editModeHorizontalAlign={editModeHorizontalAlign}
editModeContent={
<GenericEditableTextCellEditMode
fieldName={viewField.metadata.contentFieldName}
viewFieldId={viewField.id}
placeholder={placeholder}
/>
}
nonEditModeContent={
<GenericEditableChipCellDisplayMode fieldDefinition={viewField} />
}
></EditableCell>
);
}

View File

@ -0,0 +1,52 @@
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 {
ViewFieldChipMetadata,
ViewFieldDefinition,
} from '@/ui/table/types/ViewField';
import { getLogoUrlFromDomainName } from '~/utils';
type OwnProps = {
fieldDefinition: ViewFieldDefinition<ViewFieldChipMetadata>;
};
export function GenericEditableChipCellDisplayMode({
fieldDefinition,
}: OwnProps) {
const currentRowEntityId = useCurrentRowEntityId();
const content = useRecoilValue<any | null>(
tableEntityFieldFamilySelector({
entityId: currentRowEntityId ?? '',
fieldName: fieldDefinition.metadata.contentFieldName,
}),
);
const chipUrl = useRecoilValue<any | null>(
tableEntityFieldFamilySelector({
entityId: currentRowEntityId ?? '',
fieldName: fieldDefinition.metadata.urlFieldName,
}),
);
switch (fieldDefinition.metadata.relationType) {
case Entity.Company: {
return (
<CompanyChip
id={currentRowEntityId ?? ''}
name={content ?? ''}
pictureUrl={getLogoUrlFromDomainName(chipUrl)}
/>
);
}
default:
console.warn(
`Unknown relation type: "${fieldDefinition.metadata.relationType}" in GenericEditableChipCellEditMode`,
);
return <> </>;
}
}

View File

@ -1,18 +1,21 @@
import { RelationPickerHotkeyScope } from '@/ui/relation-picker/types/RelationPickerHotkeyScope'; import { RelationPickerHotkeyScope } from '@/ui/relation-picker/types/RelationPickerHotkeyScope';
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell'; import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
import { EntityFieldMetadata } from '@/ui/table/types/EntityFieldMetadata'; import {
ViewFieldDefinition,
ViewFieldRelationMetadata,
} from '@/ui/table/types/ViewField';
import { GenericEditableRelationCellDisplayMode } from './GenericEditableRelationCellDisplayMode'; import { GenericEditableRelationCellDisplayMode } from './GenericEditableRelationCellDisplayMode';
import { GenericEditableRelationCellEditMode } from './GenericEditableRelationCellEditMode'; import { GenericEditableRelationCellEditMode } from './GenericEditableRelationCellEditMode';
type OwnProps = { type OwnProps = {
fieldMetadata: EntityFieldMetadata; fieldDefinition: ViewFieldDefinition<ViewFieldRelationMetadata>;
editModeHorizontalAlign?: 'left' | 'right'; editModeHorizontalAlign?: 'left' | 'right';
placeholder?: string; placeholder?: string;
}; };
export function GenericEditableRelationCell({ export function GenericEditableRelationCell({
fieldMetadata, fieldDefinition,
editModeHorizontalAlign, editModeHorizontalAlign,
placeholder, placeholder,
}: OwnProps) { }: OwnProps) {
@ -21,11 +24,13 @@ export function GenericEditableRelationCell({
editModeHorizontalAlign={editModeHorizontalAlign} editModeHorizontalAlign={editModeHorizontalAlign}
editHotkeyScope={{ scope: RelationPickerHotkeyScope.RelationPicker }} editHotkeyScope={{ scope: RelationPickerHotkeyScope.RelationPicker }}
editModeContent={ editModeContent={
<GenericEditableRelationCellEditMode fieldMetadata={fieldMetadata} /> <GenericEditableRelationCellEditMode
viewFieldDefinition={fieldDefinition}
/>
} }
nonEditModeContent={ nonEditModeContent={
<GenericEditableRelationCellDisplayMode <GenericEditableRelationCellDisplayMode
fieldMetadata={fieldMetadata} fieldDefinition={fieldDefinition}
editModeHorizontalAlign={editModeHorizontalAlign} editModeHorizontalAlign={editModeHorizontalAlign}
placeholder={placeholder} placeholder={placeholder}
/> />

View File

@ -4,28 +4,31 @@ import { CompanyChip } from '@/companies/components/CompanyChip';
import { Entity } from '@/ui/relation-picker/types/EntityTypeForSelect'; import { Entity } from '@/ui/relation-picker/types/EntityTypeForSelect';
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';
import { EntityFieldMetadata } from '@/ui/table/types/EntityFieldMetadata'; import {
ViewFieldDefinition,
ViewFieldRelationMetadata,
} from '@/ui/table/types/ViewField';
import { getLogoUrlFromDomainName } from '~/utils'; import { getLogoUrlFromDomainName } from '~/utils';
type OwnProps = { type OwnProps = {
fieldMetadata: EntityFieldMetadata; fieldDefinition: ViewFieldDefinition<ViewFieldRelationMetadata>;
editModeHorizontalAlign?: 'left' | 'right'; editModeHorizontalAlign?: 'left' | 'right';
placeholder?: string; placeholder?: string;
}; };
export function GenericEditableRelationCellDisplayMode({ export function GenericEditableRelationCellDisplayMode({
fieldMetadata, fieldDefinition,
}: OwnProps) { }: OwnProps) {
const currentRowEntityId = useCurrentRowEntityId(); const currentRowEntityId = useCurrentRowEntityId();
const fieldValue = useRecoilValue<any | null>( const fieldValue = useRecoilValue<any | null>(
tableEntityFieldFamilySelector({ tableEntityFieldFamilySelector({
entityId: currentRowEntityId ?? '', entityId: currentRowEntityId ?? '',
fieldName: fieldMetadata.fieldName, fieldName: fieldDefinition.metadata.fieldName,
}), }),
); );
switch (fieldMetadata.relationType) { switch (fieldDefinition.metadata.relationType) {
case Entity.Company: { case Entity.Company: {
return ( return (
<CompanyChip <CompanyChip
@ -37,7 +40,7 @@ export function GenericEditableRelationCellDisplayMode({
} }
default: default:
console.warn( console.warn(
`Unknown relation type: "${fieldMetadata.relationType}" in GenericEditableRelationCellEditMode`, `Unknown relation type: "${fieldDefinition.metadata.relationType}" in GenericEditableRelationCellEditMode`,
); );
return <> </>; return <> </>;
} }

View File

@ -7,14 +7,17 @@ import { Entity } from '@/ui/relation-picker/types/EntityTypeForSelect';
import { useEditableCell } from '@/ui/table/editable-cell/hooks/useEditableCell'; import { useEditableCell } from '@/ui/table/editable-cell/hooks/useEditableCell';
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';
import { EntityFieldMetadata } from '@/ui/table/types/EntityFieldMetadata'; import {
ViewFieldDefinition,
ViewFieldRelationMetadata,
} from '@/ui/table/types/ViewField';
type OwnProps = { type OwnProps = {
fieldMetadata: EntityFieldMetadata; viewFieldDefinition: ViewFieldDefinition<ViewFieldRelationMetadata>;
}; };
export function GenericEditableRelationCellEditMode({ export function GenericEditableRelationCellEditMode({
fieldMetadata, viewFieldDefinition,
}: OwnProps) { }: OwnProps) {
const currentRowEntityId = useCurrentRowEntityId(); const currentRowEntityId = useCurrentRowEntityId();
@ -23,7 +26,7 @@ export function GenericEditableRelationCellEditMode({
const [fieldValueEntity] = useRecoilState<any | null>( const [fieldValueEntity] = useRecoilState<any | null>(
tableEntityFieldFamilySelector({ tableEntityFieldFamilySelector({
entityId: currentRowEntityId ?? '', entityId: currentRowEntityId ?? '',
fieldName: fieldMetadata.fieldName, fieldName: viewFieldDefinition.metadata.fieldName,
}), }),
); );
@ -37,7 +40,7 @@ export function GenericEditableRelationCellEditMode({
) { ) {
updateEntityField( updateEntityField(
currentRowEntityId, currentRowEntityId,
fieldMetadata.fieldName, viewFieldDefinition.id,
newFieldEntity, newFieldEntity,
); );
} }
@ -49,7 +52,7 @@ export function GenericEditableRelationCellEditMode({
closeEditableCell(); closeEditableCell();
} }
switch (fieldMetadata.relationType) { switch (viewFieldDefinition.metadata.relationType) {
case Entity.Company: { case Entity.Company: {
return ( return (
<CompanyPickerCell <CompanyPickerCell
@ -61,7 +64,7 @@ export function GenericEditableRelationCellEditMode({
} }
default: default:
console.warn( console.warn(
`Unknown relation type: "${fieldMetadata.relationType}" in GenericEditableRelationCellEditMode`, `Unknown relation type: "${viewFieldDefinition.metadata.relationType}" in GenericEditableRelationCellEditMode`,
); );
return <></>; return <></>;
} }

View File

@ -5,16 +5,18 @@ import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
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';
import { ViewFieldDefinition, ViewFieldTextMetadata } from '../types/ViewField';
import { GenericEditableTextCellEditMode } from './GenericEditableTextCellEditMode'; import { GenericEditableTextCellEditMode } from './GenericEditableTextCellEditMode';
type OwnProps = { type OwnProps = {
fieldName: string; viewField: ViewFieldDefinition<ViewFieldTextMetadata>;
editModeHorizontalAlign?: 'left' | 'right'; editModeHorizontalAlign?: 'left' | 'right';
placeholder?: string; placeholder?: string;
}; };
export function GenericEditableTextCell({ export function GenericEditableTextCell({
fieldName, viewField,
editModeHorizontalAlign, editModeHorizontalAlign,
placeholder, placeholder,
}: OwnProps) { }: OwnProps) {
@ -23,7 +25,7 @@ export function GenericEditableTextCell({
const fieldValue = useRecoilValue<string>( const fieldValue = useRecoilValue<string>(
tableEntityFieldFamilySelector({ tableEntityFieldFamilySelector({
entityId: currentRowEntityId ?? '', entityId: currentRowEntityId ?? '',
fieldName, fieldName: viewField.metadata.fieldName,
}), }),
); );
@ -32,7 +34,8 @@ export function GenericEditableTextCell({
editModeHorizontalAlign={editModeHorizontalAlign} editModeHorizontalAlign={editModeHorizontalAlign}
editModeContent={ editModeContent={
<GenericEditableTextCellEditMode <GenericEditableTextCellEditMode
fieldName={fieldName} fieldName={viewField.metadata.fieldName}
viewFieldId={viewField.id}
placeholder={placeholder} placeholder={placeholder}
/> />
} }

View File

@ -7,15 +7,18 @@ import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFie
type OwnProps = { type OwnProps = {
fieldName: string; fieldName: string;
viewFieldId: string;
placeholder?: string; placeholder?: string;
}; };
export function GenericEditableTextCellEditMode({ export function GenericEditableTextCellEditMode({
fieldName, fieldName,
viewFieldId,
placeholder, placeholder,
}: OwnProps) { }: OwnProps) {
const currentRowEntityId = useCurrentRowEntityId(); const currentRowEntityId = useCurrentRowEntityId();
// TODO: we could use a hook that would return the field value with the right type
const [fieldValue, setFieldValue] = useRecoilState<string>( const [fieldValue, setFieldValue] = useRecoilState<string>(
tableEntityFieldFamilySelector({ tableEntityFieldFamilySelector({
entityId: currentRowEntityId ?? '', entityId: currentRowEntityId ?? '',
@ -31,7 +34,7 @@ export function GenericEditableTextCellEditMode({
setFieldValue(newText); setFieldValue(newText);
if (currentRowEntityId && updateField) { if (currentRowEntityId && updateField) {
updateField(currentRowEntityId, fieldName, newText); updateField(currentRowEntityId, viewFieldId, newText);
} }
} }

View File

@ -1,6 +0,0 @@
import { createContext } from 'react';
import { EntityFieldMetadata } from '../types/EntityFieldMetadata';
export const EntityFieldMetadataContext =
createContext<EntityFieldMetadata | null>(null);

View File

@ -0,0 +1,6 @@
import { createContext } from 'react';
import { ViewFieldDefinition } from '../types/ViewField';
export const ViewFieldContext =
createContext<ViewFieldDefinition<unknown> | null>(null);

View File

@ -1,8 +0,0 @@
import { atom } from 'recoil';
import { EntityFieldMetadata } from '../types/EntityFieldMetadata';
export const entityFieldMetadataArrayState = atom<EntityFieldMetadata[]>({
key: 'entityFieldMetadataArrayState',
default: [],
});

View File

@ -0,0 +1,8 @@
import { atom } from 'recoil';
import { ViewFieldDefinition } from '../types/ViewField';
export const viewFieldsState = atom<ViewFieldDefinition<unknown>[]>({
key: 'viewFieldsState',
default: [],
});

View File

@ -1,13 +0,0 @@
import { Entity } from '@/ui/relation-picker/types/EntityTypeForSelect';
export type EntityFieldType = 'text' | 'relation';
export type EntityFieldMetadata = {
fieldName: string;
label: string;
type: EntityFieldType;
icon: JSX.Element;
columnSize: number;
filterIcon?: JSX.Element;
relationType?: Entity; // TODO: condition this type with type === "relation"
};

View File

@ -0,0 +1,36 @@
import { Entity } from '@/ui/relation-picker/types/EntityTypeForSelect';
export type ViewFieldType = 'text' | 'relation' | 'chip';
export type ViewFieldTextMetadata = {
placeHolder: string;
fieldName: string;
};
export type ViewFieldRelationMetadata = {
relationType: Entity;
fieldName: string;
};
export type ViewFieldChipMetadata = {
relationType: Entity;
contentFieldName: string;
urlFieldName: string;
};
export type ViewFieldDefinition<
T extends
| ViewFieldTextMetadata
| ViewFieldRelationMetadata
| ViewFieldChipMetadata
| unknown,
> = {
id: string;
columnLabel: string;
columnSize: number;
columnOrder: number;
columnIcon?: JSX.Element;
filterIcon?: JSX.Element;
type: ViewFieldType;
metadata: T;
};

View File

@ -0,0 +1,7 @@
import { ViewFieldChipMetadata, ViewFieldDefinition } from '../ViewField';
export function isViewFieldChip(
field: ViewFieldDefinition<unknown>,
): field is ViewFieldDefinition<ViewFieldChipMetadata> {
return field.type === 'chip';
}

View File

@ -0,0 +1,7 @@
import { ViewFieldDefinition, ViewFieldRelationMetadata } from '../ViewField';
export function isViewFieldRelation(
field: ViewFieldDefinition<unknown>,
): field is ViewFieldDefinition<ViewFieldRelationMetadata> {
return field.type === 'relation';
}

View File

@ -0,0 +1,7 @@
import { ViewFieldDefinition, ViewFieldTextMetadata } from '../ViewField';
export function isViewFieldText(
field: ViewFieldDefinition<unknown>,
): field is ViewFieldDefinition<ViewFieldTextMetadata> {
return field.type === 'text';
}

View File

@ -4,6 +4,7 @@ import styled from '@emotion/styled';
import { GET_COMPANIES } from '@/companies/queries'; import { GET_COMPANIES } from '@/companies/queries';
import { CompanyTable } from '@/companies/table/components/CompanyTable'; import { CompanyTable } from '@/companies/table/components/CompanyTable';
import { CompanyTable as CompanyTableV2 } from '@/companies/table/components/CompanyTableV2';
import { TableActionBarButtonCreateActivityCompany } from '@/companies/table/components/TableActionBarButtonCreateActivityCompany'; import { TableActionBarButtonCreateActivityCompany } from '@/companies/table/components/TableActionBarButtonCreateActivityCompany';
import { TableActionBarButtonDeleteCompanies } from '@/companies/table/components/TableActionBarButtonDeleteCompanies'; import { TableActionBarButtonDeleteCompanies } from '@/companies/table/components/TableActionBarButtonDeleteCompanies';
import { IconBuildingSkyscraper } from '@/ui/icon'; import { IconBuildingSkyscraper } from '@/ui/icon';
@ -11,6 +12,7 @@ import { WithTopBarContainer } from '@/ui/layout/components/WithTopBarContainer'
import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope'; import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope';
import { EntityTableActionBar } from '@/ui/table/action-bar/components/EntityTableActionBar'; import { EntityTableActionBar } from '@/ui/table/action-bar/components/EntityTableActionBar';
import { TableContext } from '@/ui/table/states/TableContext'; import { TableContext } from '@/ui/table/states/TableContext';
import { ACTIVATE_VIEW_FIELDS } from '~/App';
import { useInsertOneCompanyMutation } from '~/generated/graphql'; import { useInsertOneCompanyMutation } from '~/generated/graphql';
import { SEARCH_COMPANY_QUERY } from '../../modules/search/queries/search'; import { SEARCH_COMPANY_QUERY } from '../../modules/search/queries/search';
@ -41,6 +43,10 @@ export function Companies() {
const theme = useTheme(); const theme = useTheme();
const CompanyTableComponent = ACTIVATE_VIEW_FIELDS
? CompanyTableV2
: CompanyTable;
return ( return (
<> <>
<WithTopBarContainer <WithTopBarContainer
@ -50,7 +56,7 @@ export function Companies() {
> >
<RecoilScope SpecificContext={TableContext}> <RecoilScope SpecificContext={TableContext}>
<StyledTableContainer> <StyledTableContainer>
<CompanyTable /> <CompanyTableComponent />
</StyledTableContainer> </StyledTableContainer>
<EntityTableActionBar> <EntityTableActionBar>
<TableActionBarButtonCreateActivityCompany /> <TableActionBarButtonCreateActivityCompany />

View File

@ -4,6 +4,7 @@ import styled from '@emotion/styled';
import { GET_PEOPLE } from '@/people/queries'; import { GET_PEOPLE } from '@/people/queries';
import { PeopleTable } from '@/people/table/components/PeopleTable'; import { PeopleTable } from '@/people/table/components/PeopleTable';
import { PeopleTable as PeopleTableV2 } from '@/people/table/components/PeopleTableV2';
import { TableActionBarButtonCreateActivityPeople } from '@/people/table/components/TableActionBarButtonCreateActivityPeople'; import { TableActionBarButtonCreateActivityPeople } from '@/people/table/components/TableActionBarButtonCreateActivityPeople';
import { TableActionBarButtonDeletePeople } from '@/people/table/components/TableActionBarButtonDeletePeople'; import { TableActionBarButtonDeletePeople } from '@/people/table/components/TableActionBarButtonDeletePeople';
import { IconUser } from '@/ui/icon'; import { IconUser } from '@/ui/icon';
@ -11,6 +12,7 @@ import { WithTopBarContainer } from '@/ui/layout/components/WithTopBarContainer'
import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope'; import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope';
import { EntityTableActionBar } from '@/ui/table/action-bar/components/EntityTableActionBar'; import { EntityTableActionBar } from '@/ui/table/action-bar/components/EntityTableActionBar';
import { TableContext } from '@/ui/table/states/TableContext'; import { TableContext } from '@/ui/table/states/TableContext';
import { ACTIVATE_VIEW_FIELDS } from '~/App';
import { useInsertOnePersonMutation } from '~/generated/graphql'; import { useInsertOnePersonMutation } from '~/generated/graphql';
const StyledTableContainer = styled.div` const StyledTableContainer = styled.div`
@ -35,6 +37,10 @@ export function People() {
const theme = useTheme(); const theme = useTheme();
const PeopleTableComponent = ACTIVATE_VIEW_FIELDS
? PeopleTableV2
: PeopleTable;
return ( return (
<RecoilScope SpecificContext={TableContext}> <RecoilScope SpecificContext={TableContext}>
<WithTopBarContainer <WithTopBarContainer
@ -43,7 +49,7 @@ export function People() {
onAddButtonClick={handleAddButtonClick} onAddButtonClick={handleAddButtonClick}
> >
<StyledTableContainer> <StyledTableContainer>
<PeopleTable /> <PeopleTableComponent />
</StyledTableContainer> </StyledTableContainer>
<EntityTableActionBar> <EntityTableActionBar>
<TableActionBarButtonCreateActivityPeople /> <TableActionBarButtonCreateActivityPeople />