Add aggregate on view groups headers (#9749)
Adding aggregate operations on view groups headers, with a design similar to what is done on kanban headers: all view groups share the same operation on the same field. https://github.com/user-attachments/assets/26f6dd6f-1cf7-4ea6-9600-78d5ad5d690a
This commit is contained in:
@ -10,7 +10,7 @@ const globalCoverage = {
|
|||||||
const modulesCoverage = {
|
const modulesCoverage = {
|
||||||
branches: 25,
|
branches: 25,
|
||||||
statements: 44,
|
statements: 44,
|
||||||
lines: 45,
|
lines: 44,
|
||||||
functions: 38,
|
functions: 38,
|
||||||
include: ['src/modules/**/*'],
|
include: ['src/modules/**/*'],
|
||||||
exclude: ['src/**/*.ts'],
|
exclude: ['src/**/*.ts'],
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/StyledHeaderDropdownButton';
|
||||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { AppTooltip, Tag, TooltipDelay } from 'twenty-ui';
|
import { AppTooltip, Tag, TooltipDelay } from 'twenty-ui';
|
||||||
@ -6,7 +7,7 @@ const StyledTag = styled(Tag)`
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
const StyledHeader = styled(StyledHeaderDropdownButton)`
|
||||||
padding: 0;
|
padding: 0;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -22,8 +23,8 @@ export const RecordBoardColumnHeaderAggregateDropdownButton = ({
|
|||||||
const { isDropdownOpen } = useDropdown(dropdownId);
|
const { isDropdownOpen } = useDropdown(dropdownId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id={dropdownId}>
|
<StyledHeader id={dropdownId} isUnfolded={isDropdownOpen}>
|
||||||
<StyledContainer>
|
<>
|
||||||
<StyledTag
|
<StyledTag
|
||||||
text={value ? value.toString() : '-'}
|
text={value ? value.toString() : '-'}
|
||||||
color={'transparent'}
|
color={'transparent'}
|
||||||
@ -39,7 +40,7 @@ export const RecordBoardColumnHeaderAggregateDropdownButton = ({
|
|||||||
delay={TooltipDelay.mediumDelay}
|
delay={TooltipDelay.mediumDelay}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</StyledContainer>
|
</>
|
||||||
</div>
|
</StyledHeader>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,28 +1,14 @@
|
|||||||
import { useAggregateRecords } from '@/object-record/hooks/useAggregateRecords';
|
|
||||||
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
||||||
import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
|
import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
|
||||||
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 { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
|
||||||
import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter';
|
|
||||||
import { recordIndexFiltersState } from '@/object-record/record-index/states/recordIndexFiltersState';
|
|
||||||
import { recordIndexKanbanAggregateOperationState } from '@/object-record/record-index/states/recordIndexKanbanAggregateOperationState';
|
|
||||||
import { recordIndexKanbanFieldMetadataIdState } from '@/object-record/record-index/states/recordIndexKanbanFieldMetadataIdState';
|
import { recordIndexKanbanFieldMetadataIdState } from '@/object-record/record-index/states/recordIndexKanbanFieldMetadataIdState';
|
||||||
import { recordIndexViewFilterGroupsState } from '@/object-record/record-index/states/recordIndexViewFilterGroupsState';
|
import { useAggregateRecordsForHeader } from '@/object-record/record-table/hooks/useAggregateRecordsForHeader';
|
||||||
import { UserContext } from '@/users/contexts/UserContext';
|
|
||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
export const useAggregateRecordsForRecordBoardColumn = () => {
|
export const useAggregateRecordsForRecordBoardColumn = () => {
|
||||||
const { columnDefinition } = useContext(RecordBoardColumnContext);
|
const { columnDefinition } = useContext(RecordBoardColumnContext);
|
||||||
|
|
||||||
const { objectMetadataItem } = useContext(RecordBoardContext);
|
const { objectMetadataItem } = useContext(RecordBoardContext);
|
||||||
|
|
||||||
const recordIndexKanbanAggregateOperation = useRecoilValue(
|
|
||||||
recordIndexKanbanAggregateOperationState,
|
|
||||||
);
|
|
||||||
|
|
||||||
const recordIndexKanbanFieldMetadataId = useRecoilValue(
|
const recordIndexKanbanFieldMetadataId = useRecoilValue(
|
||||||
recordIndexKanbanFieldMetadataIdState,
|
recordIndexKanbanFieldMetadataIdState,
|
||||||
);
|
);
|
||||||
@ -37,56 +23,16 @@ export const useAggregateRecordsForRecordBoardColumn = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const recordGqlFieldsAggregate = buildRecordGqlFieldsAggregateForRecordBoard({
|
const additionalFilters = {
|
||||||
objectMetadataItem: objectMetadataItem,
|
|
||||||
recordIndexKanbanAggregateOperation: recordIndexKanbanAggregateOperation,
|
|
||||||
kanbanFieldName: kanbanFieldName,
|
|
||||||
});
|
|
||||||
|
|
||||||
const recordIndexViewFilterGroups = useRecoilValue(
|
|
||||||
recordIndexViewFilterGroupsState,
|
|
||||||
);
|
|
||||||
|
|
||||||
const recordIndexFilters = useRecoilValue(recordIndexFiltersState);
|
|
||||||
|
|
||||||
const { filterValueDependencies } = useFilterValueDependencies();
|
|
||||||
|
|
||||||
const requestFilters = computeViewRecordGqlOperationFilter(
|
|
||||||
filterValueDependencies,
|
|
||||||
recordIndexFilters,
|
|
||||||
objectMetadataItem.fields,
|
|
||||||
recordIndexViewFilterGroups,
|
|
||||||
);
|
|
||||||
|
|
||||||
const filter = {
|
|
||||||
...requestFilters,
|
|
||||||
[kanbanFieldName]:
|
[kanbanFieldName]:
|
||||||
columnDefinition.value === null
|
columnDefinition.value === null
|
||||||
? { is: 'NULL' }
|
? { is: 'NULL' }
|
||||||
: { eq: columnDefinition.value },
|
: { eq: columnDefinition.value },
|
||||||
};
|
};
|
||||||
|
|
||||||
const { data } = useAggregateRecords({
|
return useAggregateRecordsForHeader({
|
||||||
objectNameSingular: objectMetadataItem.nameSingular,
|
|
||||||
recordGqlFieldsAggregate,
|
|
||||||
filter,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { dateFormat, timeFormat, timeZone } = useContext(UserContext);
|
|
||||||
|
|
||||||
const { value, labelWithFieldName } = computeAggregateValueAndLabel({
|
|
||||||
data,
|
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
fieldMetadataId: recordIndexKanbanAggregateOperation?.fieldMetadataId,
|
additionalFilters,
|
||||||
aggregateOperation: recordIndexKanbanAggregateOperation?.operation,
|
|
||||||
fallbackFieldName: kanbanFieldName,
|
fallbackFieldName: kanbanFieldName,
|
||||||
dateFormat,
|
|
||||||
timeFormat,
|
|
||||||
timeZone,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
|
||||||
aggregateValue: value,
|
|
||||||
aggregateLabel: isDefined(value) ? labelWithFieldName : undefined,
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
import { buildRecordGqlFieldsAggregateForRecordBoard } from '@/object-record/record-board/record-board-column/utils/buildRecordGqlFieldsAggregateForRecordBoard';
|
import { buildRecordGqlFieldsAggregateForView } from '@/object-record/record-board/record-board-column/utils/buildRecordGqlFieldsAggregateForView';
|
||||||
import { KanbanAggregateOperation } from '@/object-record/record-index/states/recordIndexKanbanAggregateOperationState';
|
import { KanbanAggregateOperation } from '@/object-record/record-index/states/recordIndexKanbanAggregateOperationState';
|
||||||
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations';
|
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations';
|
||||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
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_FIELD_ID = '7d2d7b5e-7b3e-4b4a-8b0a-7b3e4b4a8b0a';
|
||||||
const MOCK_KANBAN_FIELD = 'stage';
|
const MOCK_KANBAN_FIELD = 'stage';
|
||||||
|
|
||||||
describe('buildRecordGqlFieldsAggregateForRecordBoard', () => {
|
describe('buildRecordGqlFieldsAggregateForView', () => {
|
||||||
const mockObjectMetadata: ObjectMetadataItem = {
|
const mockObjectMetadata: ObjectMetadataItem = {
|
||||||
id: '123',
|
id: '123',
|
||||||
nameSingular: 'opportunity',
|
nameSingular: 'opportunity',
|
||||||
@ -50,10 +50,10 @@ describe('buildRecordGqlFieldsAggregateForRecordBoard', () => {
|
|||||||
operation: AGGREGATE_OPERATIONS.sum,
|
operation: AGGREGATE_OPERATIONS.sum,
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = buildRecordGqlFieldsAggregateForRecordBoard({
|
const result = buildRecordGqlFieldsAggregateForView({
|
||||||
objectMetadataItem: mockObjectMetadata,
|
objectMetadataItem: mockObjectMetadata,
|
||||||
recordIndexKanbanAggregateOperation: kanbanAggregateOperation,
|
recordIndexKanbanAggregateOperation: kanbanAggregateOperation,
|
||||||
kanbanFieldName: MOCK_KANBAN_FIELD,
|
fieldNameForCount: MOCK_KANBAN_FIELD,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
@ -67,10 +67,10 @@ describe('buildRecordGqlFieldsAggregateForRecordBoard', () => {
|
|||||||
operation: AGGREGATE_OPERATIONS.count,
|
operation: AGGREGATE_OPERATIONS.count,
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = buildRecordGqlFieldsAggregateForRecordBoard({
|
const result = buildRecordGqlFieldsAggregateForView({
|
||||||
objectMetadataItem: mockObjectMetadata,
|
objectMetadataItem: mockObjectMetadata,
|
||||||
recordIndexKanbanAggregateOperation: operation,
|
recordIndexKanbanAggregateOperation: operation,
|
||||||
kanbanFieldName: MOCK_KANBAN_FIELD,
|
fieldNameForCount: MOCK_KANBAN_FIELD,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
@ -85,10 +85,10 @@ describe('buildRecordGqlFieldsAggregateForRecordBoard', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
expect(() =>
|
expect(() =>
|
||||||
buildRecordGqlFieldsAggregateForRecordBoard({
|
buildRecordGqlFieldsAggregateForView({
|
||||||
objectMetadataItem: mockObjectMetadata,
|
objectMetadataItem: mockObjectMetadata,
|
||||||
recordIndexKanbanAggregateOperation: operation,
|
recordIndexKanbanAggregateOperation: operation,
|
||||||
kanbanFieldName: MOCK_KANBAN_FIELD,
|
fieldNameForCount: MOCK_KANBAN_FIELD,
|
||||||
}),
|
}),
|
||||||
).toThrow(
|
).toThrow(
|
||||||
`No field found to compute aggregate operation ${AGGREGATE_OPERATIONS.sum} on object ${mockObjectMetadata.nameSingular}`,
|
`No field found to compute aggregate operation ${AGGREGATE_OPERATIONS.sum} on object ${mockObjectMetadata.nameSingular}`,
|
||||||
@ -4,14 +4,14 @@ import { KanbanAggregateOperation } from '@/object-record/record-index/states/re
|
|||||||
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations';
|
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
export const buildRecordGqlFieldsAggregateForRecordBoard = ({
|
export const buildRecordGqlFieldsAggregateForView = ({
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
recordIndexKanbanAggregateOperation,
|
recordIndexKanbanAggregateOperation,
|
||||||
kanbanFieldName,
|
fieldNameForCount,
|
||||||
}: {
|
}: {
|
||||||
objectMetadataItem: ObjectMetadataItem;
|
objectMetadataItem: ObjectMetadataItem;
|
||||||
recordIndexKanbanAggregateOperation: KanbanAggregateOperation;
|
recordIndexKanbanAggregateOperation: KanbanAggregateOperation;
|
||||||
kanbanFieldName: string;
|
fieldNameForCount: string;
|
||||||
}): RecordGqlFieldsAggregate => {
|
}): RecordGqlFieldsAggregate => {
|
||||||
let recordGqlFieldsAggregate = {};
|
let recordGqlFieldsAggregate = {};
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ export const buildRecordGqlFieldsAggregateForRecordBoard = ({
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
recordGqlFieldsAggregate = {
|
recordGqlFieldsAggregate = {
|
||||||
[kanbanFieldName]: [AGGREGATE_OPERATIONS.count],
|
[fieldNameForCount]: [AGGREGATE_OPERATIONS.count],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -0,0 +1,74 @@
|
|||||||
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
|
import { useAggregateRecords } from '@/object-record/hooks/useAggregateRecords';
|
||||||
|
import { buildRecordGqlFieldsAggregateForView } from '@/object-record/record-board/record-board-column/utils/buildRecordGqlFieldsAggregateForView';
|
||||||
|
import { computeAggregateValueAndLabel } from '@/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel';
|
||||||
|
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
||||||
|
import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter';
|
||||||
|
import { recordIndexFiltersState } from '@/object-record/record-index/states/recordIndexFiltersState';
|
||||||
|
import { recordIndexKanbanAggregateOperationState } from '@/object-record/record-index/states/recordIndexKanbanAggregateOperationState';
|
||||||
|
import { recordIndexViewFilterGroupsState } from '@/object-record/record-index/states/recordIndexViewFilterGroupsState';
|
||||||
|
import { UserContext } from '@/users/contexts/UserContext';
|
||||||
|
import { useContext } from 'react';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
|
type UseAggregateRecordsProps = {
|
||||||
|
objectMetadataItem: ObjectMetadataItem;
|
||||||
|
additionalFilters?: Record<string, unknown>;
|
||||||
|
fallbackFieldName: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useAggregateRecordsForHeader = ({
|
||||||
|
objectMetadataItem,
|
||||||
|
additionalFilters = {},
|
||||||
|
fallbackFieldName,
|
||||||
|
}: UseAggregateRecordsProps) => {
|
||||||
|
const recordIndexViewFilterGroups = useRecoilValue(
|
||||||
|
recordIndexViewFilterGroupsState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const recordIndexFilters = useRecoilValue(recordIndexFiltersState);
|
||||||
|
|
||||||
|
const recordIndexKanbanAggregateOperation = useRecoilValue(
|
||||||
|
recordIndexKanbanAggregateOperationState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { filterValueDependencies } = useFilterValueDependencies();
|
||||||
|
|
||||||
|
const { dateFormat, timeFormat, timeZone } = useContext(UserContext);
|
||||||
|
|
||||||
|
const requestFilters = computeViewRecordGqlOperationFilter(
|
||||||
|
filterValueDependencies,
|
||||||
|
recordIndexFilters,
|
||||||
|
objectMetadataItem.fields,
|
||||||
|
recordIndexViewFilterGroups,
|
||||||
|
);
|
||||||
|
|
||||||
|
const recordGqlFieldsAggregate = buildRecordGqlFieldsAggregateForView({
|
||||||
|
objectMetadataItem,
|
||||||
|
recordIndexKanbanAggregateOperation,
|
||||||
|
fieldNameForCount: fallbackFieldName,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data } = useAggregateRecords({
|
||||||
|
objectNameSingular: objectMetadataItem.nameSingular,
|
||||||
|
recordGqlFieldsAggregate,
|
||||||
|
filter: { ...requestFilters, ...additionalFilters },
|
||||||
|
});
|
||||||
|
|
||||||
|
const { value, labelWithFieldName } = computeAggregateValueAndLabel({
|
||||||
|
data,
|
||||||
|
objectMetadataItem,
|
||||||
|
fieldMetadataId: recordIndexKanbanAggregateOperation?.fieldMetadataId,
|
||||||
|
aggregateOperation: recordIndexKanbanAggregateOperation?.operation,
|
||||||
|
fallbackFieldName,
|
||||||
|
dateFormat,
|
||||||
|
timeFormat,
|
||||||
|
timeZone,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
aggregateValue: value,
|
||||||
|
aggregateLabel: isDefined(value) ? labelWithFieldName : undefined,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -8,16 +8,17 @@ import {
|
|||||||
Tag,
|
Tag,
|
||||||
} from 'twenty-ui';
|
} from 'twenty-ui';
|
||||||
|
|
||||||
|
import { RecordBoardColumnHeaderAggregateDropdown } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdown';
|
||||||
import { useCurrentRecordGroupId } from '@/object-record/record-group/hooks/useCurrentRecordGroupId';
|
import { useCurrentRecordGroupId } from '@/object-record/record-group/hooks/useCurrentRecordGroupId';
|
||||||
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
|
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
|
||||||
import { RecordGroupDefinitionType } from '@/object-record/record-group/types/RecordGroupDefinition';
|
import { RecordGroupDefinitionType } from '@/object-record/record-group/types/RecordGroupDefinition';
|
||||||
import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState';
|
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||||
import { RecordTableTd } from '@/object-record/record-table/record-table-cell/components/RecordTableTd';
|
import { RecordTableTd } from '@/object-record/record-table/record-table-cell/components/RecordTableTd';
|
||||||
import { RecordTableRecordGroupStickyEffect } from '@/object-record/record-table/record-table-section/components/RecordTableRecordGroupStickyEffect';
|
import { RecordTableRecordGroupStickyEffect } from '@/object-record/record-table/record-table-section/components/RecordTableRecordGroupStickyEffect';
|
||||||
|
import { useAggregateRecordsForRecordTableSection } from '@/object-record/record-table/record-table-section/hooks/useAggregateRecordsForRecordTableSection';
|
||||||
import { isRecordGroupTableSectionToggledComponentState } from '@/object-record/record-table/record-table-section/states/isRecordGroupTableSectionToggledComponentState';
|
import { isRecordGroupTableSectionToggledComponentState } from '@/object-record/record-table/record-table-section/states/isRecordGroupTableSectionToggledComponentState';
|
||||||
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
|
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
|
||||||
import { useRecoilComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyStateV2';
|
import { useRecoilComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyStateV2';
|
||||||
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
@ -37,24 +38,22 @@ const StyledAnimatedLightIconButton = styled(AnimatedLightIconButton)`
|
|||||||
margin: auto;
|
margin: auto;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledTotalRow = styled.span`
|
|
||||||
color: ${({ theme }) => theme.font.color.tertiary};
|
|
||||||
margin-left: ${({ theme }) => theme.spacing(2)};
|
|
||||||
text-align: center;
|
|
||||||
vertical-align: middle;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledRecordGroupSection = styled(RecordTableTd)`
|
const StyledRecordGroupSection = styled(RecordTableTd)`
|
||||||
border-right: none;
|
border-right: none;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: ${({ theme }) => theme.spacing(1)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledEmptyTd = styled.td`
|
const StyledEmptyTd = styled.td`
|
||||||
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
|
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const StyledTag = styled(Tag)`
|
||||||
|
flex-shrink: 0;
|
||||||
|
`;
|
||||||
|
|
||||||
export const RecordTableRecordGroupSection = () => {
|
export const RecordTableRecordGroupSection = () => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
@ -64,10 +63,10 @@ export const RecordTableRecordGroupSection = () => {
|
|||||||
visibleTableColumnsComponentSelector,
|
visibleTableColumnsComponentSelector,
|
||||||
);
|
);
|
||||||
|
|
||||||
const recordIdsByGroup = useRecoilComponentFamilyValueV2(
|
const { objectMetadataItem } = useRecordTableContextOrThrow();
|
||||||
recordIndexRecordIdsByGroupComponentFamilyState,
|
|
||||||
currentRecordGroupId,
|
const { aggregateValue, aggregateLabel } =
|
||||||
);
|
useAggregateRecordsForRecordTableSection();
|
||||||
|
|
||||||
const [
|
const [
|
||||||
isRecordGroupTableSectionToggled,
|
isRecordGroupTableSectionToggled,
|
||||||
@ -102,7 +101,7 @@ export const RecordTableRecordGroupSection = () => {
|
|||||||
/>
|
/>
|
||||||
</StyledChevronContainer>
|
</StyledChevronContainer>
|
||||||
<StyledRecordGroupSection className="disable-shadow">
|
<StyledRecordGroupSection className="disable-shadow">
|
||||||
<Tag
|
<StyledTag
|
||||||
variant={
|
variant={
|
||||||
recordGroup.type !== RecordGroupDefinitionType.NoValue
|
recordGroup.type !== RecordGroupDefinitionType.NoValue
|
||||||
? 'solid'
|
? 'solid'
|
||||||
@ -116,7 +115,12 @@ export const RecordTableRecordGroupSection = () => {
|
|||||||
text={recordGroup.title}
|
text={recordGroup.title}
|
||||||
weight="medium"
|
weight="medium"
|
||||||
/>
|
/>
|
||||||
<StyledTotalRow>{recordIdsByGroup.length}</StyledTotalRow>
|
<RecordBoardColumnHeaderAggregateDropdown
|
||||||
|
aggregateValue={aggregateValue}
|
||||||
|
dropdownId={`record-group-section-aggregate-dropdown-${currentRecordGroupId}`}
|
||||||
|
objectMetadataItem={objectMetadataItem}
|
||||||
|
aggregateLabel={aggregateLabel}
|
||||||
|
/>
|
||||||
<RecordTableRecordGroupStickyEffect />
|
<RecordTableRecordGroupStickyEffect />
|
||||||
</StyledRecordGroupSection>
|
</StyledRecordGroupSection>
|
||||||
<StyledEmptyTd colSpan={visibleColumns.length - 1} />
|
<StyledEmptyTd colSpan={visibleColumns.length - 1} />
|
||||||
|
|||||||
@ -0,0 +1,16 @@
|
|||||||
|
import { useRecordGroupFilter } from '@/object-record/record-group/hooks/useRecordGroupFilter';
|
||||||
|
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||||
|
import { useAggregateRecordsForHeader } from '@/object-record/record-table/hooks/useAggregateRecordsForHeader';
|
||||||
|
|
||||||
|
const DEFAULT_FIELD_NAME_FOR_COUNT = 'name';
|
||||||
|
|
||||||
|
export const useAggregateRecordsForRecordTableSection = () => {
|
||||||
|
const { objectMetadataItem } = useRecordTableContextOrThrow();
|
||||||
|
const { recordGroupFilter } = useRecordGroupFilter(objectMetadataItem.fields);
|
||||||
|
|
||||||
|
return useAggregateRecordsForHeader({
|
||||||
|
objectMetadataItem,
|
||||||
|
additionalFilters: recordGroupFilter,
|
||||||
|
fallbackFieldName: DEFAULT_FIELD_NAME_FOR_COUNT,
|
||||||
|
});
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user