From 7da40125408a767894b6235819ace0315394b431 Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Tue, 10 Dec 2024 15:09:12 +0100 Subject: [PATCH 01/11] Introduce aggregateOperationForViewFieldState --- .../components/RecordIndexContainer.tsx | 20 ++++++++++ .../RecordIndexTableContainerEffect.tsx | 38 +++++++++++++++++++ .../aggregateOperationForViewFieldState.ts | 10 +++++ .../RecordTableColumnHeadWithDropdown.tsx | 1 - .../layout/dropdown/components/Dropdown.tsx | 1 - 5 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 packages/twenty-front/src/modules/object-record/record-table/record-table-footer/states/aggregateOperationForViewFieldState.ts diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx index 7af9419473e1..6458d9368ddb 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx @@ -28,6 +28,7 @@ import { useSetRecordGroup } from '@/object-record/record-group/hooks/useSetReco import { RecordIndexFiltersToContextStoreEffect } from '@/object-record/record-index/components/RecordIndexFiltersToContextStoreEffect'; import { recordIndexKanbanAggregateOperationState } from '@/object-record/record-index/states/recordIndexKanbanAggregateOperationState'; import { recordIndexViewFilterGroupsState } from '@/object-record/record-index/states/recordIndexViewFilterGroupsState'; +import { aggregateOperationForViewFieldState } from '@/object-record/record-table/record-table-footer/states/aggregateOperationForViewFieldState'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { ViewBar } from '@/views/components/ViewBar'; import { ViewField } from '@/views/types/ViewField'; @@ -118,6 +119,25 @@ export const RecordIndexContainer = () => { ) { set(recordIndexFieldDefinitionsState, newFieldDefinitions); } + + viewFields.forEach((viewField) => { + const aggregateOperationForViewField = snapshot + .getLoadable( + aggregateOperationForViewFieldState({ + viewFieldId: viewField.id, + }), + ) + .getValue(); + + if (aggregateOperationForViewField !== viewField.aggregateOperation) { + set( + aggregateOperationForViewFieldState({ + viewFieldId: viewField.id, + }), + viewField.aggregateOperation, + ); + } + }); }, [columnDefinitions, setTableColumns], ); diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx index 2ff744268dd0..52da256ce14b 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx @@ -6,7 +6,11 @@ import { RecordIndexRootPropsContext } from '@/object-record/record-index/contex import { useHandleToggleColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleColumnFilter'; import { useHandleToggleColumnSort } from '@/object-record/record-index/hooks/useHandleToggleColumnSort'; import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; +import { aggregateOperationForViewFieldState } from '@/object-record/record-table/record-table-footer/states/aggregateOperationForViewFieldState'; +import { useGetCurrentView } from '@/views/hooks/useGetCurrentView'; import { useSetRecordCountInCurrentView } from '@/views/hooks/useSetRecordCountInCurrentView'; +import { ViewField } from '@/views/types/ViewField'; +import { useRecoilCallback } from 'recoil'; export const RecordIndexTableContainerEffect = () => { const { recordIndexId, objectNameSingular } = useContext( @@ -48,6 +52,8 @@ export const RecordIndexTableContainerEffect = () => { viewBarId, }); + const { currentViewWithSavedFiltersAndSorts } = useGetCurrentView(); + useEffect(() => { setOnToggleColumnFilter( () => (fieldMetadataId: string) => @@ -68,5 +74,37 @@ export const RecordIndexTableContainerEffect = () => { ); }, [setRecordCountInCurrentView, setOnEntityCountChange]); + const setViewFieldsAggregateOperations = useRecoilCallback( + ({ set, snapshot }) => + (viewField: ViewField) => { + const aggregateOperationForViewField = snapshot + .getLoadable( + aggregateOperationForViewFieldState({ + viewFieldId: viewField.id, + }), + ) + .getValue(); + + if (aggregateOperationForViewField !== viewField.aggregateOperation) { + set( + aggregateOperationForViewFieldState({ + viewFieldId: viewField.id, + }), + viewField.aggregateOperation, + ); + } + }, + [], + ); + + useEffect(() => { + currentViewWithSavedFiltersAndSorts?.viewFields.forEach((viewField) => { + setViewFieldsAggregateOperations(viewField); + }); + }, [ + currentViewWithSavedFiltersAndSorts?.viewFields, + setViewFieldsAggregateOperations, + ]); + return <>; }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/states/aggregateOperationForViewFieldState.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/states/aggregateOperationForViewFieldState.ts new file mode 100644 index 000000000000..7e39ed2b48d1 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/states/aggregateOperationForViewFieldState.ts @@ -0,0 +1,10 @@ +import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; +import { createFamilyState } from '@/ui/utilities/state/utils/createFamilyState'; + +export const aggregateOperationForViewFieldState = createFamilyState< + AGGREGATE_OPERATIONS | null | undefined, + { viewFieldId: string } +>({ + key: 'aggregateOperationForViewFieldState', + defaultValue: null, +}); diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHeadWithDropdown.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHeadWithDropdown.tsx index adb91a3ecb31..473e92c79b6f 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHeadWithDropdown.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHeadWithDropdown.tsx @@ -47,7 +47,6 @@ export const RecordTableColumnHeadWithDropdown = ({ clickableComponent={} dropdownComponents={} dropdownOffset={{ x: -1 }} - usePortal dropdownPlacement="bottom-start" dropdownHotkeyScope={{ scope: column.fieldMetadataId + '-header' }} /> diff --git a/packages/twenty-front/src/modules/ui/layout/dropdown/components/Dropdown.tsx b/packages/twenty-front/src/modules/ui/layout/dropdown/components/Dropdown.tsx index 2c45e4fba792..812e68a90c5c 100644 --- a/packages/twenty-front/src/modules/ui/layout/dropdown/components/Dropdown.tsx +++ b/packages/twenty-front/src/modules/ui/layout/dropdown/components/Dropdown.tsx @@ -39,7 +39,6 @@ type DropdownProps = { dropdownStrategy?: 'fixed' | 'absolute'; disableBlur?: boolean; onClickOutside?: () => void; - usePortal?: boolean; onClose?: () => void; onOpen?: () => void; }; From 98a0c671c491a316f41dc30549fdcbfeac999263 Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Tue, 10 Dec 2024 17:36:08 +0100 Subject: [PATCH 02/11] Use loops instead of forEach --- .../record-index/components/RecordIndexContainer.tsx | 4 ++-- .../components/RecordIndexTableContainerEffect.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx index 6458d9368ddb..4f9e85547bff 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx @@ -120,7 +120,7 @@ export const RecordIndexContainer = () => { set(recordIndexFieldDefinitionsState, newFieldDefinitions); } - viewFields.forEach((viewField) => { + for (const viewField of viewFields) { const aggregateOperationForViewField = snapshot .getLoadable( aggregateOperationForViewFieldState({ @@ -137,7 +137,7 @@ export const RecordIndexContainer = () => { viewField.aggregateOperation, ); } - }); + }; }, [columnDefinitions, setTableColumns], ); diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx index 52da256ce14b..54403c4fc374 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx @@ -98,9 +98,9 @@ export const RecordIndexTableContainerEffect = () => { ); useEffect(() => { - currentViewWithSavedFiltersAndSorts?.viewFields.forEach((viewField) => { + for (const viewField of (currentViewWithSavedFiltersAndSorts?.viewFields || [])) { setViewFieldsAggregateOperations(viewField); - }); + }; }, [ currentViewWithSavedFiltersAndSorts?.viewFields, setViewFieldsAggregateOperations, From 8cd963dfee32f0a9e108903b60b1703ebbfa4167 Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Tue, 10 Dec 2024 18:02:45 +0100 Subject: [PATCH 03/11] Lint fix and switch back to forEach --- .../record-index/components/RecordIndexContainer.tsx | 2 +- .../components/RecordIndexTableContainerEffect.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx index 4f9e85547bff..276348c9d83c 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx @@ -137,7 +137,7 @@ export const RecordIndexContainer = () => { viewField.aggregateOperation, ); } - }; + } }, [columnDefinitions, setTableColumns], ); diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx index 54403c4fc374..b79cbb280ffc 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx @@ -98,9 +98,9 @@ export const RecordIndexTableContainerEffect = () => { ); useEffect(() => { - for (const viewField of (currentViewWithSavedFiltersAndSorts?.viewFields || [])) { + currentViewWithSavedFiltersAndSorts?.viewFields.forEach((viewField) => { setViewFieldsAggregateOperations(viewField); - }; + }) }, [ currentViewWithSavedFiltersAndSorts?.viewFields, setViewFieldsAggregateOperations, From a1bbdecd9fb1544dd7e8c0856e8615f58f842045 Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Tue, 10 Dec 2024 18:10:45 +0100 Subject: [PATCH 04/11] fix lint --- .../record-index/components/RecordIndexTableContainerEffect.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx index b79cbb280ffc..52da256ce14b 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx @@ -100,7 +100,7 @@ export const RecordIndexTableContainerEffect = () => { useEffect(() => { currentViewWithSavedFiltersAndSorts?.viewFields.forEach((viewField) => { setViewFieldsAggregateOperations(viewField); - }) + }); }, [ currentViewWithSavedFiltersAndSorts?.viewFields, setViewFieldsAggregateOperations, From f90f57448f963d7aeef7b455eb26c6296e452bb8 Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Tue, 10 Dec 2024 18:13:14 +0100 Subject: [PATCH 05/11] update naming --- .../components/RecordIndexTableContainerEffect.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx index 52da256ce14b..aa7e470c9c70 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx @@ -74,7 +74,7 @@ export const RecordIndexTableContainerEffect = () => { ); }, [setRecordCountInCurrentView, setOnEntityCountChange]); - const setViewFieldsAggregateOperations = useRecoilCallback( + const setViewFieldAggregateOperation = useRecoilCallback( ({ set, snapshot }) => (viewField: ViewField) => { const aggregateOperationForViewField = snapshot @@ -99,11 +99,11 @@ export const RecordIndexTableContainerEffect = () => { useEffect(() => { currentViewWithSavedFiltersAndSorts?.viewFields.forEach((viewField) => { - setViewFieldsAggregateOperations(viewField); + setViewFieldAggregateOperation(viewField); }); }, [ currentViewWithSavedFiltersAndSorts?.viewFields, - setViewFieldsAggregateOperations, + setViewFieldAggregateOperation, ]); return <>; From 599eed88ac8217f89b4fd2877b49789dacd273c7 Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Wed, 11 Dec 2024 16:53:12 +0100 Subject: [PATCH 06/11] Update computeAggregateValueAndLabel signature --- ...useAggregateRecordsForRecordBoardColumn.ts | 9 +-- .../computeAggregateValueAndLabel.test.ts | 53 +++++++++--------- .../utils/computeAggregateValueAndLabel.ts | 56 +++++++++++-------- 3 files changed, 64 insertions(+), 54 deletions(-) diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/hooks/useAggregateRecordsForRecordBoardColumn.ts b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/hooks/useAggregateRecordsForRecordBoardColumn.ts index b16db61e0ced..ad37fe530ff6 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/hooks/useAggregateRecordsForRecordBoardColumn.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/hooks/useAggregateRecordsForRecordBoardColumn.ts @@ -74,12 +74,13 @@ export const useAggregateRecordsForRecordBoardColumn = () => { skip: !isAggregateQueryEnabled, }); - const { value, label } = computeAggregateValueAndLabel( + const { value, label } = computeAggregateValueAndLabel({ data, objectMetadataItem, - recordIndexKanbanAggregateOperation, - kanbanFieldName, - ); + fieldMetadataId: recordIndexKanbanAggregateOperation?.fieldMetadataId, + aggregateOperation: recordIndexKanbanAggregateOperation?.operation, + fallbackFieldName: kanbanFieldName, + }); return { aggregateValue: isAggregateQueryEnabled ? value : recordCount, diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/__tests__/computeAggregateValueAndLabel.test.ts b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/__tests__/computeAggregateValueAndLabel.test.ts index f81f89e24f0f..44401248616b 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/__tests__/computeAggregateValueAndLabel.test.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/__tests__/computeAggregateValueAndLabel.test.ts @@ -5,7 +5,7 @@ import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/Agg import { FieldMetadataType } from '~/generated/graphql'; const MOCK_FIELD_ID = '7d2d7b5e-7b3e-4b4a-8b0a-7b3e4b4a8b0a'; -const MOCK_KANBAN_FIELD = 'stage'; +const MOCK_KANBAN_FIELD_NAME = 'stage'; describe('computeAggregateValueAndLabel', () => { const mockObjectMetadata: ObjectMetadataItem = { @@ -20,12 +20,13 @@ describe('computeAggregateValueAndLabel', () => { } as ObjectMetadataItem; it('should return empty object for empty data', () => { - const result = computeAggregateValueAndLabel( - {}, - mockObjectMetadata, - { fieldMetadataId: MOCK_FIELD_ID, operation: AGGREGATE_OPERATIONS.sum }, - MOCK_KANBAN_FIELD, - ); + const result = computeAggregateValueAndLabel({ + data: {}, + objectMetadataItem: mockObjectMetadata, + fieldMetadataId: MOCK_FIELD_ID, + aggregateOperation: AGGREGATE_OPERATIONS.sum, + fallbackFieldName: MOCK_KANBAN_FIELD_NAME, + }); expect(result).toEqual({}); }); @@ -37,12 +38,13 @@ describe('computeAggregateValueAndLabel', () => { }, }; - const result = computeAggregateValueAndLabel( - mockData, - mockObjectMetadata, - { fieldMetadataId: MOCK_FIELD_ID, operation: AGGREGATE_OPERATIONS.sum }, - MOCK_KANBAN_FIELD, - ); + const result = computeAggregateValueAndLabel({ + data: mockData, + objectMetadataItem: mockObjectMetadata, + fieldMetadataId: MOCK_FIELD_ID, + aggregateOperation: AGGREGATE_OPERATIONS.sum, + fallbackFieldName: MOCK_KANBAN_FIELD_NAME, + }); expect(result).toEqual({ value: 2, @@ -52,17 +54,16 @@ describe('computeAggregateValueAndLabel', () => { it('should default to count when field not found', () => { const mockData = { - [MOCK_KANBAN_FIELD]: { + [MOCK_KANBAN_FIELD_NAME]: { [AGGREGATE_OPERATIONS.count]: 42, }, }; - const result = computeAggregateValueAndLabel( - mockData, - mockObjectMetadata, - { fieldMetadataId: 'non-existent', operation: AGGREGATE_OPERATIONS.sum }, - MOCK_KANBAN_FIELD, - ); + const result = computeAggregateValueAndLabel({ + data: mockData, + objectMetadataItem: mockObjectMetadata, + fallbackFieldName: MOCK_KANBAN_FIELD_NAME, + }); expect(result).toEqual({ value: 42, @@ -77,12 +78,12 @@ describe('computeAggregateValueAndLabel', () => { }, }; - const result = computeAggregateValueAndLabel( - mockData, - mockObjectMetadata, - { fieldMetadataId: MOCK_FIELD_ID, operation: AGGREGATE_OPERATIONS.sum }, - MOCK_KANBAN_FIELD, - ); + const result = computeAggregateValueAndLabel({ + data: mockData, + objectMetadataItem: mockObjectMetadata, + fieldMetadataId: MOCK_FIELD_ID, + aggregateOperation: AGGREGATE_OPERATIONS.sum, + }); expect(result).toEqual({ value: undefined, diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel.ts b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel.ts index 52ef2b86d4cd..b30c673c708a 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel.ts @@ -1,51 +1,59 @@ import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { AggregateRecordsData } from '@/object-record/hooks/useAggregateRecords'; import { getAggregateOperationLabel } from '@/object-record/record-board/record-board-column/utils/getAggregateOperationLabel'; -import { KanbanAggregateOperation } from '@/object-record/record-index/states/recordIndexKanbanAggregateOperationState'; import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import isEmpty from 'lodash.isempty'; import { FieldMetadataType } from '~/generated-metadata/graphql'; import { isDefined } from '~/utils/isDefined'; -export const computeAggregateValueAndLabel = ( - data: AggregateRecordsData, - objectMetadataItem: ObjectMetadataItem, - recordIndexKanbanAggregateOperation: KanbanAggregateOperation, - kanbanFieldName: string, -) => { +export const computeAggregateValueAndLabel = ({ + data, + objectMetadataItem, + fieldMetadataId, + aggregateOperation, + fallbackFieldName, +}: { + data: AggregateRecordsData; + objectMetadataItem: ObjectMetadataItem; + fieldMetadataId?: string | null; + aggregateOperation?: AGGREGATE_OPERATIONS | null; + fallbackFieldName?: string; +}) => { if (isEmpty(data)) { return {}; } - const kanbanAggregateOperationField = objectMetadataItem.fields?.find( - (field) => - field.id === recordIndexKanbanAggregateOperation?.fieldMetadataId, + const field = objectMetadataItem.fields?.find( + (field) => field.id === fieldMetadataId, ); - const kanbanAggregateOperationFieldName = kanbanAggregateOperationField?.name; - - if ( - !isDefined(kanbanAggregateOperationFieldName) || - !isDefined(recordIndexKanbanAggregateOperation?.operation) - ) { + if (!isDefined(field)) { + if (!fallbackFieldName) { + throw new Error('Missing fallback field name'); + } return { - value: data?.[kanbanFieldName]?.[AGGREGATE_OPERATIONS.count], + value: data?.[fallbackFieldName]?.[AGGREGATE_OPERATIONS.count], label: `${getAggregateOperationLabel(AGGREGATE_OPERATIONS.count)}`, }; } - const aggregateValue = - data[kanbanAggregateOperationFieldName]?.[ - recordIndexKanbanAggregateOperation.operation - ]; + if (!isDefined(aggregateOperation)) { + throw new Error('Missing aggregate operation'); + } + + const aggregateValue = data[field.name]?.[aggregateOperation]; const value = - isDefined(aggregateValue) && - kanbanAggregateOperationField?.type === FieldMetadataType.Currency + isDefined(aggregateValue) && field.type === FieldMetadataType.Currency ? Number(aggregateValue) / 1_000_000 : aggregateValue; + const label = + aggregateOperation === AGGREGATE_OPERATIONS.count + ? `${getAggregateOperationLabel(AGGREGATE_OPERATIONS.count)}` + : `${getAggregateOperationLabel(aggregateOperation)} of ${field.name}`; + return { value, - label: `${getAggregateOperationLabel(recordIndexKanbanAggregateOperation.operation)} of ${kanbanAggregateOperationFieldName}`, + label, }; }; From 7dbdfc24ef3b45962c11e418afe8c927019df4d2 Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Wed, 11 Dec 2024 16:57:03 +0100 Subject: [PATCH 07/11] Fetch aggregate value for footer --- ...egateRecordsForRecordTableColumnFooter.tsx | 76 +++++++++++++++++++ .../utils/buildRecordGqlFieldsAggregate.ts | 14 ++++ ...ggregateOperationsForFieldMetadataType.tsx | 30 ++++++++ .../record-table/types/ColumnDefinition.ts | 2 + .../utils/generateAggregateQuery.ts | 1 + .../internal/usePersistViewFieldRecords.ts | 1 + .../modules/views/hooks/useUpdateViewField.ts | 27 +++++++ .../utils/mapColumnDefinitionToViewField.ts | 1 + 8 files changed, 152 insertions(+) create mode 100644 packages/twenty-front/src/modules/object-record/record-table/record-table-footer/hooks/useAggregateRecordsForRecordTableColumnFooter.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-table/record-table-footer/utils/buildRecordGqlFieldsAggregate.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-table/record-table-footer/utils/getAvailableAggregateOperationsForFieldMetadataType.tsx create mode 100644 packages/twenty-front/src/modules/views/hooks/useUpdateViewField.ts diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/hooks/useAggregateRecordsForRecordTableColumnFooter.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/hooks/useAggregateRecordsForRecordTableColumnFooter.tsx new file mode 100644 index 000000000000..041c9e0c223e --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/hooks/useAggregateRecordsForRecordTableColumnFooter.tsx @@ -0,0 +1,76 @@ +import { useAggregateRecords } from '@/object-record/hooks/useAggregateRecords'; +import { computeAggregateValueAndLabel } from '@/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel'; +import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter'; +import { recordIndexFiltersState } from '@/object-record/record-index/states/recordIndexFiltersState'; +import { recordIndexViewFilterGroupsState } from '@/object-record/record-index/states/recordIndexViewFilterGroupsState'; +import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext'; +import { aggregateOperationForViewFieldState } from '@/object-record/record-table/record-table-footer/states/aggregateOperationForViewFieldState'; +import { useGetCurrentView } from '@/views/hooks/useGetCurrentView'; +import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; +import { useContext } from 'react'; +import { useRecoilValue } from 'recoil'; +import { isDefined } from '~/utils/isDefined'; + +export const useAggregateRecordsForRecordTableColumnFooter = ( + fieldMetadataId: string, +) => { + const isAggregateQueryEnabled = useIsFeatureEnabled( + 'IS_AGGREGATE_QUERY_ENABLED', + ); + + const { objectMetadataItem } = useContext(RecordTableContext); + const { currentViewWithSavedFiltersAndSorts } = useGetCurrentView(); + const recordIndexViewFilterGroups = useRecoilValue( + recordIndexViewFilterGroupsState, + ); + + const recordIndexFilters = useRecoilValue(recordIndexFiltersState); + const requestFilters = computeViewRecordGqlOperationFilter( + recordIndexFilters, + objectMetadataItem.fields, + recordIndexViewFilterGroups, + ); + + const viewFieldId = currentViewWithSavedFiltersAndSorts?.viewFields?.find( + (viewField) => viewField.fieldMetadataId === fieldMetadataId, + )?.id; + + if (!viewFieldId) { + throw new Error('View field not found') + } + + const aggregateOperationForViewField = useRecoilValue( + aggregateOperationForViewFieldState({ viewFieldId: viewFieldId }), + ); + + const fieldName = objectMetadataItem.fields.find( + (field) => field.id === fieldMetadataId, + )?.name; + + const recordGqlFieldsAggregate = + isDefined(aggregateOperationForViewField) && isDefined(fieldName) + ? { + [fieldName]: [aggregateOperationForViewField], + } + : {}; + + const { data } = useAggregateRecords({ + objectNameSingular: objectMetadataItem.nameSingular, + recordGqlFieldsAggregate, + filter: { ...requestFilters }, + skip: + !isAggregateQueryEnabled || !isDefined(aggregateOperationForViewField), + }); + + const { value, label } = computeAggregateValueAndLabel({ + data, + objectMetadataItem, + fieldMetadataId: fieldMetadataId, + aggregateOperation: aggregateOperationForViewField, + }); + + return { + aggregateValue: value, + aggregateLabel: isDefined(value) ? label : undefined, + }; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/utils/buildRecordGqlFieldsAggregate.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/utils/buildRecordGqlFieldsAggregate.ts new file mode 100644 index 000000000000..de0d70842e90 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/utils/buildRecordGqlFieldsAggregate.ts @@ -0,0 +1,14 @@ +import { RecordGqlFieldsAggregate } from '@/object-record/graphql/types/RecordGqlFieldsAggregate'; +import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; + +export const buildRecordGqlFieldsAggregate = ({ + aggregateOperation, + fieldName, +}: { + fieldName: string; + aggregateOperation?: AGGREGATE_OPERATIONS | null; +}): RecordGqlFieldsAggregate => { + return { + [fieldName]: [aggregateOperation ?? AGGREGATE_OPERATIONS.count], + }; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/utils/getAvailableAggregateOperationsForFieldMetadataType.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/utils/getAvailableAggregateOperationsForFieldMetadataType.tsx new file mode 100644 index 000000000000..c2b19d548b55 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/utils/getAvailableAggregateOperationsForFieldMetadataType.tsx @@ -0,0 +1,30 @@ +import { AGGREGATE_OPERATIONS } from "@/object-record/record-table/constants/AggregateOperations"; +import { FIELDS_AVAILABLE_BY_AGGREGATE_OPERATION } from "@/object-record/record-table/constants/FieldsAvailableByAggregateOperation"; +import { AggregateOperationsOmittingCount } from "@/object-record/types/AggregateOperationsOmittingCount"; +import { isFieldTypeValidForAggregateOperation } from "@/object-record/utils/isFieldTypeValidForAggregateOperation"; +import { FieldMetadataType } from "~/generated/graphql"; +import { isDefined } from "~/utils/isDefined"; + +export const getAvailableAggregateOperationsForFieldMetadataType = ({fieldMetadataType}: {fieldMetadataType?: FieldMetadataType}) => { + let availableAggregateOperations: AGGREGATE_OPERATIONS[] = [AGGREGATE_OPERATIONS.count]; + + if (!isDefined(fieldMetadataType)) { + return availableAggregateOperations; + } + Object.keys(FIELDS_AVAILABLE_BY_AGGREGATE_OPERATION).forEach( + (aggregateOperation) => { + const typedAggregateOperation = + aggregateOperation as AggregateOperationsOmittingCount; + + if ( + isFieldTypeValidForAggregateOperation( + fieldMetadataType, + typedAggregateOperation, + ) + ) { + availableAggregateOperations.push(typedAggregateOperation); + } + }, + ); + return availableAggregateOperations; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/types/ColumnDefinition.ts b/packages/twenty-front/src/modules/object-record/record-table/types/ColumnDefinition.ts index c14b745ef541..c649dc4c182b 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/types/ColumnDefinition.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/types/ColumnDefinition.ts @@ -1,5 +1,6 @@ import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition'; import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; +import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; export type ColumnDefinition = FieldDefinition & { size: number; @@ -9,4 +10,5 @@ export type ColumnDefinition = FieldDefinition & { viewFieldId?: string; isFilterable?: boolean; isSortable?: boolean; + aggregateOperation?: AGGREGATE_OPERATIONS | null; }; diff --git a/packages/twenty-front/src/modules/object-record/utils/generateAggregateQuery.ts b/packages/twenty-front/src/modules/object-record/utils/generateAggregateQuery.ts index d6feb05e5475..f2e4c2871363 100644 --- a/packages/twenty-front/src/modules/object-record/utils/generateAggregateQuery.ts +++ b/packages/twenty-front/src/modules/object-record/utils/generateAggregateQuery.ts @@ -22,6 +22,7 @@ export const generateAggregateQuery = ({ objectMetadataItem.nameSingular, )}FilterInput) { ${objectMetadataItem.namePlural}(filter: $filter) { + ${selectedFields ? '' : 'typename'} ${selectedFields} } } diff --git a/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewFieldRecords.ts b/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewFieldRecords.ts index f08c49e54729..289911241a98 100644 --- a/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewFieldRecords.ts +++ b/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewFieldRecords.ts @@ -88,6 +88,7 @@ export const usePersistViewFieldRecords = () => { isVisible: viewField.isVisible, position: viewField.position, size: viewField.size, + aggregateOperation: viewField.aggregateOperation, }, }, update: (cache, { data }) => { diff --git a/packages/twenty-front/src/modules/views/hooks/useUpdateViewField.ts b/packages/twenty-front/src/modules/views/hooks/useUpdateViewField.ts new file mode 100644 index 000000000000..2f73b062ca14 --- /dev/null +++ b/packages/twenty-front/src/modules/views/hooks/useUpdateViewField.ts @@ -0,0 +1,27 @@ +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; +import { ViewField } from '@/views/types/ViewField'; +import { useRecoilCallback } from 'recoil'; +import { isDefined } from 'twenty-ui'; + +export const useUpdateViewField = () => { + const { updateOneRecord } = useUpdateOneRecord({ + objectNameSingular: CoreObjectNameSingular.ViewField, + }); + + const updateViewField = useRecoilCallback( + () => async (viewField: Partial) => { + if (isDefined(viewField.id)) { + await updateOneRecord({ + idToUpdate: viewField.id, + updateOneRecordInput: viewField, + }); + } + }, + [updateOneRecord], + ); + + return { + updateViewField, + }; +}; diff --git a/packages/twenty-front/src/modules/views/utils/mapColumnDefinitionToViewField.ts b/packages/twenty-front/src/modules/views/utils/mapColumnDefinitionToViewField.ts index 7b00889bbded..35973987635e 100644 --- a/packages/twenty-front/src/modules/views/utils/mapColumnDefinitionToViewField.ts +++ b/packages/twenty-front/src/modules/views/utils/mapColumnDefinitionToViewField.ts @@ -14,5 +14,6 @@ export const mapColumnDefinitionsToViewFields = ( size: columnDefinition.size, isVisible: columnDefinition.isVisible ?? true, definition: columnDefinition, + aggregateOperation: columnDefinition.aggregateOperation, })); }; From ba9781987dc43ff30691e0a84d7b028d0d874bda Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Wed, 11 Dec 2024 17:01:29 +0100 Subject: [PATCH 08/11] Add footer cells --- .../record-table/components/RecordTable.tsx | 7 ++ .../components/RecordTableStickyEffect.tsx | 6 ++ .../RecordTableColumnFooterAggregateValue.tsx | 89 ++++++++++++++++++ .../RecordTableColumnFooterDropdown.tsx | 77 +++++++++++++++ .../RecordTableColumnFooterWithDropdown.tsx | 68 ++++++++++++++ .../components/RecordTableFooter.tsx | 93 +++++++++++++++++++ .../components/RecordTableFooterCell.tsx | 90 ++++++++++++++++++ 7 files changed, 430 insertions(+) create mode 100644 packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnFooterAggregateValue.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnFooterDropdown.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnFooterWithDropdown.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableFooter.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableFooterCell.tsx diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx index 4d6f5331bccc..9df91555708a 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx @@ -14,12 +14,14 @@ import { RecordTableNoRecordGroupBody } from '@/object-record/record-table/recor import { RecordTableNoRecordGroupBodyEffect } from '@/object-record/record-table/record-table-body/components/RecordTableNoRecordGroupBodyEffect'; import { RecordTableRecordGroupBodyEffects } from '@/object-record/record-table/record-table-body/components/RecordTableRecordGroupBodyEffects'; import { RecordTableRecordGroupsBody } from '@/object-record/record-table/record-table-body/components/RecordTableRecordGroupsBody'; +import { RecordTableFooter } from '@/object-record/record-table/record-table-footer/components/RecordTableFooter'; import { RecordTableHeader } from '@/object-record/record-table/record-table-header/components/RecordTableHeader'; import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState'; import { recordTablePendingRecordIdComponentState } from '@/object-record/record-table/states/recordTablePendingRecordIdComponentState'; import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect'; import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useRef } from 'react'; const StyledTable = styled.table` @@ -44,6 +46,10 @@ export const RecordTable = ({ }: RecordTableProps) => { const tableBodyRef = useRef(null); + const isAggregateQueryEnabled = useIsFeatureEnabled( + 'IS_AGGREGATE_QUERY_ENABLED', + ); + const { toggleClickOutsideListener } = useClickOutsideListener( RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID, ); @@ -111,6 +117,7 @@ export const RecordTable = ({ ) : ( )} + {isAggregateQueryEnabled && } { document .getElementById('record-table-header') ?.classList.add('first-columns-sticky'); + document + .getElementById('record-table-footer') + ?.classList.add('first-columns-sticky'); } else { document .getElementById('record-table-body') @@ -42,6 +45,9 @@ export const RecordTableStickyEffect = () => { document .getElementById('record-table-header') ?.classList.remove('first-columns-sticky'); + document + .getElementById('record-table-footer') + ?.classList.remove('first-columns-sticky'); } }, [scrollLeft, setIsRecordTableScrolledLeft]); diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnFooterAggregateValue.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnFooterAggregateValue.tsx new file mode 100644 index 000000000000..fd690ef55e8c --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnFooterAggregateValue.tsx @@ -0,0 +1,89 @@ +import { useTheme } from '@emotion/react'; +import styled from '@emotion/styled'; +import { useState } from 'react'; +import { AppTooltip, IconChevronDown, TooltipDelay } from 'twenty-ui'; +import { isDefined } from '~/utils/isDefined'; + +const StyledCell = styled.div<{ hideTitle?: boolean }>` + align-items: center; + display: flex; + flex-direction: row; + flex-shrink: 0; + font-weight: ${({ theme }) => theme.font.weight.medium}; + + gap: ${({ theme }) => theme.spacing(1)}; + height: ${({ theme }) => theme.spacing(7)}; + justify-content: space-between; + min-width: ${({ theme }) => theme.spacing(7)}; + flex-grow: 1; + width: 100%; +`; + +const StyledText = styled.span` + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + display: flex; + height: 20px; + align-items: center; + gap: 4px; + flex-grow: 1; + + padding-left: ${({ theme }) => theme.spacing(2)}; + z-index: 1; +`; + +const StyledIcon = styled(IconChevronDown)` + align-items: center; + display: flex; + height: 20px; + justify-content: center; + flex-grow: 0; + weight: light; + padding-right: ${({ theme }) => theme.spacing(2)}; +`; + +export const RecordTableColumnFooterAggregateValue = ({ + dropdownId, + aggregateValue, + aggregateLabel, +}: { + dropdownId: string; + aggregateValue?: string | number | null; + aggregateLabel?: string; +}) => { + const [isHovered, setIsHovered] = useState(false); + const sanitizedId = `tooltip-${dropdownId.replace(/[^a-zA-Z0-9-_]/g, '-')}`; + const theme = useTheme(); + return ( +
{ + setIsHovered(true); + }} + onMouseLeave={() => setIsHovered(false)} + > + + {isHovered || isDefined(aggregateValue) ? ( + <> + + {aggregateValue ?? 'Calculate'} + + + {aggregateValue && ( + + )} + + ) : ( + <> + )} + +
+ ); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnFooterDropdown.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnFooterDropdown.tsx new file mode 100644 index 000000000000..b2489ddaa016 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnFooterDropdown.tsx @@ -0,0 +1,77 @@ +import { getAggregateOperationLabel } from '@/object-record/record-board/record-board-column/utils/getAggregateOperationLabel'; +import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; +import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; +import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext'; +import { getAvailableAggregateOperationsForFieldMetadataType } from '@/object-record/record-table/record-table-footer/utils/getAvailableAggregateOperationsForFieldMetadataType'; +import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition'; +import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope'; +import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; +import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; +import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; +import { usePersistViewFieldRecords } from '@/views/hooks/internal/usePersistViewFieldRecords'; +import { useGetCurrentView } from '@/views/hooks/useGetCurrentView'; +import { useContext, useMemo } from 'react'; +import { Key } from 'ts-key-enum'; +import { MenuItem } from 'twenty-ui'; + +export const RecordTableColumnFooterDropdown = ({ + column, +}: { + column: ColumnDefinition; +}) => { + const { closeDropdown } = useDropdown(column.fieldMetadataId + '-footer'); + const { objectMetadataItem } = useContext(RecordTableContext); + const { currentViewWithSavedFiltersAndSorts } = useGetCurrentView(); + + const currentViewField = + currentViewWithSavedFiltersAndSorts?.viewFields?.find( + (viewField) => viewField.fieldMetadataId === column.fieldMetadataId, + ); + + if (!currentViewField) { + throw new Error('View field not found'); + } + + useScopedHotkeys( + [Key.Escape], + () => { + closeDropdown(); + }, + TableOptionsHotkeyScope.Dropdown, + ); + + const availableAggregateOperations = useMemo( + () => + getAvailableAggregateOperationsForFieldMetadataType({ + fieldMetadataType: objectMetadataItem.fields.find( + (field) => field.id === column.fieldMetadataId, + )?.type, + }), + [column.fieldMetadataId, objectMetadataItem.fields], + ); + + const { updateViewFieldRecords } = usePersistViewFieldRecords(); + const handleAggregationChange = ( + aggregateOperation: AGGREGATE_OPERATIONS, + ) => { + updateViewFieldRecords([ + { ...currentViewField, aggregateOperation: aggregateOperation }, + ]); + }; + + return ( + <> + + {availableAggregateOperations.map((aggregation) => ( + { + handleAggregationChange(aggregation); + }} + text={getAggregateOperationLabel(aggregation)} + /> + ))} + + + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnFooterWithDropdown.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnFooterWithDropdown.tsx new file mode 100644 index 000000000000..2f175cd24040 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnFooterWithDropdown.tsx @@ -0,0 +1,68 @@ +import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; +import { RecordTableColumnFooterAggregateValue } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnFooterAggregateValue'; +import { RecordTableColumnFooterDropdown } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnFooterDropdown'; +import { useAggregateRecordsForRecordTableColumnFooter } from '@/object-record/record-table/record-table-footer/hooks/useAggregateRecordsForRecordTableColumnFooter'; +import { isScrollEnabledForRecordTableState } from '@/object-record/record-table/states/isScrollEnabledForRecordTableState'; +import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition'; +import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; +import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; +import styled from '@emotion/styled'; +import { useCallback } from 'react'; + +type RecordTableColumnFooterWithDropdownProps = { + column: ColumnDefinition +}; + +const StyledDropdown = styled(Dropdown)` + display: flex; + flex: 1; + z-index: ${({ theme }) => theme.lastLayerZIndex}; + + transition: opacity 150ms ease-in-out; +`; + +export const RecordTableColumnFooterWithDropdown = ({ + column, +}: RecordTableColumnFooterWithDropdownProps) => { + const setIsScrollEnabledForRecordTable = useSetRecoilComponentStateV2( + isScrollEnabledForRecordTableState, + ); + + const handleDropdownOpen = useCallback(() => { + setIsScrollEnabledForRecordTable({ + enableXScroll: false, + enableYScroll: false, + }); + }, [setIsScrollEnabledForRecordTable]); + + const handleDropdownClose = useCallback(() => { + setIsScrollEnabledForRecordTable({ + enableXScroll: true, + enableYScroll: true, + }); + }, [setIsScrollEnabledForRecordTable]); + + const { aggregateValue, aggregateLabel } = + useAggregateRecordsForRecordTableColumnFooter(column.fieldMetadataId); + + const dropdownId = column.fieldMetadataId + '-footer'; + + return ( + + } + dropdownComponents={} + dropdownOffset={{ x: -1 }} + dropdownPlacement="bottom-start" + dropdownHotkeyScope={{ scope: dropdownId }} + /> + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableFooter.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableFooter.tsx new file mode 100644 index 000000000000..cbd8026978f5 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableFooter.tsx @@ -0,0 +1,93 @@ +import styled from '@emotion/styled'; +import { MOBILE_VIEWPORT } from 'twenty-ui'; + +import { RecordTableFooterCell } from '@/object-record/record-table/record-table-footer/components/RecordTableFooterCell'; +import { RecordTableHeaderDragDropColumn } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderDragDropColumn'; +import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; + +const StyledTableHead = styled.thead` + cursor: pointer; + + th:nth-of-type(1) { + width: 9px; + left: 0; + border-right-color: ${({ theme }) => theme.background.primary}; + } + + th:nth-of-type(2) { + border-right-color: ${({ theme }) => theme.background.primary}; + } + + &.first-columns-sticky { + th:nth-of-type(1) { + position: sticky; + left: 0; + z-index: 5; + transition: 0.3s ease; + } + + th:nth-of-type(2) { + position: sticky; + left: 11px; + z-index: 5; + transition: 0.3s ease; + } + + th:nth-of-type(3) { + position: sticky; + left: 43px; + z-index: 5; + transition: 0.3s ease; + + &::after { + content: ''; + position: absolute; + top: -1px; + height: calc(100% + 2px); + width: 4px; + right: 0px; + box-shadow: ${({ theme }) => theme.boxShadow.light}; + clip-path: inset(0px -4px 0px 0px); + } + + @media (max-width: ${MOBILE_VIEWPORT}px) { + width: 34px; + max-width: 34px; + } + } + } + + &.header-sticky { + th { + position: sticky; + top: 0; + z-index: 5; + } + } + + &.header-sticky.first-columns-sticky { + th:nth-of-type(1), + th:nth-of-type(2), + th:nth-of-type(3) { + z-index: 10; + } + } +`; + +export const RecordTableFooter = () => { + const visibleTableColumns = useRecoilComponentValueV2( + visibleTableColumnsComponentSelector, + ); + + return ( + + + + {visibleTableColumns.map((column) => ( + + ))} + + + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableFooterCell.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableFooterCell.tsx new file mode 100644 index 000000000000..1ae3e6d1620d --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableFooterCell.tsx @@ -0,0 +1,90 @@ +import styled from '@emotion/styled'; +import { useMemo } from 'react'; + +import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; +import { RecordTableColumnFooterWithDropdown } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnFooterWithDropdown'; +import { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState'; +import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { mapArrayToObject } from '~/utils/array/mapArrayToObject'; + +const COLUMN_MIN_WIDTH = 104; + +const StyledColumnFooterCell = styled.th<{ + columnWidth: number; + isResizing?: boolean; +}>` + color: ${({ theme }) => theme.font.color.tertiary}; + padding: 0; + text-align: left; + transition: 0.3s ease; + + background-color: ${({ theme }) => theme.background.primary}; + ${({ columnWidth }) => ` + min-width: ${columnWidth}px; + width: ${columnWidth}px; + `} + position: relative; + user-select: none; + ${({ theme }) => { + return ` + &:hover { + background: ${theme.background.secondary}; + }; + &:active { + background: ${theme.background.tertiary}; + }; + `; + }}; + ${({ isResizing, theme }) => { + if (isResizing === true) { + return `&:after { + background-color: ${theme.color.blue}; + bottom: 0; + content: ''; + display: block; + position: absolute; + right: -1px; + top: 0; + width: 2px; + }`; + } + }}; + + // TODO: refactor this, each component should own its CSS + overflow: auto; +`; + +const StyledColumnFootContainer = styled.div` + position: relative; + z-index: 1; + width: 100%; + overflow: auto; +`; + +export const RecordTableFooterCell = ({ + column, +}: { + column: ColumnDefinition; +}) => { + const tableColumns = useRecoilComponentValueV2(tableColumnsComponentState); + const tableColumnsByKey = useMemo( + () => + mapArrayToObject(tableColumns, ({ fieldMetadataId }) => fieldMetadataId), + [tableColumns], + ); + + return ( + + + + + + ); +}; From 05197af9d928a61b668106fdba63df95078ef2bc Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Wed, 11 Dec 2024 18:33:38 +0100 Subject: [PATCH 09/11] Fix sticky first column --- .../record-table-footer/components/RecordTableFooter.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableFooter.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableFooter.tsx index cbd8026978f5..32b69d3c55e7 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableFooter.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableFooter.tsx @@ -75,6 +75,10 @@ const StyledTableHead = styled.thead` } `; +const StyledDiv = styled.div` +width: 30px +`; + export const RecordTableFooter = () => { const visibleTableColumns = useRecoilComponentValueV2( visibleTableColumnsComponentSelector, @@ -84,6 +88,7 @@ export const RecordTableFooter = () => { + {visibleTableColumns.map((column) => ( ))} From d1b2f1d454a8c65b80aa5240d2575aa79a057d26 Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Thu, 12 Dec 2024 12:01:30 +0100 Subject: [PATCH 10/11] Improve code quality --- ...useAggregateRecordsForRecordBoardColumn.ts | 4 +-- ...dGqlFieldsAggregateForRecordBoard.test.ts} | 10 +++--- ...RecordGqlFieldsAggregateForRecordBoard.ts} | 2 +- .../RecordTableColumnFooterAggregateValue.tsx | 13 +++++--- .../RecordTableColumnFooterDropdown.tsx | 2 +- .../components/RecordTableFooter.tsx | 11 +++---- .../components/RecordTableFooterCell.tsx | 1 - ...egateRecordsForRecordTableColumnFooter.tsx | 2 +- ...RecordGqlFieldsAggregateForRecordTable.ts} | 2 +- ...AggregateOperationsForFieldMetadataType.ts | 33 +++++++++++++++++++ ...ggregateOperationsForFieldMetadataType.tsx | 30 ----------------- .../utils/generateAggregateQuery.ts | 2 +- 12 files changed, 58 insertions(+), 54 deletions(-) rename packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/__tests__/{buildRecordGqlFieldsAggregate.test.ts => buildRecordGqlFieldsAggregateForRecordBoard.test.ts} (88%) rename packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/{buildRecordGqlFieldsAggregate.ts => buildRecordGqlFieldsAggregateForRecordBoard.ts} (96%) rename packages/twenty-front/src/modules/object-record/record-table/record-table-footer/utils/{buildRecordGqlFieldsAggregate.ts => buildRecordGqlFieldsAggregateForRecordTable.ts} (87%) create mode 100644 packages/twenty-front/src/modules/object-record/record-table/record-table-footer/utils/getAvailableAggregateOperationsForFieldMetadataType.ts delete mode 100644 packages/twenty-front/src/modules/object-record/record-table/record-table-footer/utils/getAvailableAggregateOperationsForFieldMetadataType.tsx diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/hooks/useAggregateRecordsForRecordBoardColumn.ts b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/hooks/useAggregateRecordsForRecordBoardColumn.ts index ad37fe530ff6..f3da6c261dcc 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/hooks/useAggregateRecordsForRecordBoardColumn.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/hooks/useAggregateRecordsForRecordBoardColumn.ts @@ -1,7 +1,7 @@ import { useAggregateRecords } from '@/object-record/hooks/useAggregateRecords'; import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext'; import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext'; -import { buildRecordGqlFieldsAggregate } from '@/object-record/record-board/record-board-column/utils/buildRecordGqlFieldsAggregate'; +import { buildRecordGqlFieldsAggregateForRecordBoard } from '@/object-record/record-board/record-board-column/utils/buildRecordGqlFieldsAggregateForRecordBoard'; import { computeAggregateValueAndLabel } from '@/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel'; import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter'; import { recordIndexFiltersState } from '@/object-record/record-index/states/recordIndexFiltersState'; @@ -42,7 +42,7 @@ export const useAggregateRecordsForRecordBoardColumn = () => { ); } - const recordGqlFieldsAggregate = buildRecordGqlFieldsAggregate({ + const recordGqlFieldsAggregate = buildRecordGqlFieldsAggregateForRecordBoard({ objectMetadataItem: objectMetadataItem, recordIndexKanbanAggregateOperation: recordIndexKanbanAggregateOperation, kanbanFieldName: kanbanFieldName, diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/__tests__/buildRecordGqlFieldsAggregate.test.ts b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/__tests__/buildRecordGqlFieldsAggregateForRecordBoard.test.ts similarity index 88% rename from packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/__tests__/buildRecordGqlFieldsAggregate.test.ts rename to packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/__tests__/buildRecordGqlFieldsAggregateForRecordBoard.test.ts index 85e4c5994e8a..2afbf90e04a2 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/__tests__/buildRecordGqlFieldsAggregate.test.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/__tests__/buildRecordGqlFieldsAggregateForRecordBoard.test.ts @@ -1,6 +1,6 @@ import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { buildRecordGqlFieldsAggregate } from '@/object-record/record-board/record-board-column/utils/buildRecordGqlFieldsAggregate'; +import { buildRecordGqlFieldsAggregateForRecordBoard } from '@/object-record/record-board/record-board-column/utils/buildRecordGqlFieldsAggregateForRecordBoard'; import { KanbanAggregateOperation } from '@/object-record/record-index/states/recordIndexKanbanAggregateOperationState'; import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { FieldMetadataType } from '~/generated-metadata/graphql'; @@ -8,7 +8,7 @@ import { FieldMetadataType } from '~/generated-metadata/graphql'; const MOCK_FIELD_ID = '7d2d7b5e-7b3e-4b4a-8b0a-7b3e4b4a8b0a'; const MOCK_KANBAN_FIELD = 'stage'; -describe('buildRecordGqlFieldsAggregate', () => { +describe('buildRecordGqlFieldsAggregateForRecordBoard', () => { const mockObjectMetadata: ObjectMetadataItem = { id: '123', nameSingular: 'opportunity', @@ -50,7 +50,7 @@ describe('buildRecordGqlFieldsAggregate', () => { operation: AGGREGATE_OPERATIONS.sum, }; - const result = buildRecordGqlFieldsAggregate({ + const result = buildRecordGqlFieldsAggregateForRecordBoard({ objectMetadataItem: mockObjectMetadata, recordIndexKanbanAggregateOperation: kanbanAggregateOperation, kanbanFieldName: MOCK_KANBAN_FIELD, @@ -67,7 +67,7 @@ describe('buildRecordGqlFieldsAggregate', () => { operation: AGGREGATE_OPERATIONS.count, }; - const result = buildRecordGqlFieldsAggregate({ + const result = buildRecordGqlFieldsAggregateForRecordBoard({ objectMetadataItem: mockObjectMetadata, recordIndexKanbanAggregateOperation: operation, kanbanFieldName: MOCK_KANBAN_FIELD, @@ -85,7 +85,7 @@ describe('buildRecordGqlFieldsAggregate', () => { }; expect(() => - buildRecordGqlFieldsAggregate({ + buildRecordGqlFieldsAggregateForRecordBoard({ objectMetadataItem: mockObjectMetadata, recordIndexKanbanAggregateOperation: operation, kanbanFieldName: MOCK_KANBAN_FIELD, diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/buildRecordGqlFieldsAggregate.ts b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/buildRecordGqlFieldsAggregateForRecordBoard.ts similarity index 96% rename from packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/buildRecordGqlFieldsAggregate.ts rename to packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/buildRecordGqlFieldsAggregateForRecordBoard.ts index d759868f94b4..7a3f44a04b77 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/buildRecordGqlFieldsAggregate.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/buildRecordGqlFieldsAggregateForRecordBoard.ts @@ -4,7 +4,7 @@ import { KanbanAggregateOperation } from '@/object-record/record-index/states/re import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { isDefined } from '~/utils/isDefined'; -export const buildRecordGqlFieldsAggregate = ({ +export const buildRecordGqlFieldsAggregateForRecordBoard = ({ objectMetadataItem, recordIndexKanbanAggregateOperation, kanbanFieldName, diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnFooterAggregateValue.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnFooterAggregateValue.tsx index fd690ef55e8c..b3b1a77fc5b2 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnFooterAggregateValue.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnFooterAggregateValue.tsx @@ -1,10 +1,14 @@ import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { useState } from 'react'; -import { AppTooltip, IconChevronDown, TooltipDelay } from 'twenty-ui'; -import { isDefined } from '~/utils/isDefined'; +import { + AppTooltip, + IconChevronDown, + isDefined, + TooltipDelay, +} from 'twenty-ui'; -const StyledCell = styled.div<{ hideTitle?: boolean }>` +const StyledCell = styled.div` align-items: center; display: flex; flex-direction: row; @@ -39,7 +43,6 @@ const StyledIcon = styled(IconChevronDown)` height: 20px; justify-content: center; flex-grow: 0; - weight: light; padding-right: ${({ theme }) => theme.spacing(2)}; `; @@ -69,7 +72,7 @@ export const RecordTableColumnFooterAggregateValue = ({ {aggregateValue ?? 'Calculate'} - {aggregateValue && ( + {aggregateValue && isDefined(aggregateLabel) && ( { @@ -85,14 +84,14 @@ export const RecordTableFooter = () => { ); return ( - + - + {visibleTableColumns.map((column) => ( ))} - + ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableFooterCell.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableFooterCell.tsx index 1ae3e6d1620d..74064645ac5a 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableFooterCell.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableFooterCell.tsx @@ -59,7 +59,6 @@ const StyledColumnFootContainer = styled.div` position: relative; z-index: 1; width: 100%; - overflow: auto; `; export const RecordTableFooterCell = ({ diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/hooks/useAggregateRecordsForRecordTableColumnFooter.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/hooks/useAggregateRecordsForRecordTableColumnFooter.tsx index 041c9e0c223e..f677bfe6945d 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/hooks/useAggregateRecordsForRecordTableColumnFooter.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/hooks/useAggregateRecordsForRecordTableColumnFooter.tsx @@ -36,7 +36,7 @@ export const useAggregateRecordsForRecordTableColumnFooter = ( )?.id; if (!viewFieldId) { - throw new Error('View field not found') + throw new Error('ViewField not found'); } const aggregateOperationForViewField = useRecoilValue( diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/utils/buildRecordGqlFieldsAggregate.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/utils/buildRecordGqlFieldsAggregateForRecordTable.ts similarity index 87% rename from packages/twenty-front/src/modules/object-record/record-table/record-table-footer/utils/buildRecordGqlFieldsAggregate.ts rename to packages/twenty-front/src/modules/object-record/record-table/record-table-footer/utils/buildRecordGqlFieldsAggregateForRecordTable.ts index de0d70842e90..e9abb0077d19 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/utils/buildRecordGqlFieldsAggregate.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/utils/buildRecordGqlFieldsAggregateForRecordTable.ts @@ -1,7 +1,7 @@ import { RecordGqlFieldsAggregate } from '@/object-record/graphql/types/RecordGqlFieldsAggregate'; import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; -export const buildRecordGqlFieldsAggregate = ({ +export const buildRecordGqlFieldsAggregateForRecordTable = ({ aggregateOperation, fieldName, }: { diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/utils/getAvailableAggregateOperationsForFieldMetadataType.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/utils/getAvailableAggregateOperationsForFieldMetadataType.ts new file mode 100644 index 000000000000..c49fcc66b7c9 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/utils/getAvailableAggregateOperationsForFieldMetadataType.ts @@ -0,0 +1,33 @@ +import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; +import { FIELDS_AVAILABLE_BY_AGGREGATE_OPERATION } from '@/object-record/record-table/constants/FieldsAvailableByAggregateOperation'; +import { AggregateOperationsOmittingCount } from '@/object-record/types/AggregateOperationsOmittingCount'; +import { isFieldTypeValidForAggregateOperation } from '@/object-record/utils/isFieldTypeValidForAggregateOperation'; +import { FieldMetadataType } from '~/generated/graphql'; +import { isDefined } from '~/utils/isDefined'; + +export const getAvailableAggregateOperationsForFieldMetadataType = ({ + fieldMetadataType, +}: { + fieldMetadataType?: FieldMetadataType; +}) => { + const availableAggregateOperations = new Set([ + AGGREGATE_OPERATIONS.count, + ]); + + if (!isDefined(fieldMetadataType)) { + return Array.from(availableAggregateOperations); + } + + Object.keys(FIELDS_AVAILABLE_BY_AGGREGATE_OPERATION) + .filter((operation) => + isFieldTypeValidForAggregateOperation( + fieldMetadataType, + operation as AggregateOperationsOmittingCount, + ), + ) + .forEach((operation) => + availableAggregateOperations.add(operation as AGGREGATE_OPERATIONS), + ); + + return Array.from(availableAggregateOperations); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/utils/getAvailableAggregateOperationsForFieldMetadataType.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/utils/getAvailableAggregateOperationsForFieldMetadataType.tsx deleted file mode 100644 index c2b19d548b55..000000000000 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/utils/getAvailableAggregateOperationsForFieldMetadataType.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { AGGREGATE_OPERATIONS } from "@/object-record/record-table/constants/AggregateOperations"; -import { FIELDS_AVAILABLE_BY_AGGREGATE_OPERATION } from "@/object-record/record-table/constants/FieldsAvailableByAggregateOperation"; -import { AggregateOperationsOmittingCount } from "@/object-record/types/AggregateOperationsOmittingCount"; -import { isFieldTypeValidForAggregateOperation } from "@/object-record/utils/isFieldTypeValidForAggregateOperation"; -import { FieldMetadataType } from "~/generated/graphql"; -import { isDefined } from "~/utils/isDefined"; - -export const getAvailableAggregateOperationsForFieldMetadataType = ({fieldMetadataType}: {fieldMetadataType?: FieldMetadataType}) => { - let availableAggregateOperations: AGGREGATE_OPERATIONS[] = [AGGREGATE_OPERATIONS.count]; - - if (!isDefined(fieldMetadataType)) { - return availableAggregateOperations; - } - Object.keys(FIELDS_AVAILABLE_BY_AGGREGATE_OPERATION).forEach( - (aggregateOperation) => { - const typedAggregateOperation = - aggregateOperation as AggregateOperationsOmittingCount; - - if ( - isFieldTypeValidForAggregateOperation( - fieldMetadataType, - typedAggregateOperation, - ) - ) { - availableAggregateOperations.push(typedAggregateOperation); - } - }, - ); - return availableAggregateOperations; -}; diff --git a/packages/twenty-front/src/modules/object-record/utils/generateAggregateQuery.ts b/packages/twenty-front/src/modules/object-record/utils/generateAggregateQuery.ts index f2e4c2871363..02e077ded135 100644 --- a/packages/twenty-front/src/modules/object-record/utils/generateAggregateQuery.ts +++ b/packages/twenty-front/src/modules/object-record/utils/generateAggregateQuery.ts @@ -22,7 +22,7 @@ export const generateAggregateQuery = ({ objectMetadataItem.nameSingular, )}FilterInput) { ${objectMetadataItem.namePlural}(filter: $filter) { - ${selectedFields ? '' : 'typename'} + ${selectedFields ? '' : '__typename'} ${selectedFields} } } From b24bcf9b3b67e034954350d69f64b4050b761af0 Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Thu, 12 Dec 2024 12:19:23 +0100 Subject: [PATCH 11/11] fix linter --- .../components/RecordTableColumnFooterWithDropdown.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnFooterWithDropdown.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnFooterWithDropdown.tsx index 2f175cd24040..f66111a978a7 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnFooterWithDropdown.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnFooterWithDropdown.tsx @@ -10,7 +10,7 @@ import styled from '@emotion/styled'; import { useCallback } from 'react'; type RecordTableColumnFooterWithDropdownProps = { - column: ColumnDefinition + column: ColumnDefinition; }; const StyledDropdown = styled(Dropdown)`