Aggregate fast follows (1) (#9233)
Some fixes required: - [Aggregate value should not disappear when dropdown is open](https://discord.com/channels/1130383047699738754/1319328950475817001/1319328950475817001) - [Delay the apparition of the tooltip on kanban](https://discord.com/channels/1130383047699738754/1319327824632352860/1319327824632352860) - [Group options in sub-menus](https://discord.com/channels/1130383047699738754/1319326443951362059/1319326443951362059)  - Display the currently selected option with a checkmark  - [Loading -> Aggregates should appear at the same time that records, not before](https://discord.com/channels/1130383047699738754/1319329819749646456/1319329899630301318)
This commit is contained in:
@ -1,11 +1,13 @@
|
||||
import { ObjectOptionsDropdownContextValue } from '@/object-record/object-options-dropdown/states/contexts/ObjectOptionsDropdownContext';
|
||||
import { RecordBoardColumnHeaderAggregateDropdownContextValue } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownContext';
|
||||
import { RecordTableColumnAggregateFooterDropdownContextValue } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterDropdownContext';
|
||||
import { useDropdown as useDropdownUi } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { Context, useCallback, useContext } from 'react';
|
||||
|
||||
export const useDropdown = <
|
||||
T extends
|
||||
| RecordBoardColumnHeaderAggregateDropdownContextValue
|
||||
| RecordTableColumnAggregateFooterDropdownContextValue
|
||||
| ObjectOptionsDropdownContextValue,
|
||||
>({
|
||||
context,
|
||||
|
||||
@ -6,8 +6,8 @@ import { RecordBoardColumnHeaderAggregateDropdownComponentInstanceContext } from
|
||||
import { RecordBoardColumnHeaderAggregateDropdownButton } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownButton';
|
||||
import { AggregateDropdownContent } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownContent';
|
||||
import { RecordBoardColumnHeaderAggregateDropdownContext } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownContext';
|
||||
import { AggregateContentId } from '@/object-record/record-board/types/AggregateContentId';
|
||||
import { RecordBoardColumnHotkeyScope } from '@/object-record/record-board/types/BoardColumnHotkeyScope';
|
||||
import { RecordBoardColumnHeaderAggregateContentId } from '@/object-record/record-board/types/RecordBoardColumnHeaderAggregateContentId';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
|
||||
type RecordBoardColumnHeaderAggregateDropdownProps = {
|
||||
@ -24,13 +24,14 @@ export const RecordBoardColumnHeaderAggregateDropdown = ({
|
||||
dropdownId,
|
||||
}: RecordBoardColumnHeaderAggregateDropdownProps) => {
|
||||
const { currentContentId, handleContentChange, handleResetContent } =
|
||||
useCurrentContentId<AggregateContentId>();
|
||||
useCurrentContentId<RecordBoardColumnHeaderAggregateContentId>();
|
||||
|
||||
return (
|
||||
<RecordBoardColumnHeaderAggregateDropdownComponentInstanceContext.Provider
|
||||
value={{ instanceId: dropdownId }}
|
||||
>
|
||||
<Dropdown
|
||||
onClose={handleResetContent}
|
||||
dropdownId={dropdownId}
|
||||
dropdownHotkeyScope={{
|
||||
scope: RecordBoardColumnHotkeyScope.ColumnHeader,
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/StyledHeaderDropdownButton';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import styled from '@emotion/styled';
|
||||
import { AppTooltip, Tag, TooltipDelay } from 'twenty-ui';
|
||||
|
||||
@ -15,18 +16,25 @@ export const RecordBoardColumnHeaderAggregateDropdownButton = ({
|
||||
value?: string | number;
|
||||
tooltip?: string;
|
||||
}) => {
|
||||
const { isDropdownOpen } = useDropdown(dropdownId);
|
||||
return (
|
||||
<div id={dropdownId}>
|
||||
<StyledButton>
|
||||
<Tag text={value ? value.toString() : '-'} color={'transparent'} />
|
||||
<AppTooltip
|
||||
anchorSelect={`#${dropdownId}`}
|
||||
content={tooltip}
|
||||
noArrow
|
||||
place="right"
|
||||
positionStrategy="fixed"
|
||||
delay={TooltipDelay.shortDelay}
|
||||
<Tag
|
||||
text={value ? value.toString() : '-'}
|
||||
color={'transparent'}
|
||||
weight={'regular'}
|
||||
/>
|
||||
{!isDropdownOpen && (
|
||||
<AppTooltip
|
||||
anchorSelect={`#${dropdownId}`}
|
||||
content={tooltip}
|
||||
noArrow
|
||||
place="right"
|
||||
positionStrategy="fixed"
|
||||
delay={TooltipDelay.mediumDelay}
|
||||
/>
|
||||
)}
|
||||
</StyledButton>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -2,6 +2,7 @@ import { useDropdown } from '@/dropdown/hooks/useDropdown';
|
||||
import { RecordBoardColumnHeaderAggregateDropdownContext } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownContext';
|
||||
import { RecordBoardColumnHeaderAggregateDropdownFieldsContent } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownFieldsContent';
|
||||
import { RecordBoardColumnHeaderAggregateDropdownMenuContent } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownMenuContent';
|
||||
import { RecordBoardColumnHeaderAggregateDropdownMoreOptionsContent } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownMoreOptionsContent';
|
||||
|
||||
export const AggregateDropdownContent = () => {
|
||||
const { currentContentId } = useDropdown({
|
||||
@ -9,6 +10,8 @@ export const AggregateDropdownContent = () => {
|
||||
});
|
||||
|
||||
switch (currentContentId) {
|
||||
case 'moreAggregateOperationOptions':
|
||||
return <RecordBoardColumnHeaderAggregateDropdownMoreOptionsContent />;
|
||||
case 'aggregateFields':
|
||||
return <RecordBoardColumnHeaderAggregateDropdownFieldsContent />;
|
||||
default:
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { AggregateContentId } from '@/object-record/record-board/types/AggregateContentId';
|
||||
import { RecordBoardColumnHeaderAggregateContentId } from '@/object-record/record-board/types/RecordBoardColumnHeaderAggregateContentId';
|
||||
import { createContext } from 'react';
|
||||
|
||||
export type RecordBoardColumnHeaderAggregateDropdownContextValue = {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
currentContentId: AggregateContentId | null;
|
||||
onContentChange: (key: AggregateContentId) => void;
|
||||
currentContentId: RecordBoardColumnHeaderAggregateContentId | null;
|
||||
onContentChange: (key: RecordBoardColumnHeaderAggregateContentId) => void;
|
||||
resetContent: () => void;
|
||||
dropdownId: string;
|
||||
};
|
||||
|
||||
@ -3,15 +3,23 @@ import { RecordBoardColumnHeaderAggregateDropdownContext } from '@/object-record
|
||||
import { aggregateOperationComponentState } from '@/object-record/record-board/record-board-column/states/aggregateOperationComponentState';
|
||||
import { availableFieldIdsForAggregateOperationComponentState } from '@/object-record/record-board/record-board-column/states/availableFieldIdsForAggregateOperationComponentState';
|
||||
import { getAggregateOperationLabel } from '@/object-record/record-board/record-board-column/utils/getAggregateOperationLabel';
|
||||
import { recordIndexKanbanAggregateOperationState } from '@/object-record/record-index/states/recordIndexKanbanAggregateOperationState';
|
||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useUpdateViewAggregate } from '@/views/hooks/useUpdateViewAggregate';
|
||||
import { Icon123, IconChevronLeft, MenuItem, useIcons } from 'twenty-ui';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import {
|
||||
Icon123,
|
||||
IconCheck,
|
||||
IconChevronLeft,
|
||||
MenuItem,
|
||||
useIcons,
|
||||
} from 'twenty-ui';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const RecordBoardColumnHeaderAggregateDropdownFieldsContent = () => {
|
||||
const { closeDropdown, resetContent, objectMetadataItem } = useDropdown({
|
||||
const { closeDropdown, objectMetadataItem, onContentChange } = useDropdown({
|
||||
context: RecordBoardColumnHeaderAggregateDropdownContext,
|
||||
});
|
||||
|
||||
@ -27,11 +35,18 @@ export const RecordBoardColumnHeaderAggregateDropdownFieldsContent = () => {
|
||||
availableFieldIdsForAggregateOperationComponentState,
|
||||
);
|
||||
|
||||
const recordIndexKanbanAggregateOperation = useRecoilValue(
|
||||
recordIndexKanbanAggregateOperationState,
|
||||
);
|
||||
|
||||
if (!isDefined(aggregateOperation)) return <></>;
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuHeader StartIcon={IconChevronLeft} onClick={resetContent}>
|
||||
<DropdownMenuHeader
|
||||
StartIcon={IconChevronLeft}
|
||||
onClick={() => onContentChange('moreAggregateOperationOptions')}
|
||||
>
|
||||
{getAggregateOperationLabel(aggregateOperation)}
|
||||
</DropdownMenuHeader>
|
||||
<DropdownMenuItemsContainer>
|
||||
@ -53,6 +68,14 @@ export const RecordBoardColumnHeaderAggregateDropdownFieldsContent = () => {
|
||||
}}
|
||||
LeftIcon={getIcon(fieldMetadata.icon) ?? Icon123}
|
||||
text={fieldMetadata.label}
|
||||
RightIcon={
|
||||
recordIndexKanbanAggregateOperation?.fieldMetadataId ===
|
||||
fieldId &&
|
||||
recordIndexKanbanAggregateOperation?.operation ===
|
||||
aggregateOperation
|
||||
? IconCheck
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Key } from 'ts-key-enum';
|
||||
import { MenuItem } from 'twenty-ui';
|
||||
import { IconCheck, MenuItem } from 'twenty-ui';
|
||||
|
||||
import { useDropdown } from '@/dropdown/hooks/useDropdown';
|
||||
import {
|
||||
@ -7,23 +7,18 @@ import {
|
||||
RecordBoardColumnHeaderAggregateDropdownContextValue,
|
||||
} from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownContext';
|
||||
|
||||
import { RecordBoardColumnHeaderAggregateDropdownMenuItem } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownMenuItem';
|
||||
import { aggregateOperationComponentState } from '@/object-record/record-board/record-board-column/states/aggregateOperationComponentState';
|
||||
import { availableFieldIdsForAggregateOperationComponentState } from '@/object-record/record-board/record-board-column/states/availableFieldIdsForAggregateOperationComponentState';
|
||||
import { getAggregateOperationLabel } from '@/object-record/record-board/record-board-column/utils/getAggregateOperationLabel';
|
||||
import { recordIndexKanbanAggregateOperationState } from '@/object-record/record-index/states/recordIndexKanbanAggregateOperationState';
|
||||
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations';
|
||||
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
|
||||
import { AvailableFieldsForAggregateOperation } from '@/object-record/types/AvailableFieldsForAggregateOperation';
|
||||
import { getAvailableFieldsIdsForAggregationFromObjectFields } from '@/object-record/utils/getAvailableFieldsIdsForAggregationFromObjectFields';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { useUpdateViewAggregate } from '@/views/hooks/useUpdateViewAggregate';
|
||||
import isEmpty from 'lodash.isempty';
|
||||
import { useMemo } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const RecordBoardColumnHeaderAggregateDropdownMenuContent = () => {
|
||||
const { objectMetadataItem, onContentChange, closeDropdown } =
|
||||
const { onContentChange, closeDropdown } =
|
||||
useDropdown<RecordBoardColumnHeaderAggregateDropdownContextValue>({
|
||||
context: RecordBoardColumnHeaderAggregateDropdownContext,
|
||||
});
|
||||
@ -36,24 +31,12 @@ export const RecordBoardColumnHeaderAggregateDropdownMenuContent = () => {
|
||||
TableOptionsHotkeyScope.Dropdown,
|
||||
);
|
||||
|
||||
const availableAggregations: AvailableFieldsForAggregateOperation = useMemo(
|
||||
() =>
|
||||
getAvailableFieldsIdsForAggregationFromObjectFields(
|
||||
objectMetadataItem.fields,
|
||||
),
|
||||
[objectMetadataItem.fields],
|
||||
);
|
||||
|
||||
const setAggregateOperation = useSetRecoilComponentStateV2(
|
||||
aggregateOperationComponentState,
|
||||
);
|
||||
|
||||
const setAvailableFieldsForAggregateOperation = useSetRecoilComponentStateV2(
|
||||
availableFieldIdsForAggregateOperationComponentState,
|
||||
);
|
||||
|
||||
const { updateViewAggregate } = useUpdateViewAggregate();
|
||||
|
||||
const recordIndexKanbanAggregateOperation = useRecoilValue(
|
||||
recordIndexKanbanAggregateOperationState,
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuItemsContainer>
|
||||
@ -63,35 +46,24 @@ export const RecordBoardColumnHeaderAggregateDropdownMenuContent = () => {
|
||||
kanbanAggregateOperationFieldMetadataId: null,
|
||||
kanbanAggregateOperation: AGGREGATE_OPERATIONS.count,
|
||||
});
|
||||
closeDropdown();
|
||||
}}
|
||||
text={getAggregateOperationLabel(AGGREGATE_OPERATIONS.count)}
|
||||
RightIcon={
|
||||
!isDefined(recordIndexKanbanAggregateOperation?.operation) ||
|
||||
recordIndexKanbanAggregateOperation?.operation ===
|
||||
AGGREGATE_OPERATIONS.count
|
||||
? IconCheck
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
onContentChange('moreAggregateOperationOptions');
|
||||
}}
|
||||
text={'More options'}
|
||||
hasSubMenu
|
||||
/>
|
||||
{Object.entries(availableAggregations).map(
|
||||
([
|
||||
availableAggregationOperation,
|
||||
availableAggregationFieldsIdsForOperation,
|
||||
]) =>
|
||||
isEmpty(availableAggregationFieldsIdsForOperation) ? (
|
||||
<></>
|
||||
) : (
|
||||
<RecordBoardColumnHeaderAggregateDropdownMenuItem
|
||||
key={`aggregate-dropdown-menu-content-${availableAggregationOperation}`}
|
||||
onContentChange={() => {
|
||||
setAggregateOperation(
|
||||
availableAggregationOperation as AGGREGATE_OPERATIONS,
|
||||
);
|
||||
setAvailableFieldsForAggregateOperation(
|
||||
availableAggregationFieldsIdsForOperation,
|
||||
);
|
||||
onContentChange('aggregateFields');
|
||||
}}
|
||||
text={getAggregateOperationLabel(
|
||||
availableAggregationOperation as AGGREGATE_OPERATIONS,
|
||||
)}
|
||||
hasSubMenu
|
||||
/>
|
||||
),
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -1,15 +1,22 @@
|
||||
import { MenuItem } from 'twenty-ui';
|
||||
import { IconComponent, MenuItem } from 'twenty-ui';
|
||||
|
||||
export const RecordBoardColumnHeaderAggregateDropdownMenuItem = ({
|
||||
onContentChange,
|
||||
text,
|
||||
hasSubMenu,
|
||||
RightIcon,
|
||||
}: {
|
||||
onContentChange: () => void;
|
||||
hasSubMenu: boolean;
|
||||
text: string;
|
||||
RightIcon?: IconComponent | null;
|
||||
}) => {
|
||||
return (
|
||||
<MenuItem onClick={onContentChange} text={text} hasSubMenu={hasSubMenu} />
|
||||
<MenuItem
|
||||
onClick={onContentChange}
|
||||
text={text}
|
||||
hasSubMenu={hasSubMenu}
|
||||
RightIcon={RightIcon}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@ -0,0 +1,89 @@
|
||||
import { useDropdown } from '@/dropdown/hooks/useDropdown';
|
||||
import {
|
||||
RecordBoardColumnHeaderAggregateDropdownContext,
|
||||
RecordBoardColumnHeaderAggregateDropdownContextValue,
|
||||
} from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownContext';
|
||||
import { RecordBoardColumnHeaderAggregateDropdownMenuItem } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownMenuItem';
|
||||
import { aggregateOperationComponentState } from '@/object-record/record-board/record-board-column/states/aggregateOperationComponentState';
|
||||
import { availableFieldIdsForAggregateOperationComponentState } from '@/object-record/record-board/record-board-column/states/availableFieldIdsForAggregateOperationComponentState';
|
||||
import { getAggregateOperationLabel } from '@/object-record/record-board/record-board-column/utils/getAggregateOperationLabel';
|
||||
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations';
|
||||
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
|
||||
import { AvailableFieldsForAggregateOperation } from '@/object-record/types/AvailableFieldsForAggregateOperation';
|
||||
import { getAvailableFieldsIdsForAggregationFromObjectFields } from '@/object-record/utils/getAvailableFieldsIdsForAggregationFromObjectFields';
|
||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import isEmpty from 'lodash.isempty';
|
||||
import { useMemo } from 'react';
|
||||
import { Key } from 'ts-key-enum';
|
||||
import { IconChevronLeft } from 'twenty-ui';
|
||||
|
||||
export const RecordBoardColumnHeaderAggregateDropdownMoreOptionsContent =
|
||||
() => {
|
||||
const { objectMetadataItem, onContentChange, closeDropdown, resetContent } =
|
||||
useDropdown<RecordBoardColumnHeaderAggregateDropdownContextValue>({
|
||||
context: RecordBoardColumnHeaderAggregateDropdownContext,
|
||||
});
|
||||
|
||||
useScopedHotkeys(
|
||||
[Key.Escape],
|
||||
() => {
|
||||
closeDropdown();
|
||||
},
|
||||
TableOptionsHotkeyScope.Dropdown,
|
||||
);
|
||||
|
||||
const availableAggregations: AvailableFieldsForAggregateOperation = useMemo(
|
||||
() =>
|
||||
getAvailableFieldsIdsForAggregationFromObjectFields(
|
||||
objectMetadataItem.fields,
|
||||
),
|
||||
[objectMetadataItem.fields],
|
||||
);
|
||||
|
||||
const setAggregateOperation = useSetRecoilComponentStateV2(
|
||||
aggregateOperationComponentState,
|
||||
);
|
||||
|
||||
const setAvailableFieldsForAggregateOperation =
|
||||
useSetRecoilComponentStateV2(
|
||||
availableFieldIdsForAggregateOperationComponentState,
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuHeader StartIcon={IconChevronLeft} onClick={resetContent}>
|
||||
More options
|
||||
</DropdownMenuHeader>
|
||||
<DropdownMenuItemsContainer>
|
||||
{Object.entries(availableAggregations)
|
||||
.filter(([, fields]) => !isEmpty(fields))
|
||||
.map(
|
||||
([
|
||||
availableAggregationOperation,
|
||||
availableAggregationFieldsIdsForOperation,
|
||||
]) => (
|
||||
<RecordBoardColumnHeaderAggregateDropdownMenuItem
|
||||
key={`aggregate-dropdown-menu-content-${availableAggregationOperation}`}
|
||||
onContentChange={() => {
|
||||
setAggregateOperation(
|
||||
availableAggregationOperation as AGGREGATE_OPERATIONS,
|
||||
);
|
||||
setAvailableFieldsForAggregateOperation(
|
||||
availableAggregationFieldsIdsForOperation,
|
||||
);
|
||||
onContentChange('aggregateFields');
|
||||
}}
|
||||
text={getAggregateOperationLabel(
|
||||
availableAggregationOperation as AGGREGATE_OPERATIONS,
|
||||
)}
|
||||
hasSubMenu
|
||||
/>
|
||||
),
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -141,6 +141,7 @@ describe('computeAggregateValueAndLabel', () => {
|
||||
expect(result).toEqual({
|
||||
value: 42,
|
||||
label: 'Count',
|
||||
labelWithFieldName: 'Count',
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -35,6 +35,7 @@ export const computeAggregateValueAndLabel = ({
|
||||
return {
|
||||
value: data?.[fallbackFieldName]?.[AGGREGATE_OPERATIONS.count],
|
||||
label: `${getAggregateOperationLabel(AGGREGATE_OPERATIONS.count)}`,
|
||||
labelWithFieldName: `${getAggregateOperationLabel(AGGREGATE_OPERATIONS.count)}`,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1 +0,0 @@
|
||||
export type AggregateContentId = 'aggregateOperations' | 'aggregateFields';
|
||||
@ -0,0 +1,4 @@
|
||||
export type RecordBoardColumnHeaderAggregateContentId =
|
||||
| 'aggregateOperations'
|
||||
| 'aggregateFields'
|
||||
| 'moreAggregateOperationOptions';
|
||||
@ -30,7 +30,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 { viewFieldAggregateOperationState } from '@/object-record/record-table/record-table-footer/states/viewFieldAggregateOperationState';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { ViewBar } from '@/views/components/ViewBar';
|
||||
import { ViewField } from '@/views/types/ViewField';
|
||||
@ -126,7 +126,7 @@ export const RecordIndexContainer = () => {
|
||||
for (const viewField of viewFields) {
|
||||
const aggregateOperationForViewField = snapshot
|
||||
.getLoadable(
|
||||
aggregateOperationForViewFieldState({
|
||||
viewFieldAggregateOperationState({
|
||||
viewFieldId: viewField.id,
|
||||
}),
|
||||
)
|
||||
@ -134,7 +134,7 @@ export const RecordIndexContainer = () => {
|
||||
|
||||
if (aggregateOperationForViewField !== viewField.aggregateOperation) {
|
||||
set(
|
||||
aggregateOperationForViewFieldState({
|
||||
viewFieldAggregateOperationState({
|
||||
viewFieldId: viewField.id,
|
||||
}),
|
||||
viewField.aggregateOperation,
|
||||
|
||||
@ -6,7 +6,7 @@ import { useRecordIndexContextOrThrow } from '@/object-record/record-index/conte
|
||||
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 { viewFieldAggregateOperationState } from '@/object-record/record-table/record-table-footer/states/viewFieldAggregateOperationState';
|
||||
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
||||
import { useSetRecordCountInCurrentView } from '@/views/hooks/useSetRecordCountInCurrentView';
|
||||
import { ViewField } from '@/views/types/ViewField';
|
||||
@ -77,7 +77,7 @@ export const RecordIndexTableContainerEffect = () => {
|
||||
(viewField: ViewField) => {
|
||||
const aggregateOperationForViewField = snapshot
|
||||
.getLoadable(
|
||||
aggregateOperationForViewFieldState({
|
||||
viewFieldAggregateOperationState({
|
||||
viewFieldId: viewField.id,
|
||||
}),
|
||||
)
|
||||
@ -85,7 +85,7 @@ export const RecordIndexTableContainerEffect = () => {
|
||||
|
||||
if (aggregateOperationForViewField !== viewField.aggregateOperation) {
|
||||
set(
|
||||
aggregateOperationForViewFieldState({
|
||||
viewFieldAggregateOperationState({
|
||||
viewFieldId: viewField.id,
|
||||
}),
|
||||
viewField.aggregateOperation,
|
||||
|
||||
@ -96,9 +96,12 @@ export const RecordTable = () => {
|
||||
<RecordTableRecordGroupsBody />
|
||||
)}
|
||||
<RecordTableStickyEffect />
|
||||
{isAggregateQueryEnabled && !hasRecordGroups && (
|
||||
<RecordTableAggregateFooter endOfTableSticky />
|
||||
)}
|
||||
{isAggregateQueryEnabled &&
|
||||
!hasRecordGroups &&
|
||||
!isRecordTableInitialLoading &&
|
||||
allRecordIds.length > 0 && (
|
||||
<RecordTableAggregateFooter endOfTableSticky />
|
||||
)}
|
||||
</StyledTable>
|
||||
<DragSelect
|
||||
dragSelectable={tableBodyRef}
|
||||
|
||||
@ -0,0 +1,60 @@
|
||||
import { getAggregateOperationLabel } from '@/object-record/record-board/record-board-column/utils/getAggregateOperationLabel';
|
||||
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations';
|
||||
import { RecordTableColumnAggregateFooterDropdownContext } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterDropdownContext';
|
||||
import { useViewFieldAggregateOperation } from '@/object-record/record-table/record-table-footer/hooks/useViewFieldAggregateOperation';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { ReactNode, useContext } from 'react';
|
||||
import { IconCheck, isDefined, MenuItem } from 'twenty-ui';
|
||||
|
||||
export const RecordTableColumnAggregateFooterAggregateOperationMenuItems = ({
|
||||
aggregateOperations,
|
||||
children,
|
||||
}: {
|
||||
aggregateOperations: AGGREGATE_OPERATIONS[];
|
||||
children?: ReactNode;
|
||||
}) => {
|
||||
const {
|
||||
updateViewFieldAggregateOperation,
|
||||
currentViewFieldAggregateOperation,
|
||||
} = useViewFieldAggregateOperation();
|
||||
|
||||
const { dropdownId, resetContent } = useContext(
|
||||
RecordTableColumnAggregateFooterDropdownContext,
|
||||
);
|
||||
const { closeDropdown } = useDropdown(dropdownId);
|
||||
return (
|
||||
<DropdownMenuItemsContainer>
|
||||
{aggregateOperations.map((operation) => (
|
||||
<MenuItem
|
||||
key={operation}
|
||||
onClick={() => {
|
||||
updateViewFieldAggregateOperation(operation);
|
||||
closeDropdown();
|
||||
}}
|
||||
text={getAggregateOperationLabel(operation)}
|
||||
RightIcon={
|
||||
currentViewFieldAggregateOperation === operation
|
||||
? IconCheck
|
||||
: undefined
|
||||
}
|
||||
aria-selected={currentViewFieldAggregateOperation === operation}
|
||||
/>
|
||||
))}
|
||||
{children}
|
||||
<MenuItem
|
||||
key={'none'}
|
||||
onClick={() => {
|
||||
updateViewFieldAggregateOperation(null);
|
||||
resetContent();
|
||||
closeDropdown();
|
||||
}}
|
||||
text={'None'}
|
||||
RightIcon={
|
||||
!isDefined(currentViewFieldAggregateOperation) ? IconCheck : undefined
|
||||
}
|
||||
aria-selected={!isDefined(currentViewFieldAggregateOperation)}
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
);
|
||||
};
|
||||
@ -1,79 +0,0 @@
|
||||
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 { useRecordTableContextOrThrow } 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 { useMemo } from 'react';
|
||||
import { Key } from 'ts-key-enum';
|
||||
import { MenuItem } from 'twenty-ui';
|
||||
|
||||
export const RecordTableColumnAggregateFooterDropdown = ({
|
||||
column,
|
||||
dropdownId,
|
||||
}: {
|
||||
column: ColumnDefinition<FieldMetadata>;
|
||||
dropdownId: string;
|
||||
}) => {
|
||||
const { closeDropdown } = useDropdown(dropdownId);
|
||||
const { objectMetadataItem } = useRecordTableContextOrThrow();
|
||||
const { currentViewWithSavedFiltersAndSorts } = useGetCurrentView();
|
||||
|
||||
const currentViewField =
|
||||
currentViewWithSavedFiltersAndSorts?.viewFields?.find(
|
||||
(viewField) => viewField.fieldMetadataId === column.fieldMetadataId,
|
||||
);
|
||||
|
||||
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,
|
||||
) => {
|
||||
if (!currentViewField) {
|
||||
throw new Error('ViewField not found');
|
||||
}
|
||||
updateViewFieldRecords([
|
||||
{ ...currentViewField, aggregateOperation: aggregateOperation },
|
||||
]);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuItemsContainer>
|
||||
{availableAggregateOperations.map((aggregation) => (
|
||||
<MenuItem
|
||||
key={aggregation}
|
||||
onClick={() => {
|
||||
handleAggregationChange(aggregation);
|
||||
closeDropdown();
|
||||
}}
|
||||
text={getAggregateOperationLabel(aggregation)}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,17 @@
|
||||
import { useDropdown } from '@/dropdown/hooks/useDropdown';
|
||||
import { RecordTableColumnAggregateFooterDropdownContext } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterDropdownContext';
|
||||
import { RecordTableColumnAggregateFooterDropdownMoreOptionsContent } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterDropdownMoreOptionsContent';
|
||||
import { RecordTableColumnAggregateFooterMenuContent } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterMenuContent';
|
||||
|
||||
export const RecordTableColumnAggregateFooterDropdownContent = () => {
|
||||
const { currentContentId } = useDropdown({
|
||||
context: RecordTableColumnAggregateFooterDropdownContext,
|
||||
});
|
||||
|
||||
switch (currentContentId) {
|
||||
case 'moreAggregateOperationOptions':
|
||||
return <RecordTableColumnAggregateFooterDropdownMoreOptionsContent />;
|
||||
default:
|
||||
return <RecordTableColumnAggregateFooterMenuContent />;
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,15 @@
|
||||
import { RecordTableFooterAggregateContentId } from '@/object-record/record-table/record-table-footer/types/RecordTableFooterAggregateContentId';
|
||||
import { createContext } from 'react';
|
||||
|
||||
export type RecordTableColumnAggregateFooterDropdownContextValue = {
|
||||
currentContentId: RecordTableFooterAggregateContentId | null;
|
||||
onContentChange: (key: RecordTableFooterAggregateContentId) => void;
|
||||
resetContent: () => void;
|
||||
dropdownId: string;
|
||||
fieldMetadataId: string;
|
||||
};
|
||||
|
||||
export const RecordTableColumnAggregateFooterDropdownContext =
|
||||
createContext<RecordTableColumnAggregateFooterDropdownContextValue>(
|
||||
{} as RecordTableColumnAggregateFooterDropdownContextValue,
|
||||
);
|
||||
@ -0,0 +1,57 @@
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { RecordTableColumnAggregateFooterAggregateOperationMenuItems } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterAggregateOperationMenuItems';
|
||||
import { RecordTableColumnAggregateFooterDropdownContext } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterDropdownContext';
|
||||
import { STANDARD_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/record-table-footer/constants/standardAggregateOperationOptions';
|
||||
import { getAvailableAggregateOperationsForFieldMetadataType } from '@/object-record/record-table/record-table-footer/utils/getAvailableAggregateOperationsForFieldMetadataType';
|
||||
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
|
||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader';
|
||||
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 { useContext, useMemo } from 'react';
|
||||
import { Key } from 'ts-key-enum';
|
||||
import { IconChevronLeft } from 'twenty-ui';
|
||||
|
||||
export const RecordTableColumnAggregateFooterDropdownMoreOptionsContent =
|
||||
() => {
|
||||
const { fieldMetadataId, dropdownId, resetContent } = useContext(
|
||||
RecordTableColumnAggregateFooterDropdownContext,
|
||||
);
|
||||
const { closeDropdown } = useDropdown(dropdownId);
|
||||
const { objectMetadataItem } = useRecordTableContextOrThrow();
|
||||
|
||||
useScopedHotkeys(
|
||||
[Key.Escape],
|
||||
() => {
|
||||
resetContent();
|
||||
closeDropdown();
|
||||
},
|
||||
TableOptionsHotkeyScope.Dropdown,
|
||||
);
|
||||
|
||||
const availableAggregateOperations = useMemo(
|
||||
() =>
|
||||
getAvailableAggregateOperationsForFieldMetadataType({
|
||||
fieldMetadataType: objectMetadataItem.fields.find(
|
||||
(field) => field.id === fieldMetadataId,
|
||||
)?.type,
|
||||
}).filter(
|
||||
(aggregateOperation) =>
|
||||
!STANDARD_AGGREGATE_OPERATION_OPTIONS.includes(aggregateOperation),
|
||||
),
|
||||
[fieldMetadataId, objectMetadataItem.fields],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuHeader StartIcon={IconChevronLeft} onClick={resetContent}>
|
||||
More options
|
||||
</DropdownMenuHeader>
|
||||
<DropdownMenuItemsContainer>
|
||||
<RecordTableColumnAggregateFooterAggregateOperationMenuItems
|
||||
aggregateOperations={availableAggregateOperations}
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,68 @@
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { RecordTableColumnAggregateFooterAggregateOperationMenuItems } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterAggregateOperationMenuItems';
|
||||
import { RecordTableColumnAggregateFooterDropdownContext } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterDropdownContext';
|
||||
import { STANDARD_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/record-table-footer/constants/standardAggregateOperationOptions';
|
||||
import { getAvailableAggregateOperationsForFieldMetadataType } from '@/object-record/record-table/record-table-footer/utils/getAvailableAggregateOperationsForFieldMetadataType';
|
||||
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 { useContext, useMemo } from 'react';
|
||||
import { Key } from 'ts-key-enum';
|
||||
import { MenuItem } from 'twenty-ui';
|
||||
|
||||
export const RecordTableColumnAggregateFooterMenuContent = () => {
|
||||
const { fieldMetadataId, dropdownId, onContentChange } = useContext(
|
||||
RecordTableColumnAggregateFooterDropdownContext,
|
||||
);
|
||||
const { closeDropdown } = useDropdown(dropdownId);
|
||||
const { objectMetadataItem } = useRecordTableContextOrThrow();
|
||||
|
||||
useScopedHotkeys(
|
||||
[Key.Escape],
|
||||
() => {
|
||||
closeDropdown();
|
||||
},
|
||||
TableOptionsHotkeyScope.Dropdown,
|
||||
);
|
||||
|
||||
const availableAggregateOperation = useMemo(
|
||||
() =>
|
||||
getAvailableAggregateOperationsForFieldMetadataType({
|
||||
fieldMetadataType: objectMetadataItem.fields.find(
|
||||
(field) => field.id === fieldMetadataId,
|
||||
)?.type,
|
||||
}),
|
||||
[fieldMetadataId, objectMetadataItem.fields],
|
||||
);
|
||||
|
||||
const standardAvailableAggregateOperation =
|
||||
availableAggregateOperation.filter((aggregateOperation) =>
|
||||
STANDARD_AGGREGATE_OPERATION_OPTIONS.includes(aggregateOperation),
|
||||
);
|
||||
|
||||
const otherAvailableAggregateOperation = availableAggregateOperation.filter(
|
||||
(aggregateOperation) =>
|
||||
!STANDARD_AGGREGATE_OPERATION_OPTIONS.includes(aggregateOperation),
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuItemsContainer>
|
||||
<RecordTableColumnAggregateFooterAggregateOperationMenuItems
|
||||
aggregateOperations={standardAvailableAggregateOperation}
|
||||
>
|
||||
{otherAvailableAggregateOperation.length > 0 ? (
|
||||
<MenuItem
|
||||
key={'more-options'}
|
||||
onClick={() => {
|
||||
onContentChange('moreAggregateOperationOptions');
|
||||
}}
|
||||
text={'More options'}
|
||||
hasSubMenu
|
||||
/>
|
||||
) : null}
|
||||
</RecordTableColumnAggregateFooterAggregateOperationMenuItems>
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -1,3 +1,4 @@
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useState } from 'react';
|
||||
@ -74,8 +75,11 @@ export const RecordTableColumnAggregateFooterValue = ({
|
||||
aggregateLabel?: 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;
|
||||
return (
|
||||
<div
|
||||
onMouseEnter={() => {
|
||||
@ -84,7 +88,7 @@ export const RecordTableColumnAggregateFooterValue = ({
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
<StyledCell>
|
||||
{isHovered || isDefined(aggregateValue) || isFirstCell ? (
|
||||
{shouldShowValue ? (
|
||||
<>
|
||||
{isDefined(aggregateValue) ? (
|
||||
<StyledValueContainer>
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
import { useCurrentContentId } from '@/dropdown/hooks/useCurrentContentId';
|
||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { RecordTableColumnAggregateFooterDropdown } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterDropdown';
|
||||
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 { 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';
|
||||
@ -18,6 +21,9 @@ export const RecordTableColumnFooterWithDropdown = ({
|
||||
currentRecordGroupId,
|
||||
isFirstCell,
|
||||
}: RecordTableColumnFooterWithDropdownProps) => {
|
||||
const { currentContentId, handleContentChange, handleResetContent } =
|
||||
useCurrentContentId<RecordTableFooterAggregateContentId>();
|
||||
|
||||
const { toggleScrollXWrapper, toggleScrollYWrapper } =
|
||||
useToggleScrollWrapper();
|
||||
|
||||
@ -27,9 +33,10 @@ export const RecordTableColumnFooterWithDropdown = ({
|
||||
}, [toggleScrollXWrapper, toggleScrollYWrapper]);
|
||||
|
||||
const handleDropdownClose = useCallback(() => {
|
||||
handleResetContent();
|
||||
toggleScrollXWrapper(true);
|
||||
toggleScrollYWrapper(true);
|
||||
}, [toggleScrollXWrapper, toggleScrollYWrapper]);
|
||||
}, [handleResetContent, toggleScrollXWrapper, toggleScrollYWrapper]);
|
||||
|
||||
const { aggregateValue, aggregateLabel } =
|
||||
useAggregateRecordsForRecordTableColumnFooter(column.fieldMetadataId);
|
||||
@ -52,10 +59,17 @@ export const RecordTableColumnFooterWithDropdown = ({
|
||||
/>
|
||||
}
|
||||
dropdownComponents={
|
||||
<RecordTableColumnAggregateFooterDropdown
|
||||
column={column}
|
||||
dropdownId={dropdownId}
|
||||
/>
|
||||
<RecordTableColumnAggregateFooterDropdownContext.Provider
|
||||
value={{
|
||||
currentContentId,
|
||||
onContentChange: handleContentChange,
|
||||
resetContent: handleResetContent,
|
||||
dropdownId: dropdownId,
|
||||
fieldMetadataId: column.fieldMetadataId,
|
||||
}}
|
||||
>
|
||||
<RecordTableColumnAggregateFooterDropdownContent />
|
||||
</RecordTableColumnAggregateFooterDropdownContext.Provider>
|
||||
}
|
||||
dropdownOffset={{ x: -1 }}
|
||||
dropdownPlacement="bottom-start"
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations';
|
||||
|
||||
export const STANDARD_AGGREGATE_OPERATION_OPTIONS = [
|
||||
AGGREGATE_OPERATIONS.count,
|
||||
];
|
||||
@ -6,7 +6,7 @@ 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 { aggregateOperationForViewFieldState } from '@/object-record/record-table/record-table-footer/states/aggregateOperationForViewFieldState';
|
||||
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 { useRecoilValue } from 'recoil';
|
||||
@ -43,7 +43,7 @@ export const useAggregateRecordsForRecordTableColumnFooter = (
|
||||
)?.id ?? '';
|
||||
|
||||
const aggregateOperationForViewField = useRecoilValue(
|
||||
aggregateOperationForViewFieldState({ viewFieldId: viewFieldId }),
|
||||
viewFieldAggregateOperationState({ viewFieldId: viewFieldId }),
|
||||
);
|
||||
|
||||
const fieldName = objectMetadataItem.fields.find(
|
||||
|
||||
@ -0,0 +1,41 @@
|
||||
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations';
|
||||
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 { usePersistViewFieldRecords } from '@/views/hooks/internal/usePersistViewFieldRecords';
|
||||
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
||||
import { useContext } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
export const useViewFieldAggregateOperation = () => {
|
||||
const { fieldMetadataId } = useContext(
|
||||
RecordTableColumnAggregateFooterDropdownContext,
|
||||
);
|
||||
const { currentViewWithSavedFiltersAndSorts } = useGetCurrentView();
|
||||
|
||||
const currentViewField =
|
||||
currentViewWithSavedFiltersAndSorts?.viewFields?.find(
|
||||
(viewField) => viewField.fieldMetadataId === fieldMetadataId,
|
||||
);
|
||||
const { updateViewFieldRecords } = usePersistViewFieldRecords();
|
||||
const updateViewFieldAggregateOperation = (
|
||||
aggregateOperation: AGGREGATE_OPERATIONS | null,
|
||||
) => {
|
||||
if (!currentViewField) {
|
||||
throw new Error('ViewField not found');
|
||||
}
|
||||
updateViewFieldRecords([
|
||||
{ ...currentViewField, aggregateOperation: aggregateOperation },
|
||||
]);
|
||||
};
|
||||
|
||||
const currentViewFieldAggregateOperation = useRecoilValue(
|
||||
viewFieldAggregateOperationState({
|
||||
viewFieldId: currentViewField?.id ?? '',
|
||||
}),
|
||||
);
|
||||
|
||||
return {
|
||||
updateViewFieldAggregateOperation,
|
||||
currentViewFieldAggregateOperation,
|
||||
};
|
||||
};
|
||||
@ -1,10 +1,10 @@
|
||||
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations';
|
||||
import { createFamilyState } from '@/ui/utilities/state/utils/createFamilyState';
|
||||
|
||||
export const aggregateOperationForViewFieldState = createFamilyState<
|
||||
export const viewFieldAggregateOperationState = createFamilyState<
|
||||
AGGREGATE_OPERATIONS | null | undefined,
|
||||
{ viewFieldId: string }
|
||||
>({
|
||||
key: 'aggregateOperationForViewFieldState',
|
||||
key: 'viewFieldAggregateOperationState',
|
||||
defaultValue: null,
|
||||
});
|
||||
@ -0,0 +1,2 @@
|
||||
export type RecordTableFooterAggregateContentId =
|
||||
'moreAggregateOperationOptions';
|
||||
@ -13,7 +13,7 @@ export const useUpdateViewAggregate = () => {
|
||||
kanbanAggregateOperation,
|
||||
}: {
|
||||
kanbanAggregateOperationFieldMetadataId: string | null;
|
||||
kanbanAggregateOperation: AGGREGATE_OPERATIONS;
|
||||
kanbanAggregateOperation: AGGREGATE_OPERATIONS | null;
|
||||
}) =>
|
||||
updateView({
|
||||
id: currentViewId,
|
||||
|
||||
@ -25,6 +25,7 @@ export type MenuItemProps = {
|
||||
isIconDisplayedOnHoverOnly?: boolean;
|
||||
isTooltipOpen?: boolean;
|
||||
LeftIcon?: IconComponent | null;
|
||||
RightIcon?: IconComponent | null;
|
||||
onClick?: (event: MouseEvent<HTMLDivElement>) => void;
|
||||
onMouseEnter?: (event: MouseEvent<HTMLDivElement>) => void;
|
||||
onMouseLeave?: (event: MouseEvent<HTMLDivElement>) => void;
|
||||
@ -40,6 +41,7 @@ export const MenuItem = ({
|
||||
iconButtons,
|
||||
isIconDisplayedOnHoverOnly = true,
|
||||
LeftIcon,
|
||||
RightIcon,
|
||||
onClick,
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
@ -81,6 +83,9 @@ export const MenuItem = ({
|
||||
<LightIconButtonGroup iconButtons={iconButtons} size="small" />
|
||||
)}
|
||||
</div>
|
||||
{RightIcon && (
|
||||
<RightIcon size={theme.icon.size.md} stroke={theme.icon.stroke.sm} />
|
||||
)}
|
||||
{hasSubMenu && (
|
||||
<IconChevronRight
|
||||
size={theme.icon.size.sm}
|
||||
|
||||
Reference in New Issue
Block a user