diff --git a/front/src/App.tsx b/front/src/App.tsx index aaeedffd7718..13960c38d2c3 100644 --- a/front/src/App.tsx +++ b/front/src/App.tsx @@ -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 ( <> diff --git a/front/src/modules/companies/constants/companyFieldMetadataArray.tsx b/front/src/modules/companies/constants/companyFieldMetadataArray.tsx new file mode 100644 index 000000000000..8079550614a4 --- /dev/null +++ b/front/src/modules/companies/constants/companyFieldMetadataArray.tsx @@ -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[] = [ + { + columnLabel: 'Name', + columnIcon: , + columnSize: 150, + type: 'chip', + columnOrder: 1, + metadata: { + urlFieldName: 'domainName', + contentFieldName: 'name', + relationType: Entity.Company, + }, + } as ViewFieldDefinition, +]; diff --git a/front/src/modules/companies/table/components/CompanyTableV2.tsx b/front/src/modules/companies/table/components/CompanyTableV2.tsx new file mode 100644 index 000000000000..8abfe353898e --- /dev/null +++ b/front/src/modules/companies/table/components/CompanyTableV2.tsx @@ -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(defaultOrderBy); + + const updateSorts = useCallback((sorts: Array) => { + setOrderBy(sorts.length ? reduceSortsToOrderBy(sorts) : defaultOrderBy); + }, []); + + const filters = useRecoilScopedValue(filtersScopedState, TableContext); + + const whereFilters = useMemo(() => { + return { AND: filters.map(turnFilterIntoWhereClause) }; + }, [filters]) as any; + + return ( + <> + + } + availableSorts={availableSorts} + onSortsUpdate={updateSorts} + useUpdateEntityMutation={useUpdateOneCompanyMutation} + /> + + ); +} diff --git a/front/src/modules/people/components/GenericEntityTableData.tsx b/front/src/modules/people/components/GenericEntityTableData.tsx index b8689989864e..cd148b82ffbd 100644 --- a/front/src/modules/people/components/GenericEntityTableData.tsx +++ b/front/src/modules/people/components/GenericEntityTableData.tsx @@ -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'; @@ -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[]; + filterDefinitionArray: FilterDefinition[]; }) { const setEntityTableData = useSetEntityTableData(); @@ -23,7 +26,7 @@ export function GenericEntityTableData({ onCompleted: (data: any) => { const entities = data[getRequestResultKey] ?? []; - setEntityTableData(entities, fieldMetadataArray); + setEntityTableData(entities, viewFields, filterDefinitionArray); }, }); diff --git a/front/src/modules/people/constants/peopleFieldMetadataArray.tsx b/front/src/modules/people/constants/peopleFieldMetadataArray.tsx index 3f21bf769f1e..52863b1221c1 100644 --- a/front/src/modules/people/constants/peopleFieldMetadataArray.tsx +++ b/front/src/modules/people/constants/peopleFieldMetadataArray.tsx @@ -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[] = [ { - fieldName: 'city', - label: 'City', - icon: , + id: 'city', + columnLabel: 'City', + columnIcon: , columnSize: 150, type: 'text', - }, + columnOrder: 1, + metadata: { + fieldName: 'city', + placeHolder: 'City', + }, + } as ViewFieldDefinition, { - fieldName: 'jobTitle', - label: 'Job title', - icon: , + id: 'jobTitle', + columnLabel: 'Job title', + columnIcon: , columnSize: 150, type: 'text', - }, + columnOrder: 2, + metadata: { + fieldName: 'jobTitle', + placeHolder: 'Job title', + }, + } as ViewFieldDefinition, { - fieldName: 'company', - label: 'Company', - icon: , + id: 'company', + columnLabel: 'Company', + columnIcon: , columnSize: 150, type: 'relation', relationType: Entity.Company, - }, + columnOrder: 3, + metadata: { + fieldName: 'company', + relationType: Entity.Company, + }, + } as ViewFieldDefinition, ]; diff --git a/front/src/modules/people/hooks/useSetEntityTableData.ts b/front/src/modules/people/hooks/useSetEntityTableData.ts index 217b7efbae77..aac34b8972e3 100644 --- a/front/src/modules/people/hooks/useSetEntityTableData.ts +++ b/front/src/modules/people/hooks/useSetEntityTableData.ts @@ -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'; @@ -22,7 +22,8 @@ export function useSetEntityTableData() { ({ set, snapshot }) => ( newEntityArray: T[], - entityFieldMetadataArray: EntityFieldMetadata[], + viewFields: ViewFieldDefinition[], + filters: FilterDefinition[], ) => { for (const entity of newEntityArray) { const currentEntity = snapshot @@ -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); }, diff --git a/front/src/modules/people/hooks/useUpdateEntityField.ts b/front/src/modules/people/hooks/useUpdateEntityField.ts index 1affa08dc343..6c4aa5784648 100644 --- a/front/src/modules/people/hooks/useUpdateEntityField.ts +++ b/front/src/modules/people/hooks/useUpdateEntityField.ts @@ -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'; +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: { @@ -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 }, }, }); } diff --git a/front/src/modules/people/table/components/PeopleTableV2.tsx b/front/src/modules/people/table/components/PeopleTableV2.tsx index 50a972e594c8..00e79b9a63ec 100644 --- a/front/src/modules/people/table/components/PeopleTableV2.tsx +++ b/front/src/modules/people/table/components/PeopleTableV2.tsx @@ -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'; @@ -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() { @@ -39,7 +40,8 @@ export function PeopleTable() { useGetRequest={useGetPeopleQuery} orderBy={orderBy} whereFilters={whereFilters} - fieldMetadataArray={peopleFieldMetadataArray} + viewFields={peopleViewFields} + filterDefinitionArray={peopleFilters} /> - + diff --git a/front/src/modules/ui/table/components/EntityTableHeaderV2.tsx b/front/src/modules/ui/table/components/EntityTableHeaderV2.tsx index 5557008774ff..bb18d69de7a3 100644 --- a/front/src/modules/ui/table/components/EntityTableHeaderV2.tsx +++ b/front/src/modules/ui/table/components/EntityTableHeaderV2.tsx @@ -1,12 +1,12 @@ import { useRecoilValue } from 'recoil'; -import { entityFieldMetadataArrayState } from '../states/entityFieldMetadataArrayState'; +import { viewFieldsState } from '../states/viewFieldsState'; import { ColumnHead } from './ColumnHead'; import { SelectAllCheckbox } from './SelectAllCheckbox'; export function EntityTableHeader() { - const fieldMetadataArray = useRecoilValue(entityFieldMetadataArrayState); + const viewFields = useRecoilValue(viewFieldsState); return ( @@ -20,18 +20,18 @@ export function EntityTableHeader() { > - {fieldMetadataArray.map((fieldMetadata) => ( + {viewFields.map((viewField) => ( ))} diff --git a/front/src/modules/ui/table/components/EntityTableRowV2.tsx b/front/src/modules/ui/table/components/EntityTableRowV2.tsx index cbe2b78e9263..5e17ffb0c054 100644 --- a/front/src/modules/ui/table/components/EntityTableRowV2.tsx +++ b/front/src/modules/ui/table/components/EntityTableRowV2.tsx @@ -1,8 +1,8 @@ import styled from '@emotion/styled'; import { useRecoilValue } from 'recoil'; -import { entityFieldMetadataArrayState } from '../states/entityFieldMetadataArrayState'; -import { EntityFieldMetadataContext } from '../states/EntityFieldMetadataContext'; +import { ViewFieldContext } from '../states/ViewFieldContext'; +import { viewFieldsState } from '../states/viewFieldsState'; import { CheckboxCell } from './CheckboxCell'; import { EntityTableCell } from './EntityTableCellV2'; @@ -13,9 +13,7 @@ const StyledRow = styled.tr<{ selected: boolean }>` `; export function EntityTableRow({ rowId }: { rowId: string }) { - const entityFieldMetadataArray = useRecoilValue( - entityFieldMetadataArrayState, - ); + const entityFieldMetadataArray = useRecoilValue(viewFieldsState); return ( @@ -24,12 +22,12 @@ export function EntityTableRow({ rowId }: { rowId: string }) { {entityFieldMetadataArray.map((entityFieldMetadata, columnIndex) => { return ( - - + ); })} diff --git a/front/src/modules/ui/table/components/GenericEditableCell.tsx b/front/src/modules/ui/table/components/GenericEditableCell.tsx index 839244d36af4..ac72008c5b5f 100644 --- a/front/src/modules/ui/table/components/GenericEditableCell.tsx +++ b/front/src/modules/ui/table/components/GenericEditableCell.tsx @@ -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 { GenericEditableTextCell } from './GenericEditableTextCell'; type OwnProps = { - entityFieldMetadata: EntityFieldMetadata; + fieldDefinition: ViewFieldDefinition; }; -export function GenericEditableCell({ entityFieldMetadata }: OwnProps) { - switch (entityFieldMetadata.type) { - case 'text': - return ( - - ); - case 'relation': { - return ( - - ); - } - default: - console.warn( - `Unknown field type: ${entityFieldMetadata.type} in GenericEditableCell`, - ); - return <>; +export function GenericEditableCell({ fieldDefinition }: OwnProps) { + if (isViewFieldText(fieldDefinition)) { + return ( + + ); + } else if (isViewFieldRelation(fieldDefinition)) { + return ; + } else if (isViewFieldChip(fieldDefinition)) { + return ( + + ); + } else { + console.warn( + `Unknown field type: ${fieldDefinition.type} in GenericEditableCell`, + ); + return <>; } } diff --git a/front/src/modules/ui/table/components/GenericEditableChipCell.tsx b/front/src/modules/ui/table/components/GenericEditableChipCell.tsx new file mode 100644 index 000000000000..0754f2ec0a40 --- /dev/null +++ b/front/src/modules/ui/table/components/GenericEditableChipCell.tsx @@ -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; + editModeHorizontalAlign?: 'left' | 'right'; + placeholder?: string; +}; + +export function GenericEditableChipCell({ + viewField, + editModeHorizontalAlign, + placeholder, +}: OwnProps) { + return ( + + } + nonEditModeContent={ + + } + > + ); +} diff --git a/front/src/modules/ui/table/components/GenericEditableChipCellDisplayMode.tsx b/front/src/modules/ui/table/components/GenericEditableChipCellDisplayMode.tsx new file mode 100644 index 000000000000..f7e32ce0d555 --- /dev/null +++ b/front/src/modules/ui/table/components/GenericEditableChipCellDisplayMode.tsx @@ -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; +}; + +export function GenericEditableChipCellDisplayMode({ + fieldDefinition, +}: OwnProps) { + const currentRowEntityId = useCurrentRowEntityId(); + + const content = useRecoilValue( + tableEntityFieldFamilySelector({ + entityId: currentRowEntityId ?? '', + fieldName: fieldDefinition.metadata.contentFieldName, + }), + ); + + const chipUrl = useRecoilValue( + tableEntityFieldFamilySelector({ + entityId: currentRowEntityId ?? '', + fieldName: fieldDefinition.metadata.urlFieldName, + }), + ); + + switch (fieldDefinition.metadata.relationType) { + case Entity.Company: { + return ( + + ); + } + default: + console.warn( + `Unknown relation type: "${fieldDefinition.metadata.relationType}" in GenericEditableChipCellEditMode`, + ); + return <> ; + } +} diff --git a/front/src/modules/ui/table/components/GenericEditableRelationCell.tsx b/front/src/modules/ui/table/components/GenericEditableRelationCell.tsx index 9e5d0876ec1c..66330681068b 100644 --- a/front/src/modules/ui/table/components/GenericEditableRelationCell.tsx +++ b/front/src/modules/ui/table/components/GenericEditableRelationCell.tsx @@ -1,18 +1,21 @@ 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 { + ViewFieldDefinition, + ViewFieldRelationMetadata, +} from '@/ui/table/types/ViewField'; import { GenericEditableRelationCellDisplayMode } from './GenericEditableRelationCellDisplayMode'; import { GenericEditableRelationCellEditMode } from './GenericEditableRelationCellEditMode'; type OwnProps = { - fieldMetadata: EntityFieldMetadata; + fieldDefinition: ViewFieldDefinition; editModeHorizontalAlign?: 'left' | 'right'; placeholder?: string; }; export function GenericEditableRelationCell({ - fieldMetadata, + fieldDefinition, editModeHorizontalAlign, placeholder, }: OwnProps) { @@ -21,11 +24,13 @@ export function GenericEditableRelationCell({ editModeHorizontalAlign={editModeHorizontalAlign} editHotkeyScope={{ scope: RelationPickerHotkeyScope.RelationPicker }} editModeContent={ - + } nonEditModeContent={ diff --git a/front/src/modules/ui/table/components/GenericEditableRelationCellDisplayMode.tsx b/front/src/modules/ui/table/components/GenericEditableRelationCellDisplayMode.tsx index 8488516cdb6f..dcf4e880c3af 100644 --- a/front/src/modules/ui/table/components/GenericEditableRelationCellDisplayMode.tsx +++ b/front/src/modules/ui/table/components/GenericEditableRelationCellDisplayMode.tsx @@ -4,28 +4,31 @@ 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 { + ViewFieldDefinition, + ViewFieldRelationMetadata, +} from '@/ui/table/types/ViewField'; import { getLogoUrlFromDomainName } from '~/utils'; type OwnProps = { - fieldMetadata: EntityFieldMetadata; + fieldDefinition: ViewFieldDefinition; editModeHorizontalAlign?: 'left' | 'right'; placeholder?: string; }; export function GenericEditableRelationCellDisplayMode({ - fieldMetadata, + fieldDefinition, }: OwnProps) { const currentRowEntityId = useCurrentRowEntityId(); const fieldValue = useRecoilValue( tableEntityFieldFamilySelector({ entityId: currentRowEntityId ?? '', - fieldName: fieldMetadata.fieldName, + fieldName: fieldDefinition.metadata.fieldName, }), ); - switch (fieldMetadata.relationType) { + switch (fieldDefinition.metadata.relationType) { case Entity.Company: { return ( ; } diff --git a/front/src/modules/ui/table/components/GenericEditableRelationCellEditMode.tsx b/front/src/modules/ui/table/components/GenericEditableRelationCellEditMode.tsx index 5fb2311e0e98..4eb245182deb 100644 --- a/front/src/modules/ui/table/components/GenericEditableRelationCellEditMode.tsx +++ b/front/src/modules/ui/table/components/GenericEditableRelationCellEditMode.tsx @@ -7,14 +7,17 @@ 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'; +import { + ViewFieldDefinition, + ViewFieldRelationMetadata, +} from '@/ui/table/types/ViewField'; type OwnProps = { - fieldMetadata: EntityFieldMetadata; + viewFieldDefinition: ViewFieldDefinition; }; export function GenericEditableRelationCellEditMode({ - fieldMetadata, + viewFieldDefinition, }: OwnProps) { const currentRowEntityId = useCurrentRowEntityId(); @@ -23,7 +26,7 @@ export function GenericEditableRelationCellEditMode({ const [fieldValueEntity] = useRecoilState( tableEntityFieldFamilySelector({ entityId: currentRowEntityId ?? '', - fieldName: fieldMetadata.fieldName, + fieldName: viewFieldDefinition.metadata.fieldName, }), ); @@ -37,7 +40,7 @@ export function GenericEditableRelationCellEditMode({ ) { updateEntityField( currentRowEntityId, - fieldMetadata.fieldName, + viewFieldDefinition.id, newFieldEntity, ); } @@ -49,7 +52,7 @@ export function GenericEditableRelationCellEditMode({ closeEditableCell(); } - switch (fieldMetadata.relationType) { + switch (viewFieldDefinition.metadata.relationType) { case Entity.Company: { return ( ; } diff --git a/front/src/modules/ui/table/components/GenericEditableTextCell.tsx b/front/src/modules/ui/table/components/GenericEditableTextCell.tsx index 7d3406b28d57..71f921192d97 100644 --- a/front/src/modules/ui/table/components/GenericEditableTextCell.tsx +++ b/front/src/modules/ui/table/components/GenericEditableTextCell.tsx @@ -5,16 +5,18 @@ import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell'; import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector'; +import { ViewFieldDefinition, ViewFieldTextMetadata } from '../types/ViewField'; + import { GenericEditableTextCellEditMode } from './GenericEditableTextCellEditMode'; type OwnProps = { - fieldName: string; + viewField: ViewFieldDefinition; editModeHorizontalAlign?: 'left' | 'right'; placeholder?: string; }; export function GenericEditableTextCell({ - fieldName, + viewField, editModeHorizontalAlign, placeholder, }: OwnProps) { @@ -23,7 +25,7 @@ export function GenericEditableTextCell({ const fieldValue = useRecoilValue( tableEntityFieldFamilySelector({ entityId: currentRowEntityId ?? '', - fieldName, + fieldName: viewField.metadata.fieldName, }), ); @@ -32,7 +34,8 @@ export function GenericEditableTextCell({ editModeHorizontalAlign={editModeHorizontalAlign} editModeContent={ } diff --git a/front/src/modules/ui/table/components/GenericEditableTextCellEditMode.tsx b/front/src/modules/ui/table/components/GenericEditableTextCellEditMode.tsx index 18058e9af13d..c0da2f8b6472 100644 --- a/front/src/modules/ui/table/components/GenericEditableTextCellEditMode.tsx +++ b/front/src/modules/ui/table/components/GenericEditableTextCellEditMode.tsx @@ -7,15 +7,18 @@ import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFie type OwnProps = { fieldName: string; + viewFieldId: string; placeholder?: string; }; export function GenericEditableTextCellEditMode({ fieldName, + viewFieldId, placeholder, }: OwnProps) { const currentRowEntityId = useCurrentRowEntityId(); + // TODO: we could use a hook that would return the field value with the right type const [fieldValue, setFieldValue] = useRecoilState( tableEntityFieldFamilySelector({ entityId: currentRowEntityId ?? '', @@ -31,7 +34,7 @@ export function GenericEditableTextCellEditMode({ setFieldValue(newText); if (currentRowEntityId && updateField) { - updateField(currentRowEntityId, fieldName, newText); + updateField(currentRowEntityId, viewFieldId, newText); } } diff --git a/front/src/modules/ui/table/states/EntityFieldMetadataContext.ts b/front/src/modules/ui/table/states/EntityFieldMetadataContext.ts deleted file mode 100644 index fea74eae8013..000000000000 --- a/front/src/modules/ui/table/states/EntityFieldMetadataContext.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { createContext } from 'react'; - -import { EntityFieldMetadata } from '../types/EntityFieldMetadata'; - -export const EntityFieldMetadataContext = - createContext(null); diff --git a/front/src/modules/ui/table/states/ViewFieldContext.ts b/front/src/modules/ui/table/states/ViewFieldContext.ts new file mode 100644 index 000000000000..175907e419aa --- /dev/null +++ b/front/src/modules/ui/table/states/ViewFieldContext.ts @@ -0,0 +1,6 @@ +import { createContext } from 'react'; + +import { ViewFieldDefinition } from '../types/ViewField'; + +export const ViewFieldContext = + createContext | null>(null); diff --git a/front/src/modules/ui/table/states/entityFieldMetadataArrayState.ts b/front/src/modules/ui/table/states/entityFieldMetadataArrayState.ts deleted file mode 100644 index 217adc0ad69b..000000000000 --- a/front/src/modules/ui/table/states/entityFieldMetadataArrayState.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { atom } from 'recoil'; - -import { EntityFieldMetadata } from '../types/EntityFieldMetadata'; - -export const entityFieldMetadataArrayState = atom({ - key: 'entityFieldMetadataArrayState', - default: [], -}); diff --git a/front/src/modules/ui/table/states/viewFieldsState.ts b/front/src/modules/ui/table/states/viewFieldsState.ts new file mode 100644 index 000000000000..42ba0bf55a20 --- /dev/null +++ b/front/src/modules/ui/table/states/viewFieldsState.ts @@ -0,0 +1,8 @@ +import { atom } from 'recoil'; + +import { ViewFieldDefinition } from '../types/ViewField'; + +export const viewFieldsState = atom[]>({ + key: 'viewFieldsState', + default: [], +}); diff --git a/front/src/modules/ui/table/types/EntityFieldMetadata.ts b/front/src/modules/ui/table/types/EntityFieldMetadata.ts deleted file mode 100644 index 4231306ffb98..000000000000 --- a/front/src/modules/ui/table/types/EntityFieldMetadata.ts +++ /dev/null @@ -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" -}; diff --git a/front/src/modules/ui/table/types/ViewField.ts b/front/src/modules/ui/table/types/ViewField.ts new file mode 100644 index 000000000000..ff4021888e2e --- /dev/null +++ b/front/src/modules/ui/table/types/ViewField.ts @@ -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; +}; diff --git a/front/src/modules/ui/table/types/guards/isViewFieldChip.ts b/front/src/modules/ui/table/types/guards/isViewFieldChip.ts new file mode 100644 index 000000000000..25868ec2b08f --- /dev/null +++ b/front/src/modules/ui/table/types/guards/isViewFieldChip.ts @@ -0,0 +1,7 @@ +import { ViewFieldChipMetadata, ViewFieldDefinition } from '../ViewField'; + +export function isViewFieldChip( + field: ViewFieldDefinition, +): field is ViewFieldDefinition { + return field.type === 'chip'; +} diff --git a/front/src/modules/ui/table/types/guards/isViewFieldRelation.ts b/front/src/modules/ui/table/types/guards/isViewFieldRelation.ts new file mode 100644 index 000000000000..1642f9c04a89 --- /dev/null +++ b/front/src/modules/ui/table/types/guards/isViewFieldRelation.ts @@ -0,0 +1,7 @@ +import { ViewFieldDefinition, ViewFieldRelationMetadata } from '../ViewField'; + +export function isViewFieldRelation( + field: ViewFieldDefinition, +): field is ViewFieldDefinition { + return field.type === 'relation'; +} diff --git a/front/src/modules/ui/table/types/guards/isViewFieldText.ts b/front/src/modules/ui/table/types/guards/isViewFieldText.ts new file mode 100644 index 000000000000..d51bae806eee --- /dev/null +++ b/front/src/modules/ui/table/types/guards/isViewFieldText.ts @@ -0,0 +1,7 @@ +import { ViewFieldDefinition, ViewFieldTextMetadata } from '../ViewField'; + +export function isViewFieldText( + field: ViewFieldDefinition, +): field is ViewFieldDefinition { + return field.type === 'text'; +} diff --git a/front/src/pages/companies/Companies.tsx b/front/src/pages/companies/Companies.tsx index ec8ecba5bf43..52a33f7723df 100644 --- a/front/src/pages/companies/Companies.tsx +++ b/front/src/pages/companies/Companies.tsx @@ -5,6 +5,7 @@ import { useRecoilValue } from 'recoil'; import { GET_COMPANIES } from '@/companies/queries'; import { CompanyTable } from '@/companies/table/components/CompanyTable'; +import { CompanyTable as CompanyTableV2 } from '@/companies/table/components/CompanyTableV2'; import { TableActionBarButtonCreateActivityCompany } from '@/companies/table/components/TableActionBarButtonCreateActivityCompany'; import { TableActionBarButtonDeleteCompanies } from '@/companies/table/components/TableActionBarButtonDeleteCompanies'; import { IconBuildingSkyscraper } from '@/ui/icon'; @@ -17,8 +18,9 @@ import { TableContext } from '@/ui/table/states/TableContext'; import { FavoriteCreateManyInput, useInsertManyFavoritesMutation, - useInsertOneCompanyMutation, } from '~/generated/graphql'; +import { ACTIVATE_VIEW_FIELDS } from '~/App'; +import { useInsertOneCompanyMutation } from '~/generated/graphql'; import { SEARCH_COMPANY_QUERY } from '../../modules/search/queries/search'; import { GET_FAVORITES } from '@/favorites/queries/show'; @@ -71,6 +73,10 @@ export function Companies() { const theme = useTheme(); + const CompanyTableComponent = ACTIVATE_VIEW_FIELDS + ? CompanyTableV2 + : CompanyTable; + return ( <> - + diff --git a/front/src/pages/people/People.tsx b/front/src/pages/people/People.tsx index bd37c122425e..0b60ce972e54 100644 --- a/front/src/pages/people/People.tsx +++ b/front/src/pages/people/People.tsx @@ -6,6 +6,7 @@ import { useRecoilValue } from 'recoil'; import { GET_FAVORITES } from '@/favorites/queries/show'; import { GET_PEOPLE } from '@/people/queries'; import { PeopleTable } from '@/people/table/components/PeopleTable'; +import { PeopleTable as PeopleTableV2 } from '@/people/table/components/PeopleTableV2'; import { TableActionBarButtonCreateActivityPeople } from '@/people/table/components/TableActionBarButtonCreateActivityPeople'; import { TableActionBarButtonDeletePeople } from '@/people/table/components/TableActionBarButtonDeletePeople'; import { IconUser } from '@/ui/icon'; @@ -18,8 +19,9 @@ import { TableContext } from '@/ui/table/states/TableContext'; import { FavoriteCreateManyInput, useInsertManyFavoritesMutation, - useInsertOnePersonMutation, } from '~/generated/graphql'; +import { ACTIVATE_VIEW_FIELDS } from '~/App'; +import { useInsertOnePersonMutation } from '~/generated/graphql'; const StyledTableContainer = styled.div` display: flex; @@ -65,6 +67,10 @@ export function People() { const theme = useTheme(); + const PeopleTableComponent = ACTIVATE_VIEW_FIELDS + ? PeopleTableV2 + : PeopleTable; + return ( - +