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 { ObjectOptionsDropdownContextValue } from '@/object-record/object-options-dropdown/states/contexts/ObjectOptionsDropdownContext';
|
||||||
import { RecordBoardColumnHeaderAggregateDropdownContextValue } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownContext';
|
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 { useDropdown as useDropdownUi } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||||
import { Context, useCallback, useContext } from 'react';
|
import { Context, useCallback, useContext } from 'react';
|
||||||
|
|
||||||
export const useDropdown = <
|
export const useDropdown = <
|
||||||
T extends
|
T extends
|
||||||
| RecordBoardColumnHeaderAggregateDropdownContextValue
|
| RecordBoardColumnHeaderAggregateDropdownContextValue
|
||||||
|
| RecordTableColumnAggregateFooterDropdownContextValue
|
||||||
| ObjectOptionsDropdownContextValue,
|
| ObjectOptionsDropdownContextValue,
|
||||||
>({
|
>({
|
||||||
context,
|
context,
|
||||||
|
|||||||
@ -6,8 +6,8 @@ import { RecordBoardColumnHeaderAggregateDropdownComponentInstanceContext } from
|
|||||||
import { RecordBoardColumnHeaderAggregateDropdownButton } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownButton';
|
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 { AggregateDropdownContent } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownContent';
|
||||||
import { RecordBoardColumnHeaderAggregateDropdownContext } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownContext';
|
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 { 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';
|
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||||
|
|
||||||
type RecordBoardColumnHeaderAggregateDropdownProps = {
|
type RecordBoardColumnHeaderAggregateDropdownProps = {
|
||||||
@ -24,13 +24,14 @@ export const RecordBoardColumnHeaderAggregateDropdown = ({
|
|||||||
dropdownId,
|
dropdownId,
|
||||||
}: RecordBoardColumnHeaderAggregateDropdownProps) => {
|
}: RecordBoardColumnHeaderAggregateDropdownProps) => {
|
||||||
const { currentContentId, handleContentChange, handleResetContent } =
|
const { currentContentId, handleContentChange, handleResetContent } =
|
||||||
useCurrentContentId<AggregateContentId>();
|
useCurrentContentId<RecordBoardColumnHeaderAggregateContentId>();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RecordBoardColumnHeaderAggregateDropdownComponentInstanceContext.Provider
|
<RecordBoardColumnHeaderAggregateDropdownComponentInstanceContext.Provider
|
||||||
value={{ instanceId: dropdownId }}
|
value={{ instanceId: dropdownId }}
|
||||||
>
|
>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
|
onClose={handleResetContent}
|
||||||
dropdownId={dropdownId}
|
dropdownId={dropdownId}
|
||||||
dropdownHotkeyScope={{
|
dropdownHotkeyScope={{
|
||||||
scope: RecordBoardColumnHotkeyScope.ColumnHeader,
|
scope: RecordBoardColumnHotkeyScope.ColumnHeader,
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/StyledHeaderDropdownButton';
|
import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/StyledHeaderDropdownButton';
|
||||||
|
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';
|
||||||
|
|
||||||
@ -15,18 +16,25 @@ export const RecordBoardColumnHeaderAggregateDropdownButton = ({
|
|||||||
value?: string | number;
|
value?: string | number;
|
||||||
tooltip?: string;
|
tooltip?: string;
|
||||||
}) => {
|
}) => {
|
||||||
|
const { isDropdownOpen } = useDropdown(dropdownId);
|
||||||
return (
|
return (
|
||||||
<div id={dropdownId}>
|
<div id={dropdownId}>
|
||||||
<StyledButton>
|
<StyledButton>
|
||||||
<Tag text={value ? value.toString() : '-'} color={'transparent'} />
|
<Tag
|
||||||
<AppTooltip
|
text={value ? value.toString() : '-'}
|
||||||
anchorSelect={`#${dropdownId}`}
|
color={'transparent'}
|
||||||
content={tooltip}
|
weight={'regular'}
|
||||||
noArrow
|
|
||||||
place="right"
|
|
||||||
positionStrategy="fixed"
|
|
||||||
delay={TooltipDelay.shortDelay}
|
|
||||||
/>
|
/>
|
||||||
|
{!isDropdownOpen && (
|
||||||
|
<AppTooltip
|
||||||
|
anchorSelect={`#${dropdownId}`}
|
||||||
|
content={tooltip}
|
||||||
|
noArrow
|
||||||
|
place="right"
|
||||||
|
positionStrategy="fixed"
|
||||||
|
delay={TooltipDelay.mediumDelay}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</StyledButton>
|
</StyledButton>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { useDropdown } from '@/dropdown/hooks/useDropdown';
|
|||||||
import { RecordBoardColumnHeaderAggregateDropdownContext } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownContext';
|
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 { RecordBoardColumnHeaderAggregateDropdownFieldsContent } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownFieldsContent';
|
||||||
import { RecordBoardColumnHeaderAggregateDropdownMenuContent } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownMenuContent';
|
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 = () => {
|
export const AggregateDropdownContent = () => {
|
||||||
const { currentContentId } = useDropdown({
|
const { currentContentId } = useDropdown({
|
||||||
@ -9,6 +10,8 @@ export const AggregateDropdownContent = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
switch (currentContentId) {
|
switch (currentContentId) {
|
||||||
|
case 'moreAggregateOperationOptions':
|
||||||
|
return <RecordBoardColumnHeaderAggregateDropdownMoreOptionsContent />;
|
||||||
case 'aggregateFields':
|
case 'aggregateFields':
|
||||||
return <RecordBoardColumnHeaderAggregateDropdownFieldsContent />;
|
return <RecordBoardColumnHeaderAggregateDropdownFieldsContent />;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
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';
|
import { createContext } from 'react';
|
||||||
|
|
||||||
export type RecordBoardColumnHeaderAggregateDropdownContextValue = {
|
export type RecordBoardColumnHeaderAggregateDropdownContextValue = {
|
||||||
objectMetadataItem: ObjectMetadataItem;
|
objectMetadataItem: ObjectMetadataItem;
|
||||||
currentContentId: AggregateContentId | null;
|
currentContentId: RecordBoardColumnHeaderAggregateContentId | null;
|
||||||
onContentChange: (key: AggregateContentId) => void;
|
onContentChange: (key: RecordBoardColumnHeaderAggregateContentId) => void;
|
||||||
resetContent: () => void;
|
resetContent: () => void;
|
||||||
dropdownId: string;
|
dropdownId: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,15 +3,23 @@ import { RecordBoardColumnHeaderAggregateDropdownContext } from '@/object-record
|
|||||||
import { aggregateOperationComponentState } from '@/object-record/record-board/record-board-column/states/aggregateOperationComponentState';
|
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 { availableFieldIdsForAggregateOperationComponentState } from '@/object-record/record-board/record-board-column/states/availableFieldIdsForAggregateOperationComponentState';
|
||||||
import { getAggregateOperationLabel } from '@/object-record/record-board/record-board-column/utils/getAggregateOperationLabel';
|
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 { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader';
|
||||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { useUpdateViewAggregate } from '@/views/hooks/useUpdateViewAggregate';
|
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';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
export const RecordBoardColumnHeaderAggregateDropdownFieldsContent = () => {
|
export const RecordBoardColumnHeaderAggregateDropdownFieldsContent = () => {
|
||||||
const { closeDropdown, resetContent, objectMetadataItem } = useDropdown({
|
const { closeDropdown, objectMetadataItem, onContentChange } = useDropdown({
|
||||||
context: RecordBoardColumnHeaderAggregateDropdownContext,
|
context: RecordBoardColumnHeaderAggregateDropdownContext,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -27,11 +35,18 @@ export const RecordBoardColumnHeaderAggregateDropdownFieldsContent = () => {
|
|||||||
availableFieldIdsForAggregateOperationComponentState,
|
availableFieldIdsForAggregateOperationComponentState,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const recordIndexKanbanAggregateOperation = useRecoilValue(
|
||||||
|
recordIndexKanbanAggregateOperationState,
|
||||||
|
);
|
||||||
|
|
||||||
if (!isDefined(aggregateOperation)) return <></>;
|
if (!isDefined(aggregateOperation)) return <></>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DropdownMenuHeader StartIcon={IconChevronLeft} onClick={resetContent}>
|
<DropdownMenuHeader
|
||||||
|
StartIcon={IconChevronLeft}
|
||||||
|
onClick={() => onContentChange('moreAggregateOperationOptions')}
|
||||||
|
>
|
||||||
{getAggregateOperationLabel(aggregateOperation)}
|
{getAggregateOperationLabel(aggregateOperation)}
|
||||||
</DropdownMenuHeader>
|
</DropdownMenuHeader>
|
||||||
<DropdownMenuItemsContainer>
|
<DropdownMenuItemsContainer>
|
||||||
@ -53,6 +68,14 @@ export const RecordBoardColumnHeaderAggregateDropdownFieldsContent = () => {
|
|||||||
}}
|
}}
|
||||||
LeftIcon={getIcon(fieldMetadata.icon) ?? Icon123}
|
LeftIcon={getIcon(fieldMetadata.icon) ?? Icon123}
|
||||||
text={fieldMetadata.label}
|
text={fieldMetadata.label}
|
||||||
|
RightIcon={
|
||||||
|
recordIndexKanbanAggregateOperation?.fieldMetadataId ===
|
||||||
|
fieldId &&
|
||||||
|
recordIndexKanbanAggregateOperation?.operation ===
|
||||||
|
aggregateOperation
|
||||||
|
? IconCheck
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Key } from 'ts-key-enum';
|
import { Key } from 'ts-key-enum';
|
||||||
import { MenuItem } from 'twenty-ui';
|
import { IconCheck, MenuItem } from 'twenty-ui';
|
||||||
|
|
||||||
import { useDropdown } from '@/dropdown/hooks/useDropdown';
|
import { useDropdown } from '@/dropdown/hooks/useDropdown';
|
||||||
import {
|
import {
|
||||||
@ -7,23 +7,18 @@ import {
|
|||||||
RecordBoardColumnHeaderAggregateDropdownContextValue,
|
RecordBoardColumnHeaderAggregateDropdownContextValue,
|
||||||
} from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownContext';
|
} 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 { 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 { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations';
|
||||||
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
|
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 { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
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 { useUpdateViewAggregate } from '@/views/hooks/useUpdateViewAggregate';
|
||||||
import isEmpty from 'lodash.isempty';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { useMemo } from 'react';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
export const RecordBoardColumnHeaderAggregateDropdownMenuContent = () => {
|
export const RecordBoardColumnHeaderAggregateDropdownMenuContent = () => {
|
||||||
const { objectMetadataItem, onContentChange, closeDropdown } =
|
const { onContentChange, closeDropdown } =
|
||||||
useDropdown<RecordBoardColumnHeaderAggregateDropdownContextValue>({
|
useDropdown<RecordBoardColumnHeaderAggregateDropdownContextValue>({
|
||||||
context: RecordBoardColumnHeaderAggregateDropdownContext,
|
context: RecordBoardColumnHeaderAggregateDropdownContext,
|
||||||
});
|
});
|
||||||
@ -36,24 +31,12 @@ export const RecordBoardColumnHeaderAggregateDropdownMenuContent = () => {
|
|||||||
TableOptionsHotkeyScope.Dropdown,
|
TableOptionsHotkeyScope.Dropdown,
|
||||||
);
|
);
|
||||||
|
|
||||||
const availableAggregations: AvailableFieldsForAggregateOperation = useMemo(
|
|
||||||
() =>
|
|
||||||
getAvailableFieldsIdsForAggregationFromObjectFields(
|
|
||||||
objectMetadataItem.fields,
|
|
||||||
),
|
|
||||||
[objectMetadataItem.fields],
|
|
||||||
);
|
|
||||||
|
|
||||||
const setAggregateOperation = useSetRecoilComponentStateV2(
|
|
||||||
aggregateOperationComponentState,
|
|
||||||
);
|
|
||||||
|
|
||||||
const setAvailableFieldsForAggregateOperation = useSetRecoilComponentStateV2(
|
|
||||||
availableFieldIdsForAggregateOperationComponentState,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { updateViewAggregate } = useUpdateViewAggregate();
|
const { updateViewAggregate } = useUpdateViewAggregate();
|
||||||
|
|
||||||
|
const recordIndexKanbanAggregateOperation = useRecoilValue(
|
||||||
|
recordIndexKanbanAggregateOperationState,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DropdownMenuItemsContainer>
|
<DropdownMenuItemsContainer>
|
||||||
@ -63,35 +46,24 @@ export const RecordBoardColumnHeaderAggregateDropdownMenuContent = () => {
|
|||||||
kanbanAggregateOperationFieldMetadataId: null,
|
kanbanAggregateOperationFieldMetadataId: null,
|
||||||
kanbanAggregateOperation: AGGREGATE_OPERATIONS.count,
|
kanbanAggregateOperation: AGGREGATE_OPERATIONS.count,
|
||||||
});
|
});
|
||||||
|
closeDropdown();
|
||||||
}}
|
}}
|
||||||
text={getAggregateOperationLabel(AGGREGATE_OPERATIONS.count)}
|
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>
|
</DropdownMenuItemsContainer>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,15 +1,22 @@
|
|||||||
import { MenuItem } from 'twenty-ui';
|
import { IconComponent, MenuItem } from 'twenty-ui';
|
||||||
|
|
||||||
export const RecordBoardColumnHeaderAggregateDropdownMenuItem = ({
|
export const RecordBoardColumnHeaderAggregateDropdownMenuItem = ({
|
||||||
onContentChange,
|
onContentChange,
|
||||||
text,
|
text,
|
||||||
hasSubMenu,
|
hasSubMenu,
|
||||||
|
RightIcon,
|
||||||
}: {
|
}: {
|
||||||
onContentChange: () => void;
|
onContentChange: () => void;
|
||||||
hasSubMenu: boolean;
|
hasSubMenu: boolean;
|
||||||
text: string;
|
text: string;
|
||||||
|
RightIcon?: IconComponent | null;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
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({
|
expect(result).toEqual({
|
||||||
value: 42,
|
value: 42,
|
||||||
label: 'Count',
|
label: 'Count',
|
||||||
|
labelWithFieldName: 'Count',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -35,6 +35,7 @@ export const computeAggregateValueAndLabel = ({
|
|||||||
return {
|
return {
|
||||||
value: data?.[fallbackFieldName]?.[AGGREGATE_OPERATIONS.count],
|
value: data?.[fallbackFieldName]?.[AGGREGATE_OPERATIONS.count],
|
||||||
label: `${getAggregateOperationLabel(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 { RecordIndexFiltersToContextStoreEffect } from '@/object-record/record-index/components/RecordIndexFiltersToContextStoreEffect';
|
||||||
import { recordIndexKanbanAggregateOperationState } from '@/object-record/record-index/states/recordIndexKanbanAggregateOperationState';
|
import { recordIndexKanbanAggregateOperationState } from '@/object-record/record-index/states/recordIndexKanbanAggregateOperationState';
|
||||||
import { recordIndexViewFilterGroupsState } from '@/object-record/record-index/states/recordIndexViewFilterGroupsState';
|
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 { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||||
import { ViewBar } from '@/views/components/ViewBar';
|
import { ViewBar } from '@/views/components/ViewBar';
|
||||||
import { ViewField } from '@/views/types/ViewField';
|
import { ViewField } from '@/views/types/ViewField';
|
||||||
@ -126,7 +126,7 @@ export const RecordIndexContainer = () => {
|
|||||||
for (const viewField of viewFields) {
|
for (const viewField of viewFields) {
|
||||||
const aggregateOperationForViewField = snapshot
|
const aggregateOperationForViewField = snapshot
|
||||||
.getLoadable(
|
.getLoadable(
|
||||||
aggregateOperationForViewFieldState({
|
viewFieldAggregateOperationState({
|
||||||
viewFieldId: viewField.id,
|
viewFieldId: viewField.id,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@ -134,7 +134,7 @@ export const RecordIndexContainer = () => {
|
|||||||
|
|
||||||
if (aggregateOperationForViewField !== viewField.aggregateOperation) {
|
if (aggregateOperationForViewField !== viewField.aggregateOperation) {
|
||||||
set(
|
set(
|
||||||
aggregateOperationForViewFieldState({
|
viewFieldAggregateOperationState({
|
||||||
viewFieldId: viewField.id,
|
viewFieldId: viewField.id,
|
||||||
}),
|
}),
|
||||||
viewField.aggregateOperation,
|
viewField.aggregateOperation,
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { useRecordIndexContextOrThrow } from '@/object-record/record-index/conte
|
|||||||
import { useHandleToggleColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleColumnFilter';
|
import { useHandleToggleColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleColumnFilter';
|
||||||
import { useHandleToggleColumnSort } from '@/object-record/record-index/hooks/useHandleToggleColumnSort';
|
import { useHandleToggleColumnSort } from '@/object-record/record-index/hooks/useHandleToggleColumnSort';
|
||||||
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
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 { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
||||||
import { useSetRecordCountInCurrentView } from '@/views/hooks/useSetRecordCountInCurrentView';
|
import { useSetRecordCountInCurrentView } from '@/views/hooks/useSetRecordCountInCurrentView';
|
||||||
import { ViewField } from '@/views/types/ViewField';
|
import { ViewField } from '@/views/types/ViewField';
|
||||||
@ -77,7 +77,7 @@ export const RecordIndexTableContainerEffect = () => {
|
|||||||
(viewField: ViewField) => {
|
(viewField: ViewField) => {
|
||||||
const aggregateOperationForViewField = snapshot
|
const aggregateOperationForViewField = snapshot
|
||||||
.getLoadable(
|
.getLoadable(
|
||||||
aggregateOperationForViewFieldState({
|
viewFieldAggregateOperationState({
|
||||||
viewFieldId: viewField.id,
|
viewFieldId: viewField.id,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@ -85,7 +85,7 @@ export const RecordIndexTableContainerEffect = () => {
|
|||||||
|
|
||||||
if (aggregateOperationForViewField !== viewField.aggregateOperation) {
|
if (aggregateOperationForViewField !== viewField.aggregateOperation) {
|
||||||
set(
|
set(
|
||||||
aggregateOperationForViewFieldState({
|
viewFieldAggregateOperationState({
|
||||||
viewFieldId: viewField.id,
|
viewFieldId: viewField.id,
|
||||||
}),
|
}),
|
||||||
viewField.aggregateOperation,
|
viewField.aggregateOperation,
|
||||||
|
|||||||
@ -96,9 +96,12 @@ export const RecordTable = () => {
|
|||||||
<RecordTableRecordGroupsBody />
|
<RecordTableRecordGroupsBody />
|
||||||
)}
|
)}
|
||||||
<RecordTableStickyEffect />
|
<RecordTableStickyEffect />
|
||||||
{isAggregateQueryEnabled && !hasRecordGroups && (
|
{isAggregateQueryEnabled &&
|
||||||
<RecordTableAggregateFooter endOfTableSticky />
|
!hasRecordGroups &&
|
||||||
)}
|
!isRecordTableInitialLoading &&
|
||||||
|
allRecordIds.length > 0 && (
|
||||||
|
<RecordTableAggregateFooter endOfTableSticky />
|
||||||
|
)}
|
||||||
</StyledTable>
|
</StyledTable>
|
||||||
<DragSelect
|
<DragSelect
|
||||||
dragSelectable={tableBodyRef}
|
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 { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
@ -74,8 +75,11 @@ export const RecordTableColumnAggregateFooterValue = ({
|
|||||||
aggregateLabel?: string;
|
aggregateLabel?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const [isHovered, setIsHovered] = useState(false);
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
|
const { isDropdownOpen } = useDropdown(dropdownId);
|
||||||
const sanitizedId = `tooltip-${dropdownId.replace(/[^a-zA-Z0-9-_]/g, '-')}`;
|
const sanitizedId = `tooltip-${dropdownId.replace(/[^a-zA-Z0-9-_]/g, '-')}`;
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const shouldShowValue =
|
||||||
|
isHovered || isDropdownOpen || isDefined(aggregateValue) || isFirstCell;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
onMouseEnter={() => {
|
onMouseEnter={() => {
|
||||||
@ -84,7 +88,7 @@ export const RecordTableColumnAggregateFooterValue = ({
|
|||||||
onMouseLeave={() => setIsHovered(false)}
|
onMouseLeave={() => setIsHovered(false)}
|
||||||
>
|
>
|
||||||
<StyledCell>
|
<StyledCell>
|
||||||
{isHovered || isDefined(aggregateValue) || isFirstCell ? (
|
{shouldShowValue ? (
|
||||||
<>
|
<>
|
||||||
{isDefined(aggregateValue) ? (
|
{isDefined(aggregateValue) ? (
|
||||||
<StyledValueContainer>
|
<StyledValueContainer>
|
||||||
|
|||||||
@ -1,7 +1,10 @@
|
|||||||
|
import { useCurrentContentId } from '@/dropdown/hooks/useCurrentContentId';
|
||||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
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 { RecordTableColumnAggregateFooterValue } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterValue';
|
||||||
import { useAggregateRecordsForRecordTableColumnFooter } from '@/object-record/record-table/record-table-footer/hooks/useAggregateRecordsForRecordTableColumnFooter';
|
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 { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
||||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||||
import { useToggleScrollWrapper } from '@/ui/utilities/scroll/hooks/useToggleScrollWrapper';
|
import { useToggleScrollWrapper } from '@/ui/utilities/scroll/hooks/useToggleScrollWrapper';
|
||||||
@ -18,6 +21,9 @@ export const RecordTableColumnFooterWithDropdown = ({
|
|||||||
currentRecordGroupId,
|
currentRecordGroupId,
|
||||||
isFirstCell,
|
isFirstCell,
|
||||||
}: RecordTableColumnFooterWithDropdownProps) => {
|
}: RecordTableColumnFooterWithDropdownProps) => {
|
||||||
|
const { currentContentId, handleContentChange, handleResetContent } =
|
||||||
|
useCurrentContentId<RecordTableFooterAggregateContentId>();
|
||||||
|
|
||||||
const { toggleScrollXWrapper, toggleScrollYWrapper } =
|
const { toggleScrollXWrapper, toggleScrollYWrapper } =
|
||||||
useToggleScrollWrapper();
|
useToggleScrollWrapper();
|
||||||
|
|
||||||
@ -27,9 +33,10 @@ export const RecordTableColumnFooterWithDropdown = ({
|
|||||||
}, [toggleScrollXWrapper, toggleScrollYWrapper]);
|
}, [toggleScrollXWrapper, toggleScrollYWrapper]);
|
||||||
|
|
||||||
const handleDropdownClose = useCallback(() => {
|
const handleDropdownClose = useCallback(() => {
|
||||||
|
handleResetContent();
|
||||||
toggleScrollXWrapper(true);
|
toggleScrollXWrapper(true);
|
||||||
toggleScrollYWrapper(true);
|
toggleScrollYWrapper(true);
|
||||||
}, [toggleScrollXWrapper, toggleScrollYWrapper]);
|
}, [handleResetContent, toggleScrollXWrapper, toggleScrollYWrapper]);
|
||||||
|
|
||||||
const { aggregateValue, aggregateLabel } =
|
const { aggregateValue, aggregateLabel } =
|
||||||
useAggregateRecordsForRecordTableColumnFooter(column.fieldMetadataId);
|
useAggregateRecordsForRecordTableColumnFooter(column.fieldMetadataId);
|
||||||
@ -52,10 +59,17 @@ export const RecordTableColumnFooterWithDropdown = ({
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
dropdownComponents={
|
dropdownComponents={
|
||||||
<RecordTableColumnAggregateFooterDropdown
|
<RecordTableColumnAggregateFooterDropdownContext.Provider
|
||||||
column={column}
|
value={{
|
||||||
dropdownId={dropdownId}
|
currentContentId,
|
||||||
/>
|
onContentChange: handleContentChange,
|
||||||
|
resetContent: handleResetContent,
|
||||||
|
dropdownId: dropdownId,
|
||||||
|
fieldMetadataId: column.fieldMetadataId,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RecordTableColumnAggregateFooterDropdownContent />
|
||||||
|
</RecordTableColumnAggregateFooterDropdownContext.Provider>
|
||||||
}
|
}
|
||||||
dropdownOffset={{ x: -1 }}
|
dropdownOffset={{ x: -1 }}
|
||||||
dropdownPlacement="bottom-start"
|
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 { recordIndexFiltersState } from '@/object-record/record-index/states/recordIndexFiltersState';
|
||||||
import { recordIndexViewFilterGroupsState } from '@/object-record/record-index/states/recordIndexViewFilterGroupsState';
|
import { recordIndexViewFilterGroupsState } from '@/object-record/record-index/states/recordIndexViewFilterGroupsState';
|
||||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
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 { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
||||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
@ -43,7 +43,7 @@ export const useAggregateRecordsForRecordTableColumnFooter = (
|
|||||||
)?.id ?? '';
|
)?.id ?? '';
|
||||||
|
|
||||||
const aggregateOperationForViewField = useRecoilValue(
|
const aggregateOperationForViewField = useRecoilValue(
|
||||||
aggregateOperationForViewFieldState({ viewFieldId: viewFieldId }),
|
viewFieldAggregateOperationState({ viewFieldId: viewFieldId }),
|
||||||
);
|
);
|
||||||
|
|
||||||
const fieldName = objectMetadataItem.fields.find(
|
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 { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations';
|
||||||
import { createFamilyState } from '@/ui/utilities/state/utils/createFamilyState';
|
import { createFamilyState } from '@/ui/utilities/state/utils/createFamilyState';
|
||||||
|
|
||||||
export const aggregateOperationForViewFieldState = createFamilyState<
|
export const viewFieldAggregateOperationState = createFamilyState<
|
||||||
AGGREGATE_OPERATIONS | null | undefined,
|
AGGREGATE_OPERATIONS | null | undefined,
|
||||||
{ viewFieldId: string }
|
{ viewFieldId: string }
|
||||||
>({
|
>({
|
||||||
key: 'aggregateOperationForViewFieldState',
|
key: 'viewFieldAggregateOperationState',
|
||||||
defaultValue: null,
|
defaultValue: null,
|
||||||
});
|
});
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
export type RecordTableFooterAggregateContentId =
|
||||||
|
'moreAggregateOperationOptions';
|
||||||
@ -13,7 +13,7 @@ export const useUpdateViewAggregate = () => {
|
|||||||
kanbanAggregateOperation,
|
kanbanAggregateOperation,
|
||||||
}: {
|
}: {
|
||||||
kanbanAggregateOperationFieldMetadataId: string | null;
|
kanbanAggregateOperationFieldMetadataId: string | null;
|
||||||
kanbanAggregateOperation: AGGREGATE_OPERATIONS;
|
kanbanAggregateOperation: AGGREGATE_OPERATIONS | null;
|
||||||
}) =>
|
}) =>
|
||||||
updateView({
|
updateView({
|
||||||
id: currentViewId,
|
id: currentViewId,
|
||||||
|
|||||||
@ -25,6 +25,7 @@ export type MenuItemProps = {
|
|||||||
isIconDisplayedOnHoverOnly?: boolean;
|
isIconDisplayedOnHoverOnly?: boolean;
|
||||||
isTooltipOpen?: boolean;
|
isTooltipOpen?: boolean;
|
||||||
LeftIcon?: IconComponent | null;
|
LeftIcon?: IconComponent | null;
|
||||||
|
RightIcon?: IconComponent | null;
|
||||||
onClick?: (event: MouseEvent<HTMLDivElement>) => void;
|
onClick?: (event: MouseEvent<HTMLDivElement>) => void;
|
||||||
onMouseEnter?: (event: MouseEvent<HTMLDivElement>) => void;
|
onMouseEnter?: (event: MouseEvent<HTMLDivElement>) => void;
|
||||||
onMouseLeave?: (event: MouseEvent<HTMLDivElement>) => void;
|
onMouseLeave?: (event: MouseEvent<HTMLDivElement>) => void;
|
||||||
@ -40,6 +41,7 @@ export const MenuItem = ({
|
|||||||
iconButtons,
|
iconButtons,
|
||||||
isIconDisplayedOnHoverOnly = true,
|
isIconDisplayedOnHoverOnly = true,
|
||||||
LeftIcon,
|
LeftIcon,
|
||||||
|
RightIcon,
|
||||||
onClick,
|
onClick,
|
||||||
onMouseEnter,
|
onMouseEnter,
|
||||||
onMouseLeave,
|
onMouseLeave,
|
||||||
@ -81,6 +83,9 @@ export const MenuItem = ({
|
|||||||
<LightIconButtonGroup iconButtons={iconButtons} size="small" />
|
<LightIconButtonGroup iconButtons={iconButtons} size="small" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{RightIcon && (
|
||||||
|
<RightIcon size={theme.icon.size.md} stroke={theme.icon.stroke.sm} />
|
||||||
|
)}
|
||||||
{hasSubMenu && (
|
{hasSubMenu && (
|
||||||
<IconChevronRight
|
<IconChevronRight
|
||||||
size={theme.icon.size.sm}
|
size={theme.icon.size.sm}
|
||||||
|
|||||||
Reference in New Issue
Block a user