diff --git a/packages/twenty-front/src/modules/object-metadata/states/availableFieldMetadataItemsForSortFamilySelector.ts b/packages/twenty-front/src/modules/object-metadata/states/availableFieldMetadataItemsForSortFamilySelector.ts new file mode 100644 index 000000000..847a4968d --- /dev/null +++ b/packages/twenty-front/src/modules/object-metadata/states/availableFieldMetadataItemsForSortFamilySelector.ts @@ -0,0 +1,26 @@ +import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { filterSortableFieldMetadataItems } from '@/object-metadata/utils/filterSortableFieldMetadataItems'; +import { selectorFamily } from 'recoil'; +import { isDefined } from 'twenty-shared'; + +export const availableFieldMetadataItemsForSortFamilySelector = selectorFamily({ + key: 'availableFieldMetadataItemsForSortFamilySelector', + get: + ({ objectMetadataItemId }: { objectMetadataItemId: string }) => + ({ get }) => { + const objectMetadataItems = get(objectMetadataItemsState); + + const objectMetadataItem = objectMetadataItems.find( + (item) => item.id === objectMetadataItemId, + ); + + if (!isDefined(objectMetadataItem)) { + return []; + } + + const availableFieldMetadataItemsForSort = + objectMetadataItem.fields.filter(filterSortableFieldMetadataItems); + + return availableFieldMetadataItemsForSort; + }, +}); diff --git a/packages/twenty-front/src/modules/object-metadata/utils/filterSortableFieldMetadataItems.ts b/packages/twenty-front/src/modules/object-metadata/utils/filterSortableFieldMetadataItems.ts new file mode 100644 index 000000000..386b850ad --- /dev/null +++ b/packages/twenty-front/src/modules/object-metadata/utils/filterSortableFieldMetadataItems.ts @@ -0,0 +1,13 @@ +import { SORTABLE_FIELD_METADATA_TYPES } from '@/object-metadata/constants/SortableFieldMetadataTypes'; +import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; + +export const filterSortableFieldMetadataItems = (field: FieldMetadataItem) => { + const isSystemField = field.isSystem; + const isFieldActive = field.isActive; + + const isFieldTypeSortable = SORTABLE_FIELD_METADATA_TYPES.includes( + field.type, + ); + + return !isSystemField && isFieldActive && isFieldTypeSortable; +}; diff --git a/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions.ts b/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions.ts index 6143fc6c3..bde0675d4 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions.ts @@ -11,14 +11,6 @@ export const getRelationObjectMetadataNameSingular = ({ return field.relationDefinition?.targetObjectMetadata.nameSingular; }; -export const getRelationObjectMetadataNamePlural = ({ - field, -}: { - field: ObjectMetadataItem['fields'][0]; -}): string | undefined => { - return field.relationDefinition?.targetObjectMetadata.namePlural; -}; - export const getFilterTypeFromFieldType = ( fieldType: FieldMetadataType, ): FilterableFieldType => { diff --git a/packages/twenty-front/src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx b/packages/twenty-front/src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx index b6541cc7c..b07d5dfc6 100644 --- a/packages/twenty-front/src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx +++ b/packages/twenty-front/src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx @@ -6,12 +6,16 @@ import { useCloseSortDropdown } from '@/object-record/object-sort-dropdown/hooks import { useResetRecordSortDropdownSearchInput } from '@/object-record/object-sort-dropdown/hooks/useResetRecordSortDropdownSearchInput'; import { useResetSortDropdown } from '@/object-record/object-sort-dropdown/hooks/useResetSortDropdown'; import { useToggleSortDropdown } from '@/object-record/object-sort-dropdown/hooks/useToggleSortDropdown'; -import { isSortDirectionMenuUnfoldedComponentState } from '@/object-record/object-sort-dropdown/states/isSortDirectionMenuUnfoldedState'; +import { isRecordSortDirectionMenuUnfoldedComponentState } from '@/object-record/object-sort-dropdown/states/isRecordSortDirectionMenuUnfoldedComponentState'; import { objectSortDropdownSearchInputComponentState } from '@/object-record/object-sort-dropdown/states/objectSortDropdownSearchInputComponentState'; import { onSortSelectComponentState } from '@/object-record/object-sort-dropdown/states/onSortSelectScopedState'; -import { selectedSortDirectionComponentState } from '@/object-record/object-sort-dropdown/states/selectedSortDirectionState'; +import { selectedRecordSortDirectionComponentState } from '@/object-record/object-sort-dropdown/states/selectedRecordSortDirectionComponentState'; import { SortDefinition } from '@/object-record/object-sort-dropdown/types/SortDefinition'; import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext'; +import { + RECORD_SORT_DIRECTIONS, + RecordSortDirection, +} from '@/object-record/record-sort/types/RecordSortDirection'; import { hiddenTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/hiddenTableColumnsComponentSelector'; import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; @@ -26,7 +30,7 @@ import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/ import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { availableSortDefinitionsComponentState } from '@/views/states/availableSortDefinitionsComponentState'; import { Trans, useLingui } from '@lingui/react/macro'; -import { SORT_DIRECTIONS, SortDirection } from '../types/SortDirection'; +import { v4 } from 'uuid'; export const StyledInput = styled.input` background: transparent; @@ -79,8 +83,8 @@ export const ObjectSortDropdownButton = ({ objectSortDropdownSearchInputComponentState, ); - const isSortDirectionMenuUnfolded = useRecoilComponentValueV2( - isSortDirectionMenuUnfoldedComponentState, + const isRecordSortDirectionMenuUnfolded = useRecoilComponentValueV2( + isRecordSortDirectionMenuUnfoldedComponentState, ); const { resetSortDropdown } = useResetSortDropdown(); @@ -153,22 +157,23 @@ export const ObjectSortDropdownButton = ({ setObjectSortDropdownSearchInput(''); closeSortDropdown(); onSortSelect?.({ + id: v4(), fieldMetadataId: sortDefinition.fieldMetadataId, - direction: selectedSortDirection, + direction: selectedRecordSortDirection, definition: sortDefinition, }); }; - const [selectedSortDirection, setSelectedSortDirection] = - useRecoilComponentStateV2(selectedSortDirectionComponentState); + const [selectedRecordSortDirection, setSelectedRecordSortDirection] = + useRecoilComponentStateV2(selectedRecordSortDirectionComponentState); - const setIsSortDirectionMenuUnfolded = useSetRecoilComponentStateV2( - isSortDirectionMenuUnfoldedComponentState, + const setIsRecordSortDirectionMenuUnfolded = useSetRecoilComponentStateV2( + isRecordSortDirectionMenuUnfoldedComponentState, ); - const handleSortDirectionClick = (sortDirection: SortDirection) => { - setSelectedSortDirection(sortDirection); - setIsSortDirectionMenuUnfolded(false); + const handleSortDirectionClick = (sortDirection: RecordSortDirection) => { + setSelectedRecordSortDirection(sortDirection); + setIsRecordSortDirectionMenuUnfolded(false); }; const { isDropdownOpen } = useDropdown(OBJECT_SORT_DROPDOWN_ID); @@ -190,10 +195,10 @@ export const ObjectSortDropdownButton = ({ } dropdownComponents={ <> - {isSortDirectionMenuUnfolded && ( + {isRecordSortDirectionMenuUnfolded && ( - {SORT_DIRECTIONS.map((sortDirection, index) => ( + {RECORD_SORT_DIRECTIONS.map((sortDirection, index) => ( handleSortDirectionClick(sortDirection)} @@ -208,10 +213,14 @@ export const ObjectSortDropdownButton = ({ - setIsSortDirectionMenuUnfolded(!isSortDirectionMenuUnfolded) + setIsRecordSortDirectionMenuUnfolded( + !isRecordSortDirectionMenuUnfolded, + ) } > - {selectedSortDirection === 'asc' ? t`Ascending` : t`Descending`} + {selectedRecordSortDirection === 'asc' + ? t`Ascending` + : t`Descending`} { - const setIsSortDirectionMenuUnfolded = useSetRecoilComponentStateV2( - isSortDirectionMenuUnfoldedComponentState, + const setIsRecordSortDirectionMenuUnfolded = useSetRecoilComponentStateV2( + isRecordSortDirectionMenuUnfoldedComponentState, ); - const setSelectedSortDirection = useSetRecoilComponentStateV2( - selectedSortDirectionComponentState, + const setSelectedRecordSortDirection = useSetRecoilComponentStateV2( + selectedRecordSortDirectionComponentState, ); const resetSortDropdown = () => { - setIsSortDirectionMenuUnfolded(false); - setSelectedSortDirection('asc'); + setIsRecordSortDirectionMenuUnfolded(false); + setSelectedRecordSortDirection('asc'); }; return { diff --git a/packages/twenty-front/src/modules/object-record/object-sort-dropdown/hooks/useSortDropdownStates.ts b/packages/twenty-front/src/modules/object-record/object-sort-dropdown/hooks/useSortDropdownStates.ts deleted file mode 100644 index 355e02060..000000000 --- a/packages/twenty-front/src/modules/object-record/object-sort-dropdown/hooks/useSortDropdownStates.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { isSortSelectedComponentState } from '@/object-record/object-sort-dropdown/states/isSortSelectedScopedState'; -import { objectSortDropdownSearchInputComponentState } from '@/object-record/object-sort-dropdown/states/objectSortDropdownSearchInputComponentState'; -import { onSortSelectComponentState } from '@/object-record/object-sort-dropdown/states/onSortSelectScopedState'; - -export const useSortDropdownStates = (scopeId: string) => { - const isSortSelectedState = isSortSelectedComponentState.atomFamily({ - instanceId: scopeId, - }); - - const onSortSelectState = onSortSelectComponentState.atomFamily({ - instanceId: scopeId, - }); - - const objectSortDropdownSearchInputState = - objectSortDropdownSearchInputComponentState.atomFamily({ - instanceId: scopeId, - }); - - return { - isSortSelectedState, - onSortSelectState, - objectSortDropdownSearchInputState, - }; -}; diff --git a/packages/twenty-front/src/modules/object-record/object-sort-dropdown/states/isSortSelectedScopedState.ts b/packages/twenty-front/src/modules/object-record/object-sort-dropdown/states/isRecordSortDirectionMenuUnfoldedComponentState.ts similarity index 50% rename from packages/twenty-front/src/modules/object-record/object-sort-dropdown/states/isSortSelectedScopedState.ts rename to packages/twenty-front/src/modules/object-record/object-sort-dropdown/states/isRecordSortDirectionMenuUnfoldedComponentState.ts index c6de82532..26012b1b9 100644 --- a/packages/twenty-front/src/modules/object-record/object-sort-dropdown/states/isSortSelectedScopedState.ts +++ b/packages/twenty-front/src/modules/object-record/object-sort-dropdown/states/isRecordSortDirectionMenuUnfoldedComponentState.ts @@ -1,8 +1,9 @@ import { ObjectSortDropdownComponentInstanceContext } from '@/object-record/object-sort-dropdown/states/context/ObjectSortDropdownComponentInstanceContext'; import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; -export const isSortSelectedComponentState = createComponentStateV2({ - key: 'isSortSelectedComponentState', - defaultValue: false, - componentInstanceContext: ObjectSortDropdownComponentInstanceContext, -}); +export const isRecordSortDirectionMenuUnfoldedComponentState = + createComponentStateV2({ + key: 'isRecordSortDirectionMenuUnfoldedComponentState', + defaultValue: false, + componentInstanceContext: ObjectSortDropdownComponentInstanceContext, + }); diff --git a/packages/twenty-front/src/modules/object-record/object-sort-dropdown/states/isSortDirectionMenuUnfoldedState.ts b/packages/twenty-front/src/modules/object-record/object-sort-dropdown/states/isRecordSortSelectedComponentState.ts similarity index 78% rename from packages/twenty-front/src/modules/object-record/object-sort-dropdown/states/isSortDirectionMenuUnfoldedState.ts rename to packages/twenty-front/src/modules/object-record/object-sort-dropdown/states/isRecordSortSelectedComponentState.ts index 4a50ee612..669c69edd 100644 --- a/packages/twenty-front/src/modules/object-record/object-sort-dropdown/states/isSortDirectionMenuUnfoldedState.ts +++ b/packages/twenty-front/src/modules/object-record/object-sort-dropdown/states/isRecordSortSelectedComponentState.ts @@ -1,9 +1,9 @@ import { ObjectSortDropdownComponentInstanceContext } from '@/object-record/object-sort-dropdown/states/context/ObjectSortDropdownComponentInstanceContext'; import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; -export const isSortDirectionMenuUnfoldedComponentState = +export const isRecordSortSelectedComponentState = createComponentStateV2({ - key: 'isSortDirectionMenuUnfoldedComponentState', + key: 'isRecordSortSelectedComponentState', defaultValue: false, componentInstanceContext: ObjectSortDropdownComponentInstanceContext, }); diff --git a/packages/twenty-front/src/modules/object-record/object-sort-dropdown/states/onSortSelectScopedState.ts b/packages/twenty-front/src/modules/object-record/object-sort-dropdown/states/onSortSelectScopedState.ts index 7c844e0b2..6a63272ce 100644 --- a/packages/twenty-front/src/modules/object-record/object-sort-dropdown/states/onSortSelectScopedState.ts +++ b/packages/twenty-front/src/modules/object-record/object-sort-dropdown/states/onSortSelectScopedState.ts @@ -1,9 +1,9 @@ import { ObjectSortDropdownComponentInstanceContext } from '@/object-record/object-sort-dropdown/states/context/ObjectSortDropdownComponentInstanceContext'; +import { RecordSort } from '@/object-record/record-sort/types/RecordSort'; import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; -import { Sort } from '../types/Sort'; export const onSortSelectComponentState = createComponentStateV2< - ((sort: Sort) => void) | undefined + ((sort: RecordSort) => void) | undefined >({ key: 'onSortSelectComponentState', defaultValue: undefined, diff --git a/packages/twenty-front/src/modules/object-record/object-sort-dropdown/states/selectedSortDirectionState.ts b/packages/twenty-front/src/modules/object-record/object-sort-dropdown/states/selectedRecordSortDirectionComponentState.ts similarity index 59% rename from packages/twenty-front/src/modules/object-record/object-sort-dropdown/states/selectedSortDirectionState.ts rename to packages/twenty-front/src/modules/object-record/object-sort-dropdown/states/selectedRecordSortDirectionComponentState.ts index f15f91171..6796150a5 100644 --- a/packages/twenty-front/src/modules/object-record/object-sort-dropdown/states/selectedSortDirectionState.ts +++ b/packages/twenty-front/src/modules/object-record/object-sort-dropdown/states/selectedRecordSortDirectionComponentState.ts @@ -1,10 +1,10 @@ import { ObjectSortDropdownComponentInstanceContext } from '@/object-record/object-sort-dropdown/states/context/ObjectSortDropdownComponentInstanceContext'; -import { SortDirection } from '@/object-record/object-sort-dropdown/types/SortDirection'; +import { RecordSortDirection } from '@/object-record/record-sort/types/RecordSortDirection'; import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; -export const selectedSortDirectionComponentState = - createComponentStateV2({ - key: 'selectedSortDirectionComponentState', +export const selectedRecordSortDirectionComponentState = + createComponentStateV2({ + key: 'selectedRecordSortDirectionComponentState', defaultValue: 'asc', componentInstanceContext: ObjectSortDropdownComponentInstanceContext, }); diff --git a/packages/twenty-front/src/modules/object-record/object-sort-dropdown/types/Sort.ts b/packages/twenty-front/src/modules/object-record/object-sort-dropdown/types/Sort.ts deleted file mode 100644 index 64a6b25da..000000000 --- a/packages/twenty-front/src/modules/object-record/object-sort-dropdown/types/Sort.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { SortDefinition } from './SortDefinition'; -import { SortDirection } from './SortDirection'; - -export type Sort = { - fieldMetadataId: string; - direction: SortDirection; - definition: SortDefinition; -}; diff --git a/packages/twenty-front/src/modules/object-record/object-sort-dropdown/types/SortDefinition.ts b/packages/twenty-front/src/modules/object-record/object-sort-dropdown/types/SortDefinition.ts index 23fcdb63f..4f016ccb3 100644 --- a/packages/twenty-front/src/modules/object-record/object-sort-dropdown/types/SortDefinition.ts +++ b/packages/twenty-front/src/modules/object-record/object-sort-dropdown/types/SortDefinition.ts @@ -1,8 +1,5 @@ -import { SortDirection } from './SortDirection'; - export type SortDefinition = { fieldMetadataId: string; label: string; iconName: string; - getOrderByTemplate?: (direction: SortDirection) => any[]; }; diff --git a/packages/twenty-front/src/modules/object-record/object-sort-dropdown/types/SortDirection.ts b/packages/twenty-front/src/modules/object-record/object-sort-dropdown/types/SortDirection.ts deleted file mode 100644 index 5835c6630..000000000 --- a/packages/twenty-front/src/modules/object-record/object-sort-dropdown/types/SortDirection.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const SORT_DIRECTIONS = ['asc', 'desc'] as const; - -export type SortDirection = (typeof SORT_DIRECTIONS)[number]; diff --git a/packages/twenty-front/src/modules/object-record/object-sort-dropdown/utils/__tests__/turnSortsIntoOrderBy.test.ts b/packages/twenty-front/src/modules/object-record/object-sort-dropdown/utils/__tests__/turnSortsIntoOrderBy.test.ts index 4b0b58c2e..422aa7836 100644 --- a/packages/twenty-front/src/modules/object-record/object-sort-dropdown/utils/__tests__/turnSortsIntoOrderBy.test.ts +++ b/packages/twenty-front/src/modules/object-record/object-sort-dropdown/utils/__tests__/turnSortsIntoOrderBy.test.ts @@ -1,8 +1,8 @@ import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { Sort } from '@/object-record/object-sort-dropdown/types/Sort'; import { SortDefinition } from '@/object-record/object-sort-dropdown/types/SortDefinition'; import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy'; +import { RecordSort } from '@/object-record/record-sort/types/RecordSort'; const sortDefinition: SortDefinition = { fieldMetadataId: 'id', @@ -42,8 +42,9 @@ describe('turnSortsIntoOrderBy', () => { }); it('should create OrderByField with single sort', () => { - const sorts: Sort[] = [ + const sorts: RecordSort[] = [ { + id: 'id', fieldMetadataId: 'field1', direction: 'asc', definition: sortDefinition, @@ -56,13 +57,15 @@ describe('turnSortsIntoOrderBy', () => { }); it('should create OrderByField with multiple sorts', () => { - const sorts: Sort[] = [ + const sorts: RecordSort[] = [ { + id: 'id', fieldMetadataId: 'field1', direction: 'asc', definition: sortDefinition, }, { + id: 'id', fieldMetadataId: 'field2', direction: 'desc', definition: sortDefinition, @@ -82,8 +85,9 @@ describe('turnSortsIntoOrderBy', () => { }); it('should ignore if field not found', () => { - const sorts: Sort[] = [ + const sorts: RecordSort[] = [ { + id: 'id', fieldMetadataId: 'invalidField', direction: 'asc', definition: sortDefinition, @@ -95,8 +99,9 @@ describe('turnSortsIntoOrderBy', () => { }); it('should not return position for remotes', () => { - const sorts: Sort[] = [ + const sorts: RecordSort[] = [ { + id: 'id', fieldMetadataId: 'invalidField', direction: 'asc', definition: sortDefinition, diff --git a/packages/twenty-front/src/modules/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy.ts b/packages/twenty-front/src/modules/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy.ts index c2946592d..c64999443 100644 --- a/packages/twenty-front/src/modules/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy.ts +++ b/packages/twenty-front/src/modules/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy.ts @@ -8,12 +8,12 @@ import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { getOrderByForFieldMetadataType } from '@/object-metadata/utils/getOrderByForFieldMetadataType'; +import { RecordSort } from '@/object-record/record-sort/types/RecordSort'; import { OrderBy } from '@/types/OrderBy'; -import { Sort } from '../types/Sort'; export const turnSortsIntoOrderBy = ( objectMetadataItem: ObjectMetadataItem, - sorts: Sort[], + sorts: RecordSort[], ): RecordGqlOperationOrderBy => { const fields: Pick[] = objectMetadataItem?.fields ?? []; diff --git a/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleToggleColumnSort.ts b/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleToggleColumnSort.ts index 5aefe00ef..634d0b95a 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleToggleColumnSort.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleToggleColumnSort.ts @@ -2,9 +2,11 @@ import { useCallback } from 'react'; import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; -import { Sort } from '@/object-record/object-sort-dropdown/types/Sort'; +import { useUpsertRecordSort } from '@/object-record/record-sort/hooks/useUpsertRecordSort'; +import { RecordSort } from '@/object-record/record-sort/types/RecordSort'; import { useUpsertCombinedViewSorts } from '@/views/hooks/useUpsertCombinedViewSorts'; import { isDefined } from 'twenty-shared'; +import { v4 } from 'uuid'; type UseHandleToggleColumnSortProps = { objectNameSingular: string; @@ -24,6 +26,8 @@ export const useHandleToggleColumnSort = ({ const { upsertCombinedViewSort } = useUpsertCombinedViewSorts(viewBarId); + const { upsertRecordSort } = useUpsertRecordSort(); + const handleToggleColumnSort = useCallback( (fieldMetadataId: string) => { const correspondingColumnDefinition = columnDefinitions.find( @@ -33,7 +37,8 @@ export const useHandleToggleColumnSort = ({ if (!isDefined(correspondingColumnDefinition)) return; - const newSort: Sort = { + const newSort: RecordSort = { + id: v4(), fieldMetadataId, definition: { fieldMetadataId, @@ -44,8 +49,9 @@ export const useHandleToggleColumnSort = ({ }; upsertCombinedViewSort(newSort); + upsertRecordSort(newSort); }, - [columnDefinitions, upsertCombinedViewSort], + [columnDefinitions, upsertCombinedViewSort, upsertRecordSort], ); return handleToggleColumnSort; diff --git a/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexSortsState.ts b/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexSortsState.ts index 2d839afdd..9ce465acb 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexSortsState.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexSortsState.ts @@ -1,8 +1,7 @@ +import { RecordSort } from '@/object-record/record-sort/types/RecordSort'; import { createState } from '@ui/utilities/state/utils/createState'; -import { Sort } from '@/object-record/object-sort-dropdown/types/Sort'; - -export const recordIndexSortsState = createState({ +export const recordIndexSortsState = createState({ key: 'recordIndexSortsState', defaultValue: [], }); diff --git a/packages/twenty-front/src/modules/object-record/record-sort/hooks/useSortableFieldMetadataItemsInRecordIndexContext.ts b/packages/twenty-front/src/modules/object-record/record-sort/hooks/useSortableFieldMetadataItemsInRecordIndexContext.ts new file mode 100644 index 000000000..6e5e11ecd --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-sort/hooks/useSortableFieldMetadataItemsInRecordIndexContext.ts @@ -0,0 +1,15 @@ +import { availableFieldMetadataItemsForSortFamilySelector } from '@/object-metadata/states/availableFieldMetadataItemsForSortFamilySelector'; +import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext'; +import { useRecoilValue } from 'recoil'; + +export const useSortableFieldMetadataItemsInRecordIndexContext = () => { + const { objectMetadataItem } = useRecordIndexContextOrThrow(); + + const sortableFieldMetadataItems = useRecoilValue( + availableFieldMetadataItemsForSortFamilySelector({ + objectMetadataItemId: objectMetadataItem.id, + }), + ); + + return { sortableFieldMetadataItems }; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-sort/hooks/useUpsertRecordSort.ts b/packages/twenty-front/src/modules/object-record/record-sort/hooks/useUpsertRecordSort.ts new file mode 100644 index 000000000..95aea458a --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-sort/hooks/useUpsertRecordSort.ts @@ -0,0 +1,58 @@ +import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState'; +import { RecordSort } from '@/object-record/record-sort/types/RecordSort'; +import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; +import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; +import { useRecoilCallback } from 'recoil'; + +export const useUpsertRecordSort = () => { + const currentRecordSortsCallbackState = useRecoilComponentCallbackStateV2( + currentRecordSortsComponentState, + ); + + const upsertRecordSort = useRecoilCallback( + ({ set, snapshot }) => + (recordSortToSet: RecordSort) => { + const currentRecordSorts = getSnapshotValue( + snapshot, + currentRecordSortsCallbackState, + ); + + const hasFoundRecordSortInCurrentRecordSorts = currentRecordSorts.some( + (existingSort) => + existingSort.fieldMetadataId === recordSortToSet.fieldMetadataId, + ); + + if (!hasFoundRecordSortInCurrentRecordSorts) { + set(currentRecordSortsCallbackState, [ + ...currentRecordSorts, + recordSortToSet, + ]); + } else { + set(currentRecordSortsCallbackState, (currentRecordSorts) => { + const newCurrentRecordSorts = [...currentRecordSorts]; + + const indexOfSortToUpdate = newCurrentRecordSorts.findIndex( + (existingSort) => + existingSort.fieldMetadataId === + recordSortToSet.fieldMetadataId, + ); + + if (indexOfSortToUpdate < 0) { + return newCurrentRecordSorts; + } + + newCurrentRecordSorts[indexOfSortToUpdate] = { + ...recordSortToSet, + }; + + return newCurrentRecordSorts; + }); + } + }, + [currentRecordSortsCallbackState], + ); + + return { + upsertRecordSort, + }; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/states/tableSortsComponentState.ts b/packages/twenty-front/src/modules/object-record/record-table/states/tableSortsComponentState.ts index 8f1d82158..ff280e8fe 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/states/tableSortsComponentState.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/states/tableSortsComponentState.ts @@ -1,8 +1,8 @@ +import { RecordSort } from '@/object-record/record-sort/types/RecordSort'; import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext'; import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; -import { Sort } from '../../object-sort-dropdown/types/Sort'; -export const tableSortsComponentState = createComponentStateV2({ +export const tableSortsComponentState = createComponentStateV2({ key: 'tableSortsComponentState', defaultValue: [], componentInstanceContext: RecordTableComponentInstanceContext, diff --git a/packages/twenty-front/src/modules/views/components/EditableSortChip.tsx b/packages/twenty-front/src/modules/views/components/EditableSortChip.tsx index 87ed8e91d..8e55d117d 100644 --- a/packages/twenty-front/src/modules/views/components/EditableSortChip.tsx +++ b/packages/twenty-front/src/modules/views/components/EditableSortChip.tsx @@ -1,36 +1,42 @@ import { IconArrowDown, IconArrowUp } from 'twenty-ui'; -import { Sort } from '@/object-record/object-sort-dropdown/types/Sort'; +import { useUpsertRecordSort } from '@/object-record/record-sort/hooks/useUpsertRecordSort'; +import { RecordSort } from '@/object-record/record-sort/types/RecordSort'; import { SortOrFilterChip } from '@/views/components/SortOrFilterChip'; import { useDeleteCombinedViewSorts } from '@/views/hooks/useDeleteCombinedViewSorts'; import { useUpsertCombinedViewSorts } from '@/views/hooks/useUpsertCombinedViewSorts'; type EditableSortChipProps = { - viewSort: Sort; + recordSort: RecordSort; }; -export const EditableSortChip = ({ viewSort }: EditableSortChipProps) => { +export const EditableSortChip = ({ recordSort }: EditableSortChipProps) => { const { deleteCombinedViewSort } = useDeleteCombinedViewSorts(); const { upsertCombinedViewSort } = useUpsertCombinedViewSorts(); + const { upsertRecordSort } = useUpsertRecordSort(); + const handleRemoveClick = () => { - deleteCombinedViewSort(viewSort.fieldMetadataId); + deleteCombinedViewSort(recordSort.fieldMetadataId); }; const handleClick = () => { - upsertCombinedViewSort({ - ...viewSort, - direction: viewSort.direction === 'asc' ? 'desc' : 'asc', - }); + const newSort: RecordSort = { + ...recordSort, + direction: recordSort.direction === 'asc' ? 'desc' : 'asc', + }; + + upsertCombinedViewSort(newSort); + upsertRecordSort(newSort); }; return ( diff --git a/packages/twenty-front/src/modules/views/components/ViewBar.tsx b/packages/twenty-front/src/modules/views/components/ViewBar.tsx index 302da4be4..7b9ae7fff 100644 --- a/packages/twenty-front/src/modules/views/components/ViewBar.tsx +++ b/packages/twenty-front/src/modules/views/components/ViewBar.tsx @@ -19,6 +19,7 @@ import { FiltersHotkeyScope } from '@/object-record/object-filter-dropdown/types import { VIEW_SORT_DROPDOWN_ID } from '@/object-record/object-sort-dropdown/constants/ViewSortDropdownId'; import { ObjectSortDropdownComponentInstanceContext } from '@/object-record/object-sort-dropdown/states/context/ObjectSortDropdownComponentInstanceContext'; import { ViewBarRecordFilterEffect } from '@/views/components/ViewBarRecordFilterEffect'; +import { ViewBarRecordSortEffect } from '@/views/components/ViewBarRecordSortEffect'; import { UpdateViewButtonGroup } from './UpdateViewButtonGroup'; import { ViewBarDetails } from './ViewBarDetails'; @@ -48,6 +49,7 @@ export const ViewBar = ({ value={{ instanceId: VIEW_SORT_DROPDOWN_ID }} > + diff --git a/packages/twenty-front/src/modules/views/components/ViewBarDetails.tsx b/packages/twenty-front/src/modules/views/components/ViewBarDetails.tsx index 7939ff949..0363c6248 100644 --- a/packages/twenty-front/src/modules/views/components/ViewBarDetails.tsx +++ b/packages/twenty-front/src/modules/views/components/ViewBarDetails.tsx @@ -204,8 +204,11 @@ export const ViewBarDetails = ({ {mapViewSortsToSorts( currentViewWithCombinedFiltersAndSorts?.viewSorts ?? [], availableSortDefinitions, - ).map((sort) => ( - + ).map((recordSort) => ( + ))} {isNonEmptyArray(recordFilters) && isNonEmptyArray( diff --git a/packages/twenty-front/src/modules/views/components/ViewBarRecordFilterEffect.tsx b/packages/twenty-front/src/modules/views/components/ViewBarRecordFilterEffect.tsx index 8606f537a..55b607acc 100644 --- a/packages/twenty-front/src/modules/views/components/ViewBarRecordFilterEffect.tsx +++ b/packages/twenty-front/src/modules/views/components/ViewBarRecordFilterEffect.tsx @@ -21,6 +21,12 @@ export const ViewBarRecordFilterEffect = () => { contextStoreCurrentObjectMetadataItemComponentState, ); + const currentView = useRecoilValue( + prefetchViewFromViewIdFamilySelector({ + viewId: currentViewId ?? '', + }), + ); + const [ hasInitializedCurrentRecordFilters, setHasInitializedCurrentRecordFilters, @@ -43,12 +49,6 @@ export const ViewBarRecordFilterEffect = () => { contextStoreCurrentObjectMetadataItem?.id, ); - const currentView = useRecoilValue( - prefetchViewFromViewIdFamilySelector({ - viewId: currentViewId ?? '', - }), - ); - useEffect(() => { if (isDefined(currentView) && !hasInitializedCurrentRecordFilters) { if ( diff --git a/packages/twenty-front/src/modules/views/components/ViewBarRecordSortEffect.tsx b/packages/twenty-front/src/modules/views/components/ViewBarRecordSortEffect.tsx new file mode 100644 index 000000000..9692bc1c5 --- /dev/null +++ b/packages/twenty-front/src/modules/views/components/ViewBarRecordSortEffect.tsx @@ -0,0 +1,83 @@ +import { contextStoreCurrentObjectMetadataItemComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemComponentState'; +import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState'; +import { availableFieldMetadataItemsForSortFamilySelector } from '@/object-metadata/states/availableFieldMetadataItemsForSortFamilySelector'; +import { formatFieldMetadataItemsAsSortDefinitions } from '@/object-metadata/utils/formatFieldMetadataItemsAsSortDefinitions'; +import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState'; +import { prefetchViewFromViewIdFamilySelector } from '@/prefetch/states/selector/prefetchViewFromViewIdFamilySelector'; +import { useRecoilComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyStateV2'; + +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; +import { hasInitializedCurrentRecordSortsComponentFamilyState } from '@/views/states/hasInitializedCurrentRecordSortsComponentFamilyState'; + +import { mapViewSortsToSorts } from '@/views/utils/mapViewSortsToSorts'; +import { useEffect } from 'react'; +import { useRecoilValue } from 'recoil'; +import { isDefined } from 'twenty-shared'; + +export const ViewBarRecordSortEffect = () => { + const currentViewId = useRecoilComponentValueV2( + contextStoreCurrentViewIdComponentState, + ); + + const contextStoreCurrentObjectMetadataItem = useRecoilComponentValueV2( + contextStoreCurrentObjectMetadataItemComponentState, + ); + + const currentView = useRecoilValue( + prefetchViewFromViewIdFamilySelector({ + viewId: currentViewId ?? '', + }), + ); + + const [ + hasInitializedCurrentRecordSorts, + setHasInitializedCurrentRecordSorts, + ] = useRecoilComponentFamilyStateV2( + hasInitializedCurrentRecordSortsComponentFamilyState, + { + viewId: currentViewId ?? undefined, + }, + ); + + const setCurrentRecordSorts = useSetRecoilComponentStateV2( + currentRecordSortsComponentState, + ); + + const sortableFieldMetadataItems = useRecoilValue( + availableFieldMetadataItemsForSortFamilySelector({ + objectMetadataItemId: contextStoreCurrentObjectMetadataItem?.id, + }), + ); + + useEffect(() => { + if (isDefined(currentView) && !hasInitializedCurrentRecordSorts) { + if ( + currentView.objectMetadataId !== + contextStoreCurrentObjectMetadataItem?.id + ) { + return; + } + + const sortDefinitions = formatFieldMetadataItemsAsSortDefinitions({ + fields: sortableFieldMetadataItems, + }); + + if (isDefined(currentView)) { + setCurrentRecordSorts( + mapViewSortsToSorts(currentView.viewSorts, sortDefinitions), + ); + setHasInitializedCurrentRecordSorts(true); + } + } + }, [ + hasInitializedCurrentRecordSorts, + currentView, + sortableFieldMetadataItems, + setCurrentRecordSorts, + contextStoreCurrentObjectMetadataItem, + setHasInitializedCurrentRecordSorts, + ]); + + return null; +}; diff --git a/packages/twenty-front/src/modules/views/components/ViewBarSortEffect.tsx b/packages/twenty-front/src/modules/views/components/ViewBarSortEffect.tsx index 5e13f2d17..ad952c745 100644 --- a/packages/twenty-front/src/modules/views/components/ViewBarSortEffect.tsx +++ b/packages/twenty-front/src/modules/views/components/ViewBarSortEffect.tsx @@ -1,7 +1,8 @@ import { useEffect } from 'react'; import { onSortSelectComponentState } from '@/object-record/object-sort-dropdown/states/onSortSelectScopedState'; -import { Sort } from '@/object-record/object-sort-dropdown/types/Sort'; +import { useUpsertRecordSort } from '@/object-record/record-sort/hooks/useUpsertRecordSort'; +import { RecordSort } from '@/object-record/record-sort/types/RecordSort'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { useUpsertCombinedViewSorts } from '@/views/hooks/useUpsertCombinedViewSorts'; @@ -16,6 +17,8 @@ export const ViewBarSortEffect = () => { availableSortDefinitionsComponentState, ); + const { upsertRecordSort } = useUpsertRecordSort(); + const setOnSortSelect = useSetRecoilComponentStateV2( onSortSelectComponentState, ); @@ -28,9 +31,10 @@ export const ViewBarSortEffect = () => { if (isDefined(availableSortDefinitions)) { setAvailableSortDefinitionsInSortDropdown(availableSortDefinitions); } - setOnSortSelect(() => (sort: Sort | null) => { + setOnSortSelect(() => (sort: RecordSort | null) => { if (isDefined(sort)) { upsertCombinedViewSort(sort); + upsertRecordSort(sort); } }); }, [ @@ -38,6 +42,7 @@ export const ViewBarSortEffect = () => { setAvailableSortDefinitionsInSortDropdown, setOnSortSelect, upsertCombinedViewSort, + upsertRecordSort, ]); return <>; diff --git a/packages/twenty-front/src/modules/views/hooks/useUpsertCombinedViewSorts.ts b/packages/twenty-front/src/modules/views/hooks/useUpsertCombinedViewSorts.ts index 0d9f1f283..c50392d50 100644 --- a/packages/twenty-front/src/modules/views/hooks/useUpsertCombinedViewSorts.ts +++ b/packages/twenty-front/src/modules/views/hooks/useUpsertCombinedViewSorts.ts @@ -2,7 +2,7 @@ import { useRecoilCallback } from 'recoil'; import { v4 } from 'uuid'; import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState'; -import { Sort } from '@/object-record/object-sort-dropdown/types/Sort'; +import { RecordSort } from '@/object-record/record-sort/types/RecordSort'; import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { useGetViewFromPrefetchState } from '@/views/hooks/useGetViewFromPrefetchState'; @@ -32,7 +32,7 @@ export const useUpsertCombinedViewSorts = (viewBarComponentId?: string) => { const upsertCombinedViewSort = useRecoilCallback( ({ snapshot, set }) => - async (upsertedSort: Sort) => { + async (upsertedSort: RecordSort) => { const currentViewId = getSnapshotValue( snapshot, currentViewIdCallbackState, diff --git a/packages/twenty-front/src/modules/views/states/hasInitializedCurrentRecordSortsComponentFamilyState.ts b/packages/twenty-front/src/modules/views/states/hasInitializedCurrentRecordSortsComponentFamilyState.ts new file mode 100644 index 000000000..1065daf69 --- /dev/null +++ b/packages/twenty-front/src/modules/views/states/hasInitializedCurrentRecordSortsComponentFamilyState.ts @@ -0,0 +1,9 @@ +import { RecordSortsComponentInstanceContext } from '@/object-record/record-sort/states/context/RecordSortsComponentInstanceContext'; +import { createComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentFamilyStateV2'; + +export const hasInitializedCurrentRecordSortsComponentFamilyState = + createComponentFamilyStateV2({ + key: 'hasInitializedCurrentRecordSortsComponentFamilyState', + defaultValue: false, + componentInstanceContext: RecordSortsComponentInstanceContext, + }); diff --git a/packages/twenty-front/src/modules/views/types/ViewSort.ts b/packages/twenty-front/src/modules/views/types/ViewSort.ts index 67d48fc59..dcb4ff9f2 100644 --- a/packages/twenty-front/src/modules/views/types/ViewSort.ts +++ b/packages/twenty-front/src/modules/views/types/ViewSort.ts @@ -1,8 +1,8 @@ -import { SortDirection } from '@/object-record/object-sort-dropdown/types/SortDirection'; +import { RecordSortDirection } from '@/object-record/record-sort/types/RecordSortDirection'; export type ViewSort = { __typename: 'ViewSort'; id: string; fieldMetadataId: string; - direction: SortDirection; + direction: RecordSortDirection; }; diff --git a/packages/twenty-front/src/modules/views/utils/__tests__/viewMapFunctions.test.ts b/packages/twenty-front/src/modules/views/utils/__tests__/viewMapFunctions.test.ts index bb39143d5..01a7347bc 100644 --- a/packages/twenty-front/src/modules/views/utils/__tests__/viewMapFunctions.test.ts +++ b/packages/twenty-front/src/modules/views/utils/__tests__/viewMapFunctions.test.ts @@ -1,6 +1,6 @@ -import { Sort } from '@/object-record/object-sort-dropdown/types/Sort'; import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; +import { RecordSort } from '@/object-record/record-sort/types/RecordSort'; import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition'; import { ViewField } from '@/views/types/ViewField'; import { ViewFilter } from '@/views/types/ViewFilter'; @@ -39,8 +39,9 @@ describe('mapViewSortsToSorts', () => { direction: 'asc', }, ]; - const expectedSorts: Sort[] = [ + const expectedSorts: RecordSort[] = [ { + id: 'id', fieldMetadataId: '05731f68-6e7a-4903-8374-c0b6a9063482', direction: 'asc', definition: baseDefinition, diff --git a/packages/twenty-front/src/modules/views/utils/mapViewSortsToSorts.ts b/packages/twenty-front/src/modules/views/utils/mapViewSortsToSorts.ts index 7e4141966..bdaba4a0a 100644 --- a/packages/twenty-front/src/modules/views/utils/mapViewSortsToSorts.ts +++ b/packages/twenty-front/src/modules/views/utils/mapViewSortsToSorts.ts @@ -1,13 +1,13 @@ -import { Sort } from '@/object-record/object-sort-dropdown/types/Sort'; import { SortDefinition } from '@/object-record/object-sort-dropdown/types/SortDefinition'; import { isDefined } from 'twenty-shared'; +import { RecordSort } from '@/object-record/record-sort/types/RecordSort'; import { ViewSort } from '../types/ViewSort'; export const mapViewSortsToSorts = ( viewSorts: ViewSort[], availableSortDefinitions: SortDefinition[], -): Sort[] => { +): RecordSort[] => { return viewSorts .map((viewSort) => { const availableSortDefinition = availableSortDefinitions.find( @@ -16,7 +16,9 @@ export const mapViewSortsToSorts = ( ); if (!availableSortDefinition) return null; + return { + id: viewSort.id, fieldMetadataId: viewSort.fieldMetadataId, direction: viewSort.direction, definition: availableSortDefinition,