diff --git a/packages/twenty-front/src/modules/dropdown/hooks/useDropdown.ts b/packages/twenty-front/src/modules/dropdown/hooks/useDropdown.ts index 17b08ef80..14792b216 100644 --- a/packages/twenty-front/src/modules/dropdown/hooks/useDropdown.ts +++ b/packages/twenty-front/src/modules/dropdown/hooks/useDropdown.ts @@ -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, diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdown.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdown.tsx index ce8911d97..b8e20a118 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdown.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdown.tsx @@ -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(); + useCurrentContentId(); return ( { + const { isDropdownOpen } = useDropdown(dropdownId); return (
- - + {!isDropdownOpen && ( + + )}
); diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownContent.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownContent.tsx index 255480d0a..d8b2074fb 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownContent.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownContent.tsx @@ -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 ; case 'aggregateFields': return ; default: diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownContext.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownContext.tsx index d2225bd18..d5e9f8310 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownContext.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownContext.tsx @@ -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; }; diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownFieldsContent.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownFieldsContent.tsx index 995b48eb2..7ab628644 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownFieldsContent.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownFieldsContent.tsx @@ -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 ( <> - + onContentChange('moreAggregateOperationOptions')} + > {getAggregateOperationLabel(aggregateOperation)} @@ -53,6 +68,14 @@ export const RecordBoardColumnHeaderAggregateDropdownFieldsContent = () => { }} LeftIcon={getIcon(fieldMetadata.icon) ?? Icon123} text={fieldMetadata.label} + RightIcon={ + recordIndexKanbanAggregateOperation?.fieldMetadataId === + fieldId && + recordIndexKanbanAggregateOperation?.operation === + aggregateOperation + ? IconCheck + : undefined + } /> ); })} diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownMenuContent.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownMenuContent.tsx index eb1ab6dcb..dc60c6747 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownMenuContent.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownMenuContent.tsx @@ -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({ 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 ( <> @@ -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 + } + /> + { + onContentChange('moreAggregateOperationOptions'); + }} + text={'More options'} + hasSubMenu /> - {Object.entries(availableAggregations).map( - ([ - availableAggregationOperation, - availableAggregationFieldsIdsForOperation, - ]) => - isEmpty(availableAggregationFieldsIdsForOperation) ? ( - <> - ) : ( - { - setAggregateOperation( - availableAggregationOperation as AGGREGATE_OPERATIONS, - ); - setAvailableFieldsForAggregateOperation( - availableAggregationFieldsIdsForOperation, - ); - onContentChange('aggregateFields'); - }} - text={getAggregateOperationLabel( - availableAggregationOperation as AGGREGATE_OPERATIONS, - )} - hasSubMenu - /> - ), - )} ); diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownMenuItem.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownMenuItem.tsx index 2b641776f..69a7fb8ed 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownMenuItem.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownMenuItem.tsx @@ -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 ( - + ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownMoreOptionsContent.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownMoreOptionsContent.tsx new file mode 100644 index 000000000..c14a63adf --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownMoreOptionsContent.tsx @@ -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({ + 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 ( + <> + + More options + + + {Object.entries(availableAggregations) + .filter(([, fields]) => !isEmpty(fields)) + .map( + ([ + availableAggregationOperation, + availableAggregationFieldsIdsForOperation, + ]) => ( + { + setAggregateOperation( + availableAggregationOperation as AGGREGATE_OPERATIONS, + ); + setAvailableFieldsForAggregateOperation( + availableAggregationFieldsIdsForOperation, + ); + onContentChange('aggregateFields'); + }} + text={getAggregateOperationLabel( + availableAggregationOperation as AGGREGATE_OPERATIONS, + )} + hasSubMenu + /> + ), + )} + + + ); + }; diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/__tests__/computeAggregateValueAndLabel.test.ts b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/__tests__/computeAggregateValueAndLabel.test.ts index 5a03afa7b..d16166f63 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/__tests__/computeAggregateValueAndLabel.test.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/__tests__/computeAggregateValueAndLabel.test.ts @@ -141,6 +141,7 @@ describe('computeAggregateValueAndLabel', () => { expect(result).toEqual({ value: 42, label: 'Count', + labelWithFieldName: 'Count', }); }); diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel.ts b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel.ts index 80beb0e39..03a97d0d2 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel.ts @@ -35,6 +35,7 @@ export const computeAggregateValueAndLabel = ({ return { value: data?.[fallbackFieldName]?.[AGGREGATE_OPERATIONS.count], label: `${getAggregateOperationLabel(AGGREGATE_OPERATIONS.count)}`, + labelWithFieldName: `${getAggregateOperationLabel(AGGREGATE_OPERATIONS.count)}`, }; } diff --git a/packages/twenty-front/src/modules/object-record/record-board/types/AggregateContentId.ts b/packages/twenty-front/src/modules/object-record/record-board/types/AggregateContentId.ts deleted file mode 100644 index 800a2d3de..000000000 --- a/packages/twenty-front/src/modules/object-record/record-board/types/AggregateContentId.ts +++ /dev/null @@ -1 +0,0 @@ -export type AggregateContentId = 'aggregateOperations' | 'aggregateFields'; diff --git a/packages/twenty-front/src/modules/object-record/record-board/types/RecordBoardColumnHeaderAggregateContentId.ts b/packages/twenty-front/src/modules/object-record/record-board/types/RecordBoardColumnHeaderAggregateContentId.ts new file mode 100644 index 000000000..8ecb4603b --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/types/RecordBoardColumnHeaderAggregateContentId.ts @@ -0,0 +1,4 @@ +export type RecordBoardColumnHeaderAggregateContentId = + | 'aggregateOperations' + | 'aggregateFields' + | 'moreAggregateOperationOptions'; diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx index 532c9a9f2..00ca89c1a 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx @@ -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, diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx index 098c8d646..f46be75a4 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx @@ -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, diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx index 853a3623d..16319eb45 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx @@ -96,9 +96,12 @@ export const RecordTable = () => { )} - {isAggregateQueryEnabled && !hasRecordGroups && ( - - )} + {isAggregateQueryEnabled && + !hasRecordGroups && + !isRecordTableInitialLoading && + allRecordIds.length > 0 && ( + + )} { + const { + updateViewFieldAggregateOperation, + currentViewFieldAggregateOperation, + } = useViewFieldAggregateOperation(); + + const { dropdownId, resetContent } = useContext( + RecordTableColumnAggregateFooterDropdownContext, + ); + const { closeDropdown } = useDropdown(dropdownId); + return ( + + {aggregateOperations.map((operation) => ( + { + updateViewFieldAggregateOperation(operation); + closeDropdown(); + }} + text={getAggregateOperationLabel(operation)} + RightIcon={ + currentViewFieldAggregateOperation === operation + ? IconCheck + : undefined + } + aria-selected={currentViewFieldAggregateOperation === operation} + /> + ))} + {children} + { + updateViewFieldAggregateOperation(null); + resetContent(); + closeDropdown(); + }} + text={'None'} + RightIcon={ + !isDefined(currentViewFieldAggregateOperation) ? IconCheck : undefined + } + aria-selected={!isDefined(currentViewFieldAggregateOperation)} + /> + + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterDropdown.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterDropdown.tsx deleted file mode 100644 index 46e136d4c..000000000 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterDropdown.tsx +++ /dev/null @@ -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; - 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 ( - <> - - {availableAggregateOperations.map((aggregation) => ( - { - handleAggregationChange(aggregation); - closeDropdown(); - }} - text={getAggregateOperationLabel(aggregation)} - /> - ))} - - - ); -}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterDropdownContent.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterDropdownContent.tsx new file mode 100644 index 000000000..20dca610f --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterDropdownContent.tsx @@ -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 ; + default: + return ; + } +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterDropdownContext.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterDropdownContext.tsx new file mode 100644 index 000000000..e8d108995 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterDropdownContext.tsx @@ -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( + {} as RecordTableColumnAggregateFooterDropdownContextValue, + ); diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterDropdownMoreOptionsContent.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterDropdownMoreOptionsContent.tsx new file mode 100644 index 000000000..649e91382 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterDropdownMoreOptionsContent.tsx @@ -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 ( + <> + + More options + + + + + + ); + }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterMenuContent.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterMenuContent.tsx new file mode 100644 index 000000000..4b9612109 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterMenuContent.tsx @@ -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 ( + <> + + + {otherAvailableAggregateOperation.length > 0 ? ( + { + onContentChange('moreAggregateOperationOptions'); + }} + text={'More options'} + hasSubMenu + /> + ) : null} + + + + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterValue.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterValue.tsx index 45f36eb02..800f551de 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterValue.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterValue.tsx @@ -1,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 (
{ @@ -84,7 +88,7 @@ export const RecordTableColumnAggregateFooterValue = ({ onMouseLeave={() => setIsHovered(false)} > - {isHovered || isDefined(aggregateValue) || isFirstCell ? ( + {shouldShowValue ? ( <> {isDefined(aggregateValue) ? ( diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterWithDropdown.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterWithDropdown.tsx index 13c49fc30..d205d21b5 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterWithDropdown.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterWithDropdown.tsx @@ -1,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(); + 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={ - + + + } dropdownOffset={{ x: -1 }} dropdownPlacement="bottom-start" diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/constants/standardAggregateOperationOptions.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/constants/standardAggregateOperationOptions.tsx new file mode 100644 index 000000000..ec4f26eb0 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/constants/standardAggregateOperationOptions.tsx @@ -0,0 +1,5 @@ +import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; + +export const STANDARD_AGGREGATE_OPERATION_OPTIONS = [ + AGGREGATE_OPERATIONS.count, +]; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/hooks/useAggregateRecordsForRecordTableColumnFooter.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/hooks/useAggregateRecordsForRecordTableColumnFooter.tsx index c9217a78a..d7c66b3e6 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/hooks/useAggregateRecordsForRecordTableColumnFooter.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/hooks/useAggregateRecordsForRecordTableColumnFooter.tsx @@ -6,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( diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/hooks/useViewFieldAggregateOperation.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/hooks/useViewFieldAggregateOperation.tsx new file mode 100644 index 000000000..ebfaa1f52 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/hooks/useViewFieldAggregateOperation.tsx @@ -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, + }; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/states/aggregateOperationForViewFieldState.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/states/viewFieldAggregateOperationState.ts similarity index 70% rename from packages/twenty-front/src/modules/object-record/record-table/record-table-footer/states/aggregateOperationForViewFieldState.ts rename to packages/twenty-front/src/modules/object-record/record-table/record-table-footer/states/viewFieldAggregateOperationState.ts index 7e39ed2b4..babe48606 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/states/aggregateOperationForViewFieldState.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/states/viewFieldAggregateOperationState.ts @@ -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, }); diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/types/RecordTableFooterAggregateContentId.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/types/RecordTableFooterAggregateContentId.tsx new file mode 100644 index 000000000..f788a9503 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/types/RecordTableFooterAggregateContentId.tsx @@ -0,0 +1,2 @@ +export type RecordTableFooterAggregateContentId = + 'moreAggregateOperationOptions'; diff --git a/packages/twenty-front/src/modules/views/hooks/useUpdateViewAggregate.ts b/packages/twenty-front/src/modules/views/hooks/useUpdateViewAggregate.ts index 6e0b8a704..0dface682 100644 --- a/packages/twenty-front/src/modules/views/hooks/useUpdateViewAggregate.ts +++ b/packages/twenty-front/src/modules/views/hooks/useUpdateViewAggregate.ts @@ -13,7 +13,7 @@ export const useUpdateViewAggregate = () => { kanbanAggregateOperation, }: { kanbanAggregateOperationFieldMetadataId: string | null; - kanbanAggregateOperation: AGGREGATE_OPERATIONS; + kanbanAggregateOperation: AGGREGATE_OPERATIONS | null; }) => updateView({ id: currentViewId, diff --git a/packages/twenty-ui/src/navigation/menu-item/components/MenuItem.tsx b/packages/twenty-ui/src/navigation/menu-item/components/MenuItem.tsx index 49c6ae372..b12388698 100644 --- a/packages/twenty-ui/src/navigation/menu-item/components/MenuItem.tsx +++ b/packages/twenty-ui/src/navigation/menu-item/components/MenuItem.tsx @@ -25,6 +25,7 @@ export type MenuItemProps = { isIconDisplayedOnHoverOnly?: boolean; isTooltipOpen?: boolean; LeftIcon?: IconComponent | null; + RightIcon?: IconComponent | null; onClick?: (event: MouseEvent) => void; onMouseEnter?: (event: MouseEvent) => void; onMouseLeave?: (event: MouseEvent) => void; @@ -40,6 +41,7 @@ export const MenuItem = ({ iconButtons, isIconDisplayedOnHoverOnly = true, LeftIcon, + RightIcon, onClick, onMouseEnter, onMouseLeave, @@ -81,6 +83,9 @@ export const MenuItem = ({ )}
+ {RightIcon && ( + + )} {hasSubMenu && (