From 7e7837abf3b7d22c95731fd2823dd7eaf64da12e Mon Sep 17 00:00:00 2001 From: Lucas Bordeau Date: Wed, 3 Sep 2025 13:54:44 +0200 Subject: [PATCH] Fixed aggregate footer not displayed (#14276) This PR fixes a bug that made the aggregate footer not displayed. It was because of a wrong filter on `visibleRecordFields` array. Follow-up of https://github.com/twentyhq/twenty/pull/14048 --------- Co-authored-by: Charles Bochet --- ...mnHeaderAggregateDropdownFieldsContent.tsx | 1 + ...nHeaderAggregateDropdownOptionsContent.tsx | 3 ++- .../RecordTableAggregateFooterCell.tsx | 6 +++-- ...egateFooterAggregateOperationMenuItems.tsx | 8 +++--- ...dTableColumnAggregateFooterMenuContent.tsx | 4 +-- .../hooks/useViewFieldAggregateOperation.tsx | 13 +++++++-- .../__tests__/useUpdateViewAggregate.test.tsx | 6 +++++ .../views/hooks/useUpdateViewAggregate.ts | 15 ++++++++++- .../modules/views/hooks/useUpdateViewField.ts | 27 ------------------- 9 files changed, 44 insertions(+), 39 deletions(-) delete mode 100644 packages/twenty-front/src/modules/views/hooks/useUpdateViewField.ts diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownFieldsContent.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownFieldsContent.tsx index ff85e52156..9909b403da 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownFieldsContent.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownFieldsContent.tsx @@ -78,6 +78,7 @@ export const RecordBoardColumnHeaderAggregateDropdownFieldsContent = () => { updateViewAggregate({ kanbanAggregateOperationFieldMetadataId: fieldId, kanbanAggregateOperation: aggregateOperation, + objectMetadataId: objectMetadataItem.id, }); closeDropdown(); }} diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownOptionsContent.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownOptionsContent.tsx index 2cc4a7306a..326171c6dd 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownOptionsContent.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownOptionsContent.tsx @@ -28,7 +28,7 @@ export const RecordBoardColumnHeaderAggregateDropdownOptionsContent = ({ availableAggregations: AvailableFieldsForAggregateOperation; title: string; }) => { - const { onContentChange, closeDropdown, resetContent } = + const { onContentChange, closeDropdown, resetContent, objectMetadataItem } = useDropdownContextStateManagement( { context: RecordBoardColumnHeaderAggregateDropdownContext, @@ -89,6 +89,7 @@ export const RecordBoardColumnHeaderAggregateDropdownOptionsContent = ({ availableAggregationFieldsIdsForOperation[0], kanbanAggregateOperation: availableAggregationOperation as AggregateOperations, + objectMetadataId: objectMetadataItem.id, }); closeDropdown(); } diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableAggregateFooterCell.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableAggregateFooterCell.tsx index 88ab0ed589..fb14e6b233 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableAggregateFooterCell.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableAggregateFooterCell.tsx @@ -4,7 +4,7 @@ import { useContext } from 'react'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; import { RecordTableColumnAggregateFooterCellContext } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterCellContext'; import { RecordTableColumnFooterWithDropdown } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterWithDropdown'; -import { findById, isDefined } from 'twenty-shared/utils'; +import { findByProperty, isDefined } from 'twenty-shared/utils'; const COLUMN_MIN_WIDTH = 104; @@ -64,7 +64,9 @@ export const RecordTableAggregateFooterCell = ({ RecordTableColumnAggregateFooterCellContext, ); - const recordField = visibleRecordFields.find(findById(fieldMetadataId)); + const recordField = visibleRecordFields.find( + findByProperty('fieldMetadataItemId', fieldMetadataId), + ); if (!isDefined(recordField)) { return null; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterAggregateOperationMenuItems.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterAggregateOperationMenuItems.tsx index 3a74f34694..1d40fa3e5b 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterAggregateOperationMenuItems.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterAggregateOperationMenuItems.tsx @@ -30,8 +30,8 @@ export const RecordTableColumnAggregateFooterAggregateOperationMenuItems = ({ {aggregateOperations.map((operation) => ( { - updateViewFieldAggregateOperation(operation); + onClick={async () => { + await updateViewFieldAggregateOperation(operation); closeDropdown(dropdownId); }} text={getAggregateOperationLabel(operation)} @@ -46,8 +46,8 @@ export const RecordTableColumnAggregateFooterAggregateOperationMenuItems = ({ {children} { - updateViewFieldAggregateOperation(null); + onClick={async () => { + await updateViewFieldAggregateOperation(null); resetContent(); closeDropdown(dropdownId); }} diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterMenuContent.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterMenuContent.tsx index c2abe56fe6..b8e3cb002b 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterMenuContent.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterMenuContent.tsx @@ -92,8 +92,8 @@ export const RecordTableColumnAggregateFooterMenuContent = () => { ) : null} { - updateViewFieldAggregateOperation(null); + onClick={async () => { + await updateViewFieldAggregateOperation(null); resetContent(); closeDropdown(dropdownId); }} diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/hooks/useViewFieldAggregateOperation.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/hooks/useViewFieldAggregateOperation.tsx index 83418110c7..603f13bcaf 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/hooks/useViewFieldAggregateOperation.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/hooks/useViewFieldAggregateOperation.tsx @@ -1,9 +1,11 @@ +import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext'; import { RecordTableColumnAggregateFooterDropdownContext } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterDropdownContext'; import { viewFieldAggregateOperationState } from '@/object-record/record-table/record-table-footer/states/viewFieldAggregateOperationState'; import { type ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations'; import { convertExtendedAggregateOperationToAggregateOperation } from '@/object-record/utils/convertExtendedAggregateOperationToAggregateOperation'; import { usePersistViewFieldRecords } from '@/views/hooks/internal/usePersistViewFieldRecords'; import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly'; +import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId'; import { useContext } from 'react'; import { useRecoilValue } from 'recoil'; @@ -16,14 +18,19 @@ export const useViewFieldAggregateOperation = () => { const currentViewField = currentView?.viewFields?.find( (viewField) => viewField.fieldMetadataId === fieldMetadataId, ); + + const { refreshCoreViewsByObjectMetadataId } = + useRefreshCoreViewsByObjectMetadataId(); + + const { objectMetadataItem } = useRecordIndexContextOrThrow(); const { updateViewFieldRecords } = usePersistViewFieldRecords(); - const updateViewFieldAggregateOperation = ( + const updateViewFieldAggregateOperation = async ( aggregateOperation: ExtendedAggregateOperations | null, ) => { if (!currentViewField) { throw new Error('ViewField not found'); } - updateViewFieldRecords([ + await updateViewFieldRecords([ { ...currentViewField, aggregateOperation: @@ -32,6 +39,8 @@ export const useViewFieldAggregateOperation = () => { ), }, ]); + + refreshCoreViewsByObjectMetadataId(objectMetadataItem.id); }; const currentViewFieldAggregateOperation = useRecoilValue( diff --git a/packages/twenty-front/src/modules/views/hooks/__tests__/useUpdateViewAggregate.test.tsx b/packages/twenty-front/src/modules/views/hooks/__tests__/useUpdateViewAggregate.test.tsx index b3d4e069c1..9ba8796c04 100644 --- a/packages/twenty-front/src/modules/views/hooks/__tests__/useUpdateViewAggregate.test.tsx +++ b/packages/twenty-front/src/modules/views/hooks/__tests__/useUpdateViewAggregate.test.tsx @@ -5,9 +5,11 @@ import { useUpdateView } from '@/views/hooks/useUpdateView'; import { renderHook } from '@testing-library/react'; import { useSetRecoilState } from 'recoil'; import { useUpdateViewAggregate } from '../useUpdateViewAggregate'; +import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId'; jest.mock('@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'); jest.mock('@/views/hooks/useUpdateView'); +jest.mock('@/views/hooks/useRefreshCoreViewsByObjectMetadataId'); jest.mock('recoil'); describe('useUpdateViewAggregate', () => { @@ -24,6 +26,9 @@ describe('useUpdateViewAggregate', () => { (useSetRecoilState as jest.Mock).mockReturnValue( mockSetRecordIndexKanbanAggregateOperationState, ); + (useRefreshCoreViewsByObjectMetadataId as jest.Mock).mockReturnValue({ + refreshCoreViewsByObjectMetadataId: jest.fn(), + }); }); describe('Aggregate operations on dates', () => { @@ -33,6 +38,7 @@ describe('useUpdateViewAggregate', () => { result.current.updateViewAggregate({ kanbanAggregateOperationFieldMetadataId: 'test-field-id', kanbanAggregateOperation: DateAggregateOperations.EARLIEST, + objectMetadataId: 'test-object-metadata-id', }); // updateView is called with 'EARLIEST' converted to 'MIN' diff --git a/packages/twenty-front/src/modules/views/hooks/useUpdateViewAggregate.ts b/packages/twenty-front/src/modules/views/hooks/useUpdateViewAggregate.ts index a199dcc382..2976173b4c 100644 --- a/packages/twenty-front/src/modules/views/hooks/useUpdateViewAggregate.ts +++ b/packages/twenty-front/src/modules/views/hooks/useUpdateViewAggregate.ts @@ -3,6 +3,7 @@ import { recordIndexKanbanAggregateOperationState } from '@/object-record/record import { type ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations'; import { convertExtendedAggregateOperationToAggregateOperation } from '@/object-record/utils/convertExtendedAggregateOperationToAggregateOperation'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId'; import { useUpdateView } from '@/views/hooks/useUpdateView'; import { useCallback } from 'react'; import { useSetRecoilState } from 'recoil'; @@ -17,13 +18,18 @@ export const useUpdateViewAggregate = () => { recordIndexKanbanAggregateOperationState, ); + const { refreshCoreViewsByObjectMetadataId } = + useRefreshCoreViewsByObjectMetadataId(); + const updateViewAggregate = useCallback( ({ kanbanAggregateOperationFieldMetadataId, kanbanAggregateOperation, + objectMetadataId, }: { kanbanAggregateOperationFieldMetadataId: string | null; kanbanAggregateOperation: ExtendedAggregateOperations | null; + objectMetadataId: string; }) => { const convertedKanbanAggregateOperation = convertExtendedAggregateOperationToAggregateOperation( @@ -39,8 +45,15 @@ export const useUpdateViewAggregate = () => { operation: kanbanAggregateOperation, fieldMetadataId: kanbanAggregateOperationFieldMetadataId, }); + + refreshCoreViewsByObjectMetadataId(objectMetadataId); }, - [currentViewId, updateView, setRecordIndexKanbanAggregateOperationState], + [ + currentViewId, + updateView, + setRecordIndexKanbanAggregateOperationState, + refreshCoreViewsByObjectMetadataId, + ], ); return { diff --git a/packages/twenty-front/src/modules/views/hooks/useUpdateViewField.ts b/packages/twenty-front/src/modules/views/hooks/useUpdateViewField.ts deleted file mode 100644 index a6213f3b76..0000000000 --- a/packages/twenty-front/src/modules/views/hooks/useUpdateViewField.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; -import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; -import { type ViewField } from '@/views/types/ViewField'; -import { useRecoilCallback } from 'recoil'; -import { isDefined } from 'twenty-shared/utils'; - -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, - }; -};