(
+ {} as RecordTableColumnAggregateFooterCellValue,
+ );
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterValue.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterValue.tsx
index 800f551de..1675620c4 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterValue.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterValue.tsx
@@ -1,23 +1,6 @@
-import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
-import { useTheme } from '@emotion/react';
+import { useAggregateRecordsForRecordTableColumnFooter } from '@/object-record/record-table/record-table-footer/hooks/useAggregateRecordsForRecordTableColumnFooter';
import styled from '@emotion/styled';
-import { useState } from 'react';
-import { IconChevronDown, isDefined } from 'twenty-ui';
-
-const StyledCell = styled.div`
- 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%;
-`;
+import { isDefined } from 'twenty-ui';
const StyledText = styled.span`
overflow: hidden;
@@ -54,56 +37,34 @@ const StyledValue = styled.div`
flex: 1 0 0;
`;
-const StyledIcon = styled(IconChevronDown)`
- align-items: center;
- display: flex;
- height: 20px;
- justify-content: center;
- flex-grow: 0;
- padding-right: ${({ theme }) => theme.spacing(2)};
-`;
-
export const RecordTableColumnAggregateFooterValue = ({
dropdownId,
- aggregateValue,
- aggregateLabel,
- isFirstCell,
+ fieldMetadataId,
}: {
dropdownId: string;
- isFirstCell: boolean;
- aggregateValue?: string | number | null;
- aggregateLabel?: string;
+ fieldMetadataId: string;
}) => {
- const [isHovered, setIsHovered] = useState(false);
- const { isDropdownOpen } = useDropdown(dropdownId);
const sanitizedId = `tooltip-${dropdownId.replace(/[^a-zA-Z0-9-_]/g, '-')}`;
- const theme = useTheme();
- const shouldShowValue =
- isHovered || isDropdownOpen || isDefined(aggregateValue) || isFirstCell;
+
+ const { aggregateValue, aggregateLabel, isLoading } =
+ useAggregateRecordsForRecordTableColumnFooter(fieldMetadataId);
+
return (
- {
- setIsHovered(true);
- }}
- onMouseLeave={() => setIsHovered(false)}
- >
-
- {shouldShowValue ? (
- <>
- {isDefined(aggregateValue) ? (
-
- {aggregateLabel}
- {aggregateValue}
-
- ) : (
- Calculate
- )}
-
- >
- ) : (
- <>>
- )}
-
-
+ <>
+ {isDefined(aggregateValue) || isLoading ? (
+
+ {isLoading ? (
+ <>>
+ ) : (
+ <>
+ {aggregateLabel}
+ {aggregateValue}
+ >
+ )}
+
+ ) : (
+ Calculate
+ )}
+ >
);
};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterValueCell.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterValueCell.tsx
new file mode 100644
index 000000000..151da5908
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterValueCell.tsx
@@ -0,0 +1,80 @@
+import { RecordTableColumnAggregateFooterCellContext } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterCellContext';
+import { RecordTableColumnAggregateFooterValue } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterValue';
+import { hasAggregateOperationForViewFieldFamilySelector } from '@/object-record/record-table/record-table-footer/states/hasAggregateOperationForViewFieldFamilySelector';
+import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
+import { useTheme } from '@emotion/react';
+import styled from '@emotion/styled';
+import { useContext, useState } from 'react';
+import { useRecoilValue } from 'recoil';
+import { IconChevronDown } from 'twenty-ui';
+
+const StyledCell = styled.div`
+ 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 StyledIcon = styled(IconChevronDown)`
+ align-items: center;
+ display: flex;
+ height: 20px;
+ justify-content: center;
+ flex-grow: 0;
+ padding-right: ${({ theme }) => theme.spacing(2)};
+`;
+
+export const RecordTableColumnAggregateFooterValueCell = ({
+ dropdownId,
+ isFirstCell,
+}: {
+ dropdownId: string;
+ isFirstCell: boolean;
+}) => {
+ const [isHovered, setIsHovered] = useState(false);
+ const { isDropdownOpen } = useDropdown(dropdownId);
+ const theme = useTheme();
+ const { viewFieldId, fieldMetadataId } = useContext(
+ RecordTableColumnAggregateFooterCellContext,
+ );
+
+ const hasAggregateOperationForViewField = useRecoilValue(
+ hasAggregateOperationForViewFieldFamilySelector({
+ viewFieldId,
+ }),
+ );
+
+ return (
+ {
+ setIsHovered(true);
+ }}
+ onMouseLeave={() => setIsHovered(false)}
+ >
+
+ {isHovered ||
+ isDropdownOpen ||
+ hasAggregateOperationForViewField ||
+ isFirstCell ? (
+ <>
+
+
+ >
+ ) : (
+ <>>
+ )}
+
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterWithDropdown.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterWithDropdown.tsx
index d205d21b5..a0062384c 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterWithDropdown.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterWithDropdown.tsx
@@ -1,29 +1,29 @@
import { useCurrentContentId } from '@/dropdown/hooks/useCurrentContentId';
-import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
+import { RecordTableColumnAggregateFooterCellContext } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterCellContext';
import { RecordTableColumnAggregateFooterDropdownContent } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterDropdownContent';
import { RecordTableColumnAggregateFooterDropdownContext } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterDropdownContext';
-import { RecordTableColumnAggregateFooterValue } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterValue';
-import { useAggregateRecordsForRecordTableColumnFooter } from '@/object-record/record-table/record-table-footer/hooks/useAggregateRecordsForRecordTableColumnFooter';
+import { RecordTableColumnAggregateFooterValueCell } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterValueCell';
import { RecordTableFooterAggregateContentId } from '@/object-record/record-table/record-table-footer/types/RecordTableFooterAggregateContentId';
-import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { useToggleScrollWrapper } from '@/ui/utilities/scroll/hooks/useToggleScrollWrapper';
-import { useCallback } from 'react';
+import { useCallback, useContext } from 'react';
type RecordTableColumnFooterWithDropdownProps = {
- column: ColumnDefinition;
isFirstCell: boolean;
currentRecordGroupId?: string;
};
export const RecordTableColumnFooterWithDropdown = ({
- column,
currentRecordGroupId,
isFirstCell,
}: RecordTableColumnFooterWithDropdownProps) => {
const { currentContentId, handleContentChange, handleResetContent } =
useCurrentContentId();
+ const { fieldMetadataId } = useContext(
+ RecordTableColumnAggregateFooterCellContext,
+ );
+
const { toggleScrollXWrapper, toggleScrollYWrapper } =
useToggleScrollWrapper();
@@ -38,12 +38,9 @@ export const RecordTableColumnFooterWithDropdown = ({
toggleScrollYWrapper(true);
}, [handleResetContent, toggleScrollXWrapper, toggleScrollYWrapper]);
- const { aggregateValue, aggregateLabel } =
- useAggregateRecordsForRecordTableColumnFooter(column.fieldMetadataId);
-
const dropdownId = currentRecordGroupId
- ? `${column.fieldMetadataId}-footer-${currentRecordGroupId}`
- : `${column.fieldMetadataId}-footer`;
+ ? `${fieldMetadataId}-footer-${currentRecordGroupId}`
+ : `${fieldMetadataId}-footer`;
return (
@@ -65,7 +60,7 @@ export const RecordTableColumnFooterWithDropdown = ({
onContentChange: handleContentChange,
resetContent: handleResetContent,
dropdownId: dropdownId,
- fieldMetadataId: column.fieldMetadataId,
+ fieldMetadataId: fieldMetadataId,
}}
>
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 d7c66b3e6..03b774545 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
@@ -6,9 +6,10 @@ import { useRecordGroupFilter } from '@/object-record/record-group/hooks/useReco
import { recordIndexFiltersState } from '@/object-record/record-index/states/recordIndexFiltersState';
import { recordIndexViewFilterGroupsState } from '@/object-record/record-index/states/recordIndexViewFilterGroupsState';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
+import { RecordTableColumnAggregateFooterCellContext } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterCellContext';
import { viewFieldAggregateOperationState } from '@/object-record/record-table/record-table-footer/states/viewFieldAggregateOperationState';
-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';
@@ -21,7 +22,6 @@ export const useAggregateRecordsForRecordTableColumnFooter = (
const { objectMetadataItem } = useRecordTableContextOrThrow();
const { recordGroupFilter } = useRecordGroupFilter(objectMetadataItem.fields);
- const { currentViewWithSavedFiltersAndSorts } = useGetCurrentView();
const recordIndexViewFilterGroups = useRecoilValue(
recordIndexViewFilterGroupsState,
);
@@ -37,13 +37,11 @@ export const useAggregateRecordsForRecordTableColumnFooter = (
recordIndexViewFilterGroups,
);
- const viewFieldId =
- currentViewWithSavedFiltersAndSorts?.viewFields?.find(
- (viewField) => viewField.fieldMetadataId === fieldMetadataId,
- )?.id ?? '';
-
+ const { viewFieldId } = useContext(
+ RecordTableColumnAggregateFooterCellContext,
+ );
const aggregateOperationForViewField = useRecoilValue(
- viewFieldAggregateOperationState({ viewFieldId: viewFieldId }),
+ viewFieldAggregateOperationState({ viewFieldId }),
);
const fieldName = objectMetadataItem.fields.find(
@@ -57,7 +55,7 @@ export const useAggregateRecordsForRecordTableColumnFooter = (
}
: {};
- const { data } = useAggregateRecords({
+ const { data, loading } = useAggregateRecords({
objectNameSingular: objectMetadataItem.nameSingular,
recordGqlFieldsAggregate,
filter: { ...requestFilters, ...recordGroupFilter },
@@ -75,5 +73,6 @@ export const useAggregateRecordsForRecordTableColumnFooter = (
return {
aggregateValue: value,
aggregateLabel: isDefined(value) ? label : undefined,
+ isLoading: loading,
};
};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/states/hasAggregateOperationForViewFieldFamilySelector.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/states/hasAggregateOperationForViewFieldFamilySelector.ts
new file mode 100644
index 000000000..4b7884046
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/states/hasAggregateOperationForViewFieldFamilySelector.ts
@@ -0,0 +1,21 @@
+import { viewFieldAggregateOperationState } from '@/object-record/record-table/record-table-footer/states/viewFieldAggregateOperationState';
+import { selectorFamily } from 'recoil';
+import { isDefined } from '~/utils/isDefined';
+
+export const hasAggregateOperationForViewFieldFamilySelector = selectorFamily<
+ boolean,
+ { viewFieldId: string }
+>({
+ key: 'hasAggregateOperationForViewField',
+ get:
+ ({ viewFieldId }) =>
+ ({ get }) => {
+ const aggregateOperation = get(
+ viewFieldAggregateOperationState({
+ viewFieldId,
+ }),
+ );
+
+ return isDefined(aggregateOperation);
+ },
+});