From e9b7ad21d2360c5e9ae0865dc3f0eaae405bce66 Mon Sep 17 00:00:00 2001 From: Lucas Bordeau Date: Thu, 8 Jan 2026 12:05:45 +0100 Subject: [PATCH] Fix view picker small bugs (#16987) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR solves small bugs around the view picker. - Couldn’t obtain optimistic update after a re-order of a view by drag and drop, we needed to refresh the page - Picking a new icon wouldn’t trigger optimistic update (same problem) - Picking a new icon would change the view (difficult to understand behavior) - Picking a new icon would trigger left drawer collapse (z-index problem) Since core views are not being handled by object metadata items anymore, and that all view logic is plugged on coreViewsState, this PR implemented optimistic effect by upserting into this state. Fixes https://github.com/twentyhq/twenty/issues/15422 Fixes https://github.com/twentyhq/twenty/issues/16986 # Before https://github.com/user-attachments/assets/64099c21-df9f-4772-ab0d-9ea449aed761 # After https://github.com/user-attachments/assets/f4e844b3-6530-4178-abdb-b7a10d2327b8 --------- Co-authored-by: Charles Bochet --- .../hooks/useHandleRecordGroupField.ts | 16 ++- .../hooks/useViewFieldAggregateOperation.tsx | 6 +- .../ui/input/components/IconPicker.tsx | 14 +-- .../components/ResizablePanelEdge.tsx | 2 +- ...istView.ts => usePerformViewAPIPersist.ts} | 48 +------- .../hooks/internal/usePerformViewAPIUpdate.ts | 86 ++++++++++++++ ...ld.ts => usePerformViewFieldAPIPersist.ts} | 18 +-- ...r.ts => usePerformViewFilterAPIPersist.ts} | 18 +-- ...=> usePerformViewFilterGroupAPIPersist.ts} | 14 +-- ...up.ts => usePerformViewGroupAPIPersist.ts} | 6 +- ...ort.ts => usePerformViewSortAPIPersist.ts} | 14 +-- .../hooks/useCreateViewFromCurrentView.ts | 43 +++---- .../hooks/useSaveAnyFieldFilterToView.ts | 8 +- .../views/hooks/useSaveCurrentViewFields.ts | 13 ++- .../views/hooks/useSaveCurrentViewGroups.ts | 12 +- ...aveRecordFilterGroupsToViewFilterGroups.ts | 25 ++-- .../useSaveRecordFiltersToViewFilters.ts | 27 +++-- .../hooks/useSaveRecordSortsToViewSorts.ts | 21 ++-- .../views/hooks/useUpdateViewAggregate.ts | 13 ++- .../views/utils/convertCoreViewToView.ts | 6 +- .../components/ViewPickerListContent.tsx | 18 ++- .../hooks/useDestroyViewFromCurrentState.ts | 8 +- .../hooks/useUpdateViewFromCurrentState.ts | 12 +- ...sertIntoArrayOfObjectsComparingId.test.ts} | 2 +- ...fItemIntoArrayOfObjectsComparingId.test.ts | 107 ++++++++++++++++++ ...=> upsertIntoArrayOfObjectsComparingId.ts} | 0 ...tiesOfItemIntoArrayOfObjectsComparingId.ts | 31 +++++ packages/twenty-shared/src/utils/index.ts | 3 +- 28 files changed, 404 insertions(+), 187 deletions(-) rename packages/twenty-front/src/modules/views/hooks/internal/{usePersistView.ts => usePerformViewAPIPersist.ts} (76%) create mode 100644 packages/twenty-front/src/modules/views/hooks/internal/usePerformViewAPIUpdate.ts rename packages/twenty-front/src/modules/views/hooks/internal/{usePersistViewField.ts => usePerformViewFieldAPIPersist.ts} (95%) rename packages/twenty-front/src/modules/views/hooks/internal/{usePersistViewFilter.ts => usePerformViewFilterAPIPersist.ts} (95%) rename packages/twenty-front/src/modules/views/hooks/internal/{usePersistViewFilterGroup.ts => usePerformViewFilterGroupAPIPersist.ts} (92%) rename packages/twenty-front/src/modules/views/hooks/internal/{usePersistViewGroup.ts => usePerformViewGroupAPIPersist.ts} (95%) rename packages/twenty-front/src/modules/views/hooks/internal/{usePersistViewSort.ts => usePerformViewSortAPIPersist.ts} (91%) rename packages/twenty-shared/src/utils/array/__tests__/{upsertIntoArrayOfObjectComparingId.test.ts => upsertIntoArrayOfObjectsComparingId.test.ts} (96%) create mode 100644 packages/twenty-shared/src/utils/array/__tests__/upsertPropertiesOfItemIntoArrayOfObjectsComparingId.test.ts rename packages/twenty-shared/src/utils/array/{upsertIntoArrayOfObjectComparingId.ts => upsertIntoArrayOfObjectsComparingId.ts} (100%) create mode 100644 packages/twenty-shared/src/utils/array/upsertPropertiesOfItemIntoArrayOfObjectsComparingId.ts diff --git a/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleRecordGroupField.ts b/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleRecordGroupField.ts index 58a3543b5d1..473a62d7287 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleRecordGroupField.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleRecordGroupField.ts @@ -4,7 +4,7 @@ import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataIte import { useSetRecordGroups } from '@/object-record/record-group/hooks/useSetRecordGroups'; import { useLoadRecordIndexStates } from '@/object-record/record-index/hooks/useLoadRecordIndexStates'; import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState'; -import { usePersistView } from '@/views/hooks/internal/usePersistView'; +import { usePerformViewAPIUpdate } from '@/views/hooks/internal/usePerformViewAPIUpdate'; import { useGetViewFromPrefetchState } from '@/views/hooks/useGetViewFromPrefetchState'; import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId'; import { type ViewGroup } from '@/views/types/ViewGroup'; @@ -26,7 +26,7 @@ export const useHandleRecordGroupField = () => { const { setRecordGroupsFromViewGroups } = useSetRecordGroups(); - const { updateView } = usePersistView(); + const { performViewAPIUpdate } = usePerformViewAPIUpdate(); const { loadRecordIndexStates } = useLoadRecordIndexStates(); const { refreshCoreViewsByObjectMetadataId } = useRefreshCoreViewsByObjectMetadataId(); @@ -55,7 +55,7 @@ export const useHandleRecordGroupField = () => { return; } - const updatedViewResult = await updateView({ + const updatedViewResult = await performViewAPIUpdate({ id: view.id, input: { mainGroupByFieldMetadataId: fieldMetadataItem.id, @@ -133,7 +133,7 @@ export const useHandleRecordGroupField = () => { [ currentViewIdCallbackState, getViewFromPrefetchState, - updateView, + performViewAPIUpdate, setRecordGroupsFromViewGroups, objectMetadataItem, refreshCoreViewsByObjectMetadataId, @@ -162,14 +162,18 @@ export const useHandleRecordGroupField = () => { return; } - await updateView({ + await performViewAPIUpdate({ id: view.id, input: { mainGroupByFieldMetadataId: null, }, }); }, - [currentViewIdCallbackState, getViewFromPrefetchState, updateView], + [ + currentViewIdCallbackState, + getViewFromPrefetchState, + performViewAPIUpdate, + ], ); return { handleRecordGroupFieldChange, resetRecordGroupField }; 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 index 66bb50a250c..717ed5dd337 100644 --- 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 @@ -3,7 +3,7 @@ import { RecordTableColumnAggregateFooterDropdownContext } from '@/object-record import { viewFieldAggregateOperationState } from '@/object-record/record-table/record-table-footer/states/viewFieldAggregateOperationState'; import { type ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations'; import { convertExtendedAggregateOperationToAggregateOperation } from '@/object-record/utils/convertExtendedAggregateOperationToAggregateOperation'; -import { usePersistViewField } from '@/views/hooks/internal/usePersistViewField'; +import { usePerformViewFieldAPIPersist } from '@/views/hooks/internal/usePerformViewFieldAPIPersist'; import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly'; import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId'; import { useContext } from 'react'; @@ -24,14 +24,14 @@ export const useViewFieldAggregateOperation = () => { useRefreshCoreViewsByObjectMetadataId(); const { objectMetadataItem } = useRecordIndexContextOrThrow(); - const { updateViewFields } = usePersistViewField(); + const { performViewFieldAPIUpdate } = usePerformViewFieldAPIPersist(); const updateViewFieldAggregateOperation = async ( aggregateOperation: ExtendedAggregateOperations | null, ) => { if (!currentViewField) { throw new Error('ViewField not found'); } - await updateViewFields([ + await performViewFieldAPIUpdate([ { input: { id: currentViewField.id, diff --git a/packages/twenty-front/src/modules/ui/input/components/IconPicker.tsx b/packages/twenty-front/src/modules/ui/input/components/IconPicker.tsx index f963294ff79..408c00f846e 100644 --- a/packages/twenty-front/src/modules/ui/input/components/IconPicker.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/IconPicker.tsx @@ -9,6 +9,8 @@ import { SelectableList } from '@/ui/layout/selectable-list/components/Selectabl import { arrayToChunks } from '~/utils/array/arrayToChunks'; import { ICON_PICKER_DROPDOWN_CONTENT_WIDTH } from '@/ui/input/components/constants/IconPickerDropdownContentWidth'; +import { IconPickerScrollEffect } from '@/ui/input/hooks/IconPickerScrollEffect'; +import { iconPickerVisibleCountState } from '@/ui/input/states/iconPickerVisibleCountState'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown'; import { type DropdownOffset } from '@/ui/layout/dropdown/types/DropdownOffset'; @@ -25,8 +27,6 @@ import { type IconButtonVariant, LightIconButton, } from 'twenty-ui/input'; -import { IconPickerScrollEffect } from '@/ui/input/hooks/IconPickerScrollEffect'; -import { iconPickerVisibleCountState } from '@/ui/input/states/iconPickerVisibleCountState'; export type IconPickerProps = { disabled?: boolean; @@ -86,7 +86,7 @@ const convertIconKeyToLabel = (iconKey: string) => type IconPickerIconProps = { iconKey: string; - onClick: () => void; + onSelect: () => void; selectedIconKey?: string; Icon: IconComponent; focusedIconKey?: string; @@ -94,7 +94,7 @@ type IconPickerIconProps = { const IconPickerIcon = ({ iconKey, - onClick, + onSelect, selectedIconKey, Icon, focusedIconKey, @@ -106,7 +106,7 @@ const IconPickerIcon = ({ return ( - + @@ -301,7 +301,7 @@ export const IconPicker = ({ { + onSelect={() => { onChange({ iconKey, Icon: getIcon(iconKey) }); closeDropdown(dropdownId); }} diff --git a/packages/twenty-front/src/modules/ui/layout/resizable-panel/components/ResizablePanelEdge.tsx b/packages/twenty-front/src/modules/ui/layout/resizable-panel/components/ResizablePanelEdge.tsx index b3e648d8a7b..7c75379d585 100644 --- a/packages/twenty-front/src/modules/ui/layout/resizable-panel/components/ResizablePanelEdge.tsx +++ b/packages/twenty-front/src/modules/ui/layout/resizable-panel/components/ResizablePanelEdge.tsx @@ -19,7 +19,7 @@ const StyledEdge = styled.div` side === 'right' ? 'right' : 'left'}: -${RESIZE_EDGE_WIDTH_PX / 2}px; width: ${RESIZE_EDGE_WIDTH_PX}px; cursor: col-resize; - z-index: 100; + display: flex; align-items: center; justify-content: center; diff --git a/packages/twenty-front/src/modules/views/hooks/internal/usePersistView.ts b/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewAPIPersist.ts similarity index 76% rename from packages/twenty-front/src/modules/views/hooks/internal/usePersistView.ts rename to packages/twenty-front/src/modules/views/hooks/internal/usePerformViewAPIPersist.ts index 700c6b67263..a9a6f1eb602 100644 --- a/packages/twenty-front/src/modules/views/hooks/internal/usePersistView.ts +++ b/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewAPIPersist.ts @@ -12,16 +12,13 @@ import { v4 } from 'uuid'; import { type CreateCoreViewMutationVariables, type DestroyCoreViewMutationVariables, - type UpdateCoreViewMutationVariables, useCreateCoreViewMutation, useDestroyCoreViewMutation, - useUpdateCoreViewMutation, ViewType, } from '~/generated/graphql'; -export const usePersistView = () => { +export const usePerformViewAPIPersist = () => { const [createCoreViewMutation] = useCreateCoreViewMutation(); - const [updateCoreViewMutation] = useUpdateCoreViewMutation(); const [destroyCoreViewMutation] = useDestroyCoreViewMutation(); const { triggerViewGroupOptimisticEffectAtViewCreation } = useViewsSideEffectsOnViewGroups(); @@ -29,7 +26,7 @@ export const usePersistView = () => { const { handleMetadataError } = useMetadataErrorHandler(); const { enqueueErrorSnackBar } = useSnackBar(); - const createView = useCallback( + const performViewAPICreate = useCallback( async ( variables: CreateCoreViewMutationVariables, objectMetadataItemId: string, @@ -93,41 +90,7 @@ export const usePersistView = () => { ], ); - const updateView = useCallback( - async ( - variables: UpdateCoreViewMutationVariables, - ): Promise< - MetadataRequestResult>> - > => { - try { - const result = await updateCoreViewMutation({ - variables, - }); - - return { - status: 'successful', - response: result, - }; - } catch (error) { - if (error instanceof ApolloError) { - handleMetadataError(error, { - primaryMetadataName: 'view', - operationType: CrudOperationType.UPDATE, - }); - } else { - enqueueErrorSnackBar({ message: t`An error occurred.` }); - } - - return { - status: 'failed', - error, - }; - } - }, - [updateCoreViewMutation, handleMetadataError, enqueueErrorSnackBar], - ); - - const destroyView = useCallback( + const performViewAPIDestroy = useCallback( async ( variables: DestroyCoreViewMutationVariables, ): Promise< @@ -162,8 +125,7 @@ export const usePersistView = () => { ); return { - createView, - updateView, - destroyView, + performViewAPICreate, + performViewAPIDestroy, }; }; diff --git a/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewAPIUpdate.ts b/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewAPIUpdate.ts new file mode 100644 index 00000000000..3d51b024394 --- /dev/null +++ b/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewAPIUpdate.ts @@ -0,0 +1,86 @@ +import { useCallback } from 'react'; + +import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler'; +import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type'; +import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; +import { coreViewsState } from '@/views/states/coreViewState'; +import { ApolloError } from '@apollo/client'; +import { t } from '@lingui/core/macro'; +import { useSetRecoilState } from 'recoil'; +import { CrudOperationType } from 'twenty-shared/types'; +import { upsertPropertiesOfItemIntoArrayOfObjectsComparingId } from 'twenty-shared/utils'; +import { + type CoreView, + type UpdateCoreViewMutationVariables, + useUpdateCoreViewMutation, +} from '~/generated/graphql'; + +export const usePerformViewAPIUpdate = () => { + const [updateCoreViewMutation] = useUpdateCoreViewMutation(); + + const { handleMetadataError } = useMetadataErrorHandler(); + const { enqueueErrorSnackBar } = useSnackBar(); + + const setCoreViews = useSetRecoilState(coreViewsState); + + const performViewAPIUpdate = useCallback( + async ( + variables: UpdateCoreViewMutationVariables, + ): Promise< + MetadataRequestResult>> + > => { + try { + setCoreViews((currentCoreViews) => + upsertPropertiesOfItemIntoArrayOfObjectsComparingId( + currentCoreViews, + { + ...variables.input, + id: variables.id, + } as CoreView, + ), + ); + + const result = await updateCoreViewMutation({ + variables, + }); + + setCoreViews((currentCoreViews) => + upsertPropertiesOfItemIntoArrayOfObjectsComparingId( + currentCoreViews, + { + ...result.data?.updateCoreView, + id: variables.id, + } as CoreView, + ), + ); + + return { + status: 'successful', + response: result, + }; + } catch (error) { + if (error instanceof ApolloError) { + handleMetadataError(error, { + primaryMetadataName: 'view', + operationType: CrudOperationType.UPDATE, + }); + } else { + enqueueErrorSnackBar({ message: t`An error occurred.` }); + } + + return { + status: 'failed', + error, + }; + } + }, + [ + setCoreViews, + updateCoreViewMutation, + handleMetadataError, + enqueueErrorSnackBar, + ], + ); + + return { performViewAPIUpdate }; +}; diff --git a/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewField.ts b/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewFieldAPIPersist.ts similarity index 95% rename from packages/twenty-front/src/modules/views/hooks/internal/usePersistViewField.ts rename to packages/twenty-front/src/modules/views/hooks/internal/usePerformViewFieldAPIPersist.ts index f8614fcb1ff..6932f6198c2 100644 --- a/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewField.ts +++ b/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewFieldAPIPersist.ts @@ -18,7 +18,7 @@ import { useDestroyCoreViewFieldMutation, useUpdateCoreViewFieldMutation, } from '~/generated/graphql'; -export const usePersistViewField = () => { +export const usePerformViewFieldAPIPersist = () => { const { triggerViewFieldOptimisticEffect } = useTriggerViewFieldOptimisticEffect(); @@ -31,7 +31,7 @@ export const usePersistViewField = () => { const { handleMetadataError } = useMetadataErrorHandler(); const { enqueueErrorSnackBar } = useSnackBar(); - const createViewFields = useCallback( + const performViewFieldAPICreate = useCallback( async ( createCoreViewFieldInputs: CreateManyCoreViewFieldsMutationVariables, ): Promise< @@ -92,7 +92,7 @@ export const usePersistViewField = () => { ], ); - const updateViewFields = useCallback( + const performViewFieldAPIUpdate = useCallback( async ( createCoreViewFieldInputs: UpdateCoreViewFieldMutationVariables[], ): Promise< @@ -154,7 +154,7 @@ export const usePersistViewField = () => { ], ); - const deleteViewFields = useCallback( + const performViewFieldAPIDelete = useCallback( async ( deleteCoreViewFieldInputs: DeleteCoreViewFieldMutationVariables[], ): Promise< @@ -216,7 +216,7 @@ export const usePersistViewField = () => { ], ); - const destroyViewFields = useCallback( + const performViewFieldAPIDestroy = useCallback( async ( destroyCoreViewFieldInputs: DestroyCoreViewFieldMutationVariables[], ): Promise< @@ -264,9 +264,9 @@ export const usePersistViewField = () => { ); return { - createViewFields, - updateViewFields, - deleteViewFields, - destroyViewFields, + performViewFieldAPICreate, + performViewFieldAPIUpdate, + performViewFieldAPIDelete, + performViewFieldAPIDestroy, }; }; diff --git a/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewFilter.ts b/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewFilterAPIPersist.ts similarity index 95% rename from packages/twenty-front/src/modules/views/hooks/internal/usePersistViewFilter.ts rename to packages/twenty-front/src/modules/views/hooks/internal/usePerformViewFilterAPIPersist.ts index ec61b7b3ee9..d7ea4734331 100644 --- a/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewFilter.ts +++ b/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewFilterAPIPersist.ts @@ -19,7 +19,7 @@ import { useUpdateCoreViewFilterMutation, } from '~/generated/graphql'; -export const usePersistViewFilterRecords = () => { +export const usePerformViewFilterAPIPersist = () => { const { triggerViewFilterOptimisticEffect } = useTriggerViewFilterOptimisticEffect(); const [createCoreViewFilterMutation] = useCreateCoreViewFilterMutation(); @@ -30,7 +30,7 @@ export const usePersistViewFilterRecords = () => { const { handleMetadataError } = useMetadataErrorHandler(); const { enqueueErrorSnackBar } = useSnackBar(); - const createViewFilters = useCallback( + const performViewFilterAPICreate = useCallback( async ( createCoreViewFilterInputs: CreateCoreViewFilterMutationVariables[], ): Promise< @@ -92,7 +92,7 @@ export const usePersistViewFilterRecords = () => { ], ); - const updateViewFilters = useCallback( + const performViewFilterAPIUpdate = useCallback( async ( updateCoreViewFilterInputs: UpdateCoreViewFilterMutationVariables[], ): Promise< @@ -154,7 +154,7 @@ export const usePersistViewFilterRecords = () => { ], ); - const deleteViewFilters = useCallback( + const performViewFilterAPIDelete = useCallback( async ( deleteCoreViewFilterInputs: DeleteCoreViewFilterMutationVariables[], ): Promise< @@ -216,7 +216,7 @@ export const usePersistViewFilterRecords = () => { ], ); - const destroyViewFilters = useCallback( + const performViewFilterAPIDestroy = useCallback( async ( destroyCoreViewFilterInputs: DestroyCoreViewFilterMutationVariables[], ): Promise< @@ -264,9 +264,9 @@ export const usePersistViewFilterRecords = () => { ); return { - createViewFilters, - updateViewFilters, - deleteViewFilters, - destroyViewFilters, + performViewFilterAPICreate, + performViewFilterAPIUpdate, + performViewFilterAPIDelete, + performViewFilterAPIDestroy, }; }; diff --git a/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewFilterGroup.ts b/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewFilterGroupAPIPersist.ts similarity index 92% rename from packages/twenty-front/src/modules/views/hooks/internal/usePersistViewFilterGroup.ts rename to packages/twenty-front/src/modules/views/hooks/internal/usePerformViewFilterGroupAPIPersist.ts index b4e2282d9b2..2269d68710a 100644 --- a/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewFilterGroup.ts +++ b/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewFilterGroupAPIPersist.ts @@ -9,7 +9,7 @@ import { useApolloClient } from '@apollo/client'; import { isDefined } from 'twenty-shared/utils'; import { type CoreViewFilterGroup } from '~/generated/graphql'; -export const usePersistViewFilterGroupRecords = () => { +export const usePerformViewFilterGroupAPIPersist = () => { const apolloClient = useApolloClient(); const createCoreViewFilterGroupRecord = useCallback( @@ -39,7 +39,7 @@ export const usePersistViewFilterGroupRecords = () => { [apolloClient], ); - const createViewFilterGroups = useCallback( + const performViewFilterGroupAPICreate = useCallback( async ( viewFilterGroupsToCreate: ViewFilterGroup[], view: Pick, @@ -80,7 +80,7 @@ export const usePersistViewFilterGroupRecords = () => { [createCoreViewFilterGroupRecord], ); - const updateViewFilterGroups = useCallback( + const performViewFilterGroupAPIUpdate = useCallback( (viewFilterGroupsToUpdate: ViewFilterGroup[]) => { if (!viewFilterGroupsToUpdate.length) return; return Promise.all( @@ -104,7 +104,7 @@ export const usePersistViewFilterGroupRecords = () => { [apolloClient], ); - const deleteViewFilterGroups = useCallback( + const performViewFilterGroupAPIDelete = useCallback( (viewFilterGroupIdsToDelete: string[]) => { if (!viewFilterGroupIdsToDelete.length) return; return Promise.all( @@ -122,8 +122,8 @@ export const usePersistViewFilterGroupRecords = () => { ); return { - createViewFilterGroups, - updateViewFilterGroups, - deleteViewFilterGroups, + performViewFilterGroupAPICreate, + performViewFilterGroupAPIUpdate, + performViewFilterGroupAPIDelete, }; }; diff --git a/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewGroup.ts b/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewGroupAPIPersist.ts similarity index 95% rename from packages/twenty-front/src/modules/views/hooks/internal/usePersistViewGroup.ts rename to packages/twenty-front/src/modules/views/hooks/internal/usePerformViewGroupAPIPersist.ts index d7dd66f4baf..6d6437a95e3 100644 --- a/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewGroup.ts +++ b/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewGroupAPIPersist.ts @@ -13,7 +13,7 @@ import { useUpdateCoreViewGroupMutation, } from '~/generated/graphql'; -export const usePersistViewGroupRecords = () => { +export const usePerformViewGroupAPIPersist = () => { const { triggerViewGroupOptimisticEffect } = useTriggerViewGroupOptimisticEffect(); @@ -22,7 +22,7 @@ export const usePersistViewGroupRecords = () => { const { handleMetadataError } = useMetadataErrorHandler(); const { enqueueErrorSnackBar } = useSnackBar(); - const updateViewGroups = useCallback( + const performViewGroupAPIUpdate = useCallback( async ( updateCoreViewGroupInputs: UpdateCoreViewGroupMutationVariables[], ): Promise< @@ -85,6 +85,6 @@ export const usePersistViewGroupRecords = () => { ); return { - updateViewGroups, + performViewGroupAPIUpdate, }; }; diff --git a/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewSort.ts b/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewSortAPIPersist.ts similarity index 91% rename from packages/twenty-front/src/modules/views/hooks/internal/usePersistViewSort.ts rename to packages/twenty-front/src/modules/views/hooks/internal/usePerformViewSortAPIPersist.ts index c300ec37cdc..ae9fd481eea 100644 --- a/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewSort.ts +++ b/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewSortAPIPersist.ts @@ -10,13 +10,13 @@ import { useApolloClient } from '@apollo/client'; import { isDefined } from 'twenty-shared/utils'; import { type CoreViewSort } from '~/generated/graphql'; -export const usePersistViewSortRecords = () => { +export const usePerformViewSortAPIPersist = () => { const apolloClient = useApolloClient(); const { triggerViewSortOptimisticEffect } = useTriggerViewSortOptimisticEffect(); - const createViewSorts = useCallback( + const performViewSortAPICreate = useCallback( ( viewSortsToCreate: CoreViewSortEssential[], view: Pick, @@ -49,7 +49,7 @@ export const usePersistViewSortRecords = () => { [apolloClient, triggerViewSortOptimisticEffect], ); - const updateViewSorts = useCallback( + const performViewSortAPIUpdate = useCallback( (viewSortsToUpdate: CoreViewSortEssential[]) => { if (!viewSortsToUpdate.length) return; return Promise.all( @@ -77,7 +77,7 @@ export const usePersistViewSortRecords = () => { [apolloClient, triggerViewSortOptimisticEffect], ); - const deleteViewSorts = useCallback( + const performViewSortAPIDelete = useCallback( (viewSortsToDelete: Pick[]) => { if (!viewSortsToDelete.length) return; return Promise.all( @@ -100,8 +100,8 @@ export const usePersistViewSortRecords = () => { ); return { - createViewSorts, - updateViewSorts, - deleteViewSorts, + performViewSortAPICreate, + performViewSortAPIUpdate, + performViewSortAPIDelete, }; }; diff --git a/packages/twenty-front/src/modules/views/hooks/useCreateViewFromCurrentView.ts b/packages/twenty-front/src/modules/views/hooks/useCreateViewFromCurrentView.ts index b17abae067e..5f28265c71b 100644 --- a/packages/twenty-front/src/modules/views/hooks/useCreateViewFromCurrentView.ts +++ b/packages/twenty-front/src/modules/views/hooks/useCreateViewFromCurrentView.ts @@ -7,11 +7,11 @@ import { currentRecordSortsComponentState } from '@/object-record/record-sort/st import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; -import { usePersistView } from '@/views/hooks/internal/usePersistView'; -import { usePersistViewField } from '@/views/hooks/internal/usePersistViewField'; -import { usePersistViewFilterRecords } from '@/views/hooks/internal/usePersistViewFilter'; -import { usePersistViewFilterGroupRecords } from '@/views/hooks/internal/usePersistViewFilterGroup'; -import { usePersistViewSortRecords } from '@/views/hooks/internal/usePersistViewSort'; +import { usePerformViewAPIPersist } from '@/views/hooks/internal/usePerformViewAPIPersist'; +import { usePerformViewFieldAPIPersist } from '@/views/hooks/internal/usePerformViewFieldAPIPersist'; +import { usePerformViewFilterAPIPersist } from '@/views/hooks/internal/usePerformViewFilterAPIPersist'; +import { usePerformViewFilterGroupAPIPersist } from '@/views/hooks/internal/usePerformViewFilterGroupAPIPersist'; +import { usePerformViewSortAPIPersist } from '@/views/hooks/internal/usePerformViewSortAPIPersist'; import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId'; import { coreViewFromViewIdFamilySelector } from '@/views/states/selectors/coreViewFromViewIdFamilySelector'; import { type GraphQLView } from '@/views/types/GraphQLView'; @@ -29,7 +29,7 @@ import { ViewCalendarLayout } from '~/generated-metadata/graphql'; import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => { - const { createView } = usePersistView(); + const { performViewAPICreate } = usePerformViewAPIPersist(); const currentViewIdCallbackState = useRecoilComponentCallbackState( contextStoreCurrentViewIdComponentState, @@ -40,13 +40,14 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => { anyFieldFilterValueComponentState, ); - const { createViewFields } = usePersistViewField(); + const { performViewFieldAPICreate } = usePerformViewFieldAPIPersist(); - const { createViewSorts } = usePersistViewSortRecords(); + const { performViewSortAPICreate } = usePerformViewSortAPIPersist(); - const { createViewFilters } = usePersistViewFilterRecords(); + const { performViewFilterAPICreate } = usePerformViewFilterAPIPersist(); - const { createViewFilterGroups } = usePersistViewFilterGroupRecords(); + const { performViewFilterGroupAPICreate } = + usePerformViewFilterGroupAPIPersist(); const { objectMetadataItem } = useRecordIndexContextOrThrow(); @@ -113,7 +114,7 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => { const viewType = type ?? sourceView.type; - const result = await createView( + const result = await performViewAPICreate( { input: { id: id ?? v4(), @@ -160,7 +161,7 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => { throw new Error('Failed to create view'); } - const fieldResult = await createViewFields({ + const fieldResult = await performViewFieldAPICreate({ inputs: sourceView.viewFields.map( ({ __typename, id: _id, ...viewField }) => ({ ...viewField, @@ -202,7 +203,7 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => { id: v4(), })); - await createViewFilterGroups(viewFilterGroupsToCreate, { + await performViewFilterGroupAPICreate(viewFilterGroupsToCreate, { id: newViewId, }); @@ -221,13 +222,15 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => { }), ); - const filterResult = await createViewFilters(createViewFilterInputs); + const filterResult = await performViewFilterAPICreate( + createViewFilterInputs, + ); if (filterResult.status === 'failed') { return undefined; } - await createViewSorts(viewSortsToCreate, { id: newViewId }); + await performViewSortAPICreate(viewSortsToCreate, { id: newViewId }); } await refreshCoreViewsByObjectMetadataId(objectMetadataItem.id); @@ -236,17 +239,17 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => { }, [ currentViewIdCallbackState, - createView, + performViewAPICreate, anyFieldFilterValue, objectMetadataItem, - createViewFields, + performViewFieldAPICreate, refreshCoreViewsByObjectMetadataId, currentRecordFilterGroups, currentRecordFilters, currentRecordSorts, - createViewFilterGroups, - createViewFilters, - createViewSorts, + performViewFilterGroupAPICreate, + performViewFilterAPICreate, + performViewSortAPICreate, ], ); diff --git a/packages/twenty-front/src/modules/views/hooks/useSaveAnyFieldFilterToView.ts b/packages/twenty-front/src/modules/views/hooks/useSaveAnyFieldFilterToView.ts index 3d5d98258de..981e25a8a2f 100644 --- a/packages/twenty-front/src/modules/views/hooks/useSaveAnyFieldFilterToView.ts +++ b/packages/twenty-front/src/modules/views/hooks/useSaveAnyFieldFilterToView.ts @@ -1,6 +1,6 @@ import { anyFieldFilterValueComponentState } from '@/object-record/record-filter/states/anyFieldFilterValueComponentState'; import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState'; -import { usePersistView } from '@/views/hooks/internal/usePersistView'; +import { usePerformViewAPIUpdate } from '@/views/hooks/internal/usePerformViewAPIUpdate'; import { useCanPersistViewChanges } from '@/views/hooks/useCanPersistViewChanges'; import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly'; import { convertUpdateViewInputToCore } from '@/views/utils/convertUpdateViewInputToCore'; @@ -9,7 +9,7 @@ import { isDefined } from 'twenty-shared/utils'; export const useSaveAnyFieldFilterToView = () => { const { canPersistChanges } = useCanPersistViewChanges(); - const { updateView } = usePersistView(); + const { performViewAPIUpdate } = usePerformViewAPIUpdate(); const { currentView } = useGetCurrentViewOnly(); @@ -31,7 +31,7 @@ export const useSaveAnyFieldFilterToView = () => { .getValue(); if (currentAnyFieldFilterValue !== currentViewAnyFieldFilterValue) { - await updateView({ + await performViewAPIUpdate({ id: currentView.id, input: convertUpdateViewInputToCore({ ...currentView, @@ -42,7 +42,7 @@ export const useSaveAnyFieldFilterToView = () => { }, [ canPersistChanges, - updateView, + performViewAPIUpdate, anyFieldFilterValueCallbackState, currentView, ], diff --git a/packages/twenty-front/src/modules/views/hooks/useSaveCurrentViewFields.ts b/packages/twenty-front/src/modules/views/hooks/useSaveCurrentViewFields.ts index d54635d5869..ced04f275a7 100644 --- a/packages/twenty-front/src/modules/views/hooks/useSaveCurrentViewFields.ts +++ b/packages/twenty-front/src/modules/views/hooks/useSaveCurrentViewFields.ts @@ -2,7 +2,7 @@ import { useRecoilCallback } from 'recoil'; import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState'; import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState'; -import { usePersistViewField } from '@/views/hooks/internal/usePersistViewField'; +import { usePerformViewFieldAPIPersist } from '@/views/hooks/internal/usePerformViewFieldAPIPersist'; import { useCanPersistViewChanges } from '@/views/hooks/useCanPersistViewChanges'; import { useGetViewFromPrefetchState } from '@/views/hooks/useGetViewFromPrefetchState'; import { type ViewField } from '@/views/types/ViewField'; @@ -15,7 +15,8 @@ import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; export const useSaveCurrentViewFields = () => { const { canPersistChanges } = useCanPersistViewChanges(); - const { createViewFields, updateViewFields } = usePersistViewField(); + const { performViewFieldAPICreate, performViewFieldAPIUpdate } = + usePerformViewFieldAPIPersist(); const { getViewFromPrefetchState } = useGetViewFromPrefetchState(); @@ -123,16 +124,16 @@ export const useSaveCurrentViewFields = () => { ); await Promise.all([ - createViewFields({ inputs: viewFieldsToCreate }), - updateViewFields(viewFieldsToUpdate), + performViewFieldAPICreate({ inputs: viewFieldsToCreate }), + performViewFieldAPIUpdate(viewFieldsToUpdate), ]); }, [ canPersistChanges, - createViewFields, + performViewFieldAPICreate, currentViewIdCallbackState, getViewFromPrefetchState, - updateViewFields, + performViewFieldAPIUpdate, ], ); diff --git a/packages/twenty-front/src/modules/views/hooks/useSaveCurrentViewGroups.ts b/packages/twenty-front/src/modules/views/hooks/useSaveCurrentViewGroups.ts index c7ee2ef48db..28668cf0054 100644 --- a/packages/twenty-front/src/modules/views/hooks/useSaveCurrentViewGroups.ts +++ b/packages/twenty-front/src/modules/views/hooks/useSaveCurrentViewGroups.ts @@ -2,7 +2,7 @@ import { useRecoilCallback } from 'recoil'; import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState'; import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState'; -import { usePersistViewGroupRecords } from '@/views/hooks/internal/usePersistViewGroup'; +import { usePerformViewGroupAPIPersist } from '@/views/hooks/internal/usePerformViewGroupAPIPersist'; import { useCanPersistViewChanges } from '@/views/hooks/useCanPersistViewChanges'; import { useGetViewFromPrefetchState } from '@/views/hooks/useGetViewFromPrefetchState'; import { type ViewGroup } from '@/views/types/ViewGroup'; @@ -12,7 +12,7 @@ import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; export const useSaveCurrentViewGroups = () => { const { canPersistChanges } = useCanPersistViewChanges(); - const { updateViewGroups } = usePersistViewGroupRecords(); + const { performViewGroupAPIUpdate } = usePerformViewGroupAPIPersist(); const { getViewFromPrefetchState } = useGetViewFromPrefetchState(); @@ -67,7 +67,7 @@ export const useSaveCurrentViewGroups = () => { return; } - await updateViewGroups([ + await performViewGroupAPIUpdate([ { input: { id: existingField.id, @@ -84,7 +84,7 @@ export const useSaveCurrentViewGroups = () => { canPersistChanges, currentViewIdCallbackState, getViewFromPrefetchState, - updateViewGroups, + performViewGroupAPIUpdate, ], ); @@ -154,13 +154,13 @@ export const useSaveCurrentViewGroups = () => { throw new Error('mainGroupByFieldMetadataId is required'); } - await updateViewGroups(viewGroupsToUpdate); + await performViewGroupAPIUpdate(viewGroupsToUpdate); }, [ canPersistChanges, currentViewIdCallbackState, getViewFromPrefetchState, - updateViewGroups, + performViewGroupAPIUpdate, ], ); diff --git a/packages/twenty-front/src/modules/views/hooks/useSaveRecordFilterGroupsToViewFilterGroups.ts b/packages/twenty-front/src/modules/views/hooks/useSaveRecordFilterGroupsToViewFilterGroups.ts index 48e775159f5..d8a3a2b977d 100644 --- a/packages/twenty-front/src/modules/views/hooks/useSaveRecordFilterGroupsToViewFilterGroups.ts +++ b/packages/twenty-front/src/modules/views/hooks/useSaveRecordFilterGroupsToViewFilterGroups.ts @@ -1,7 +1,7 @@ import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState'; import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState'; import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; -import { usePersistViewFilterGroupRecords } from '@/views/hooks/internal/usePersistViewFilterGroup'; +import { usePerformViewFilterGroupAPIPersist } from '@/views/hooks/internal/usePerformViewFilterGroupAPIPersist'; import { useCanPersistViewChanges } from '@/views/hooks/useCanPersistViewChanges'; import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly'; import { getViewFilterGroupsToCreate } from '@/views/utils/getViewFilterGroupsToCreate'; @@ -14,10 +14,10 @@ import { isDefined } from 'twenty-shared/utils'; export const useSaveRecordFilterGroupsToViewFilterGroups = () => { const { canPersistChanges } = useCanPersistViewChanges(); const { - createViewFilterGroups, - updateViewFilterGroups, - deleteViewFilterGroups, - } = usePersistViewFilterGroupRecords(); + performViewFilterGroupAPICreate, + performViewFilterGroupAPIUpdate, + performViewFilterGroupAPIDelete, + } = usePerformViewFilterGroupAPIPersist(); const { currentView } = useGetCurrentViewOnly(); @@ -65,17 +65,20 @@ export const useSaveRecordFilterGroupsToViewFilterGroups = () => { (viewFilterGroup) => viewFilterGroup.id, ); - await createViewFilterGroups(viewFilterGroupsToCreate, currentView); - await updateViewFilterGroups(viewFilterGroupsToUpdate); - await deleteViewFilterGroups(viewFilterGroupIdsToDelete); + await performViewFilterGroupAPICreate( + viewFilterGroupsToCreate, + currentView, + ); + await performViewFilterGroupAPIUpdate(viewFilterGroupsToUpdate); + await performViewFilterGroupAPIDelete(viewFilterGroupIdsToDelete); }, [ canPersistChanges, currentView, currentRecordFilterGroupsCallbackState, - createViewFilterGroups, - updateViewFilterGroups, - deleteViewFilterGroups, + performViewFilterGroupAPICreate, + performViewFilterGroupAPIUpdate, + performViewFilterGroupAPIDelete, ], ); diff --git a/packages/twenty-front/src/modules/views/hooks/useSaveRecordFiltersToViewFilters.ts b/packages/twenty-front/src/modules/views/hooks/useSaveRecordFiltersToViewFilters.ts index 2fe47022eac..d2423c2c4ca 100644 --- a/packages/twenty-front/src/modules/views/hooks/useSaveRecordFiltersToViewFilters.ts +++ b/packages/twenty-front/src/modules/views/hooks/useSaveRecordFiltersToViewFilters.ts @@ -1,7 +1,7 @@ import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState'; import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; -import { usePersistViewFilterRecords } from '@/views/hooks/internal/usePersistViewFilter'; +import { usePerformViewFilterAPIPersist } from '@/views/hooks/internal/usePerformViewFilterAPIPersist'; import { useCanPersistViewChanges } from '@/views/hooks/useCanPersistViewChanges'; import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly'; import { getViewFiltersToCreate } from '@/views/utils/getViewFiltersToCreate'; @@ -13,8 +13,11 @@ import { isDefined } from 'twenty-shared/utils'; export const useSaveRecordFiltersToViewFilters = () => { const { canPersistChanges } = useCanPersistViewChanges(); - const { createViewFilters, updateViewFilters, deleteViewFilters } = - usePersistViewFilterRecords(); + const { + performViewFilterAPICreate, + performViewFilterAPIUpdate, + performViewFilterAPIDelete, + } = usePerformViewFilterAPIPersist(); const { currentView } = useGetCurrentViewOnly(); @@ -93,17 +96,23 @@ export const useSaveRecordFiltersToViewFilters = () => { }), ); - const createResult = await createViewFilters(createViewFilterInputs); + const createResult = await performViewFilterAPICreate( + createViewFilterInputs, + ); if (createResult.status === 'failed') { return; } - const updateResult = await updateViewFilters(updateViewFilterInputs); + const updateResult = await performViewFilterAPIUpdate( + updateViewFilterInputs, + ); if (updateResult.status === 'failed') { return; } - const deleteResult = await deleteViewFilters(deleteViewFilterInputs); + const deleteResult = await performViewFilterAPIDelete( + deleteViewFilterInputs, + ); if (deleteResult.status === 'failed') { return; } @@ -112,9 +121,9 @@ export const useSaveRecordFiltersToViewFilters = () => { canPersistChanges, currentView, currentRecordFiltersCallbackState, - createViewFilters, - updateViewFilters, - deleteViewFilters, + performViewFilterAPICreate, + performViewFilterAPIUpdate, + performViewFilterAPIDelete, ], ); diff --git a/packages/twenty-front/src/modules/views/hooks/useSaveRecordSortsToViewSorts.ts b/packages/twenty-front/src/modules/views/hooks/useSaveRecordSortsToViewSorts.ts index ed0afb24fa8..a9217bce89d 100644 --- a/packages/twenty-front/src/modules/views/hooks/useSaveRecordSortsToViewSorts.ts +++ b/packages/twenty-front/src/modules/views/hooks/useSaveRecordSortsToViewSorts.ts @@ -1,7 +1,7 @@ import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState'; import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState'; import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; -import { usePersistViewSortRecords } from '@/views/hooks/internal/usePersistViewSort'; +import { usePerformViewSortAPIPersist } from '@/views/hooks/internal/usePerformViewSortAPIPersist'; import { useCanPersistViewChanges } from '@/views/hooks/useCanPersistViewChanges'; import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly'; import { getViewSortsToCreate } from '@/views/utils/getViewSortsToCreate'; @@ -13,8 +13,11 @@ import { isDefined } from 'twenty-shared/utils'; export const useSaveRecordSortsToViewSorts = () => { const { canPersistChanges } = useCanPersistViewChanges(); - const { createViewSorts, updateViewSorts, deleteViewSorts } = - usePersistViewSortRecords(); + const { + performViewSortAPICreate, + performViewSortAPIUpdate, + performViewSortAPIDelete, + } = usePerformViewSortAPIPersist(); const { currentView } = useGetCurrentViewOnly(); @@ -55,17 +58,17 @@ export const useSaveRecordSortsToViewSorts = () => { newViewSorts, ); - await createViewSorts(viewSortsToCreate, currentView); - await updateViewSorts(viewSortsToUpdate); - await deleteViewSorts(viewSortsToDelete); + await performViewSortAPICreate(viewSortsToCreate, currentView); + await performViewSortAPIUpdate(viewSortsToUpdate); + await performViewSortAPIDelete(viewSortsToDelete); }, [ canPersistChanges, currentView, currentRecordSortsCallbackState, - createViewSorts, - updateViewSorts, - deleteViewSorts, + performViewSortAPICreate, + performViewSortAPIUpdate, + performViewSortAPIDelete, ], ); diff --git a/packages/twenty-front/src/modules/views/hooks/useUpdateViewAggregate.ts b/packages/twenty-front/src/modules/views/hooks/useUpdateViewAggregate.ts index 83a8d5d5cb1..2eaf2fe90a6 100644 --- a/packages/twenty-front/src/modules/views/hooks/useUpdateViewAggregate.ts +++ b/packages/twenty-front/src/modules/views/hooks/useUpdateViewAggregate.ts @@ -4,7 +4,7 @@ import { useLoadRecordIndexStates } from '@/object-record/record-index/hooks/use import { type ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations'; import { convertExtendedAggregateOperationToAggregateOperation } from '@/object-record/utils/convertExtendedAggregateOperationToAggregateOperation'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; -import { usePersistView } from '@/views/hooks/internal/usePersistView'; +import { usePerformViewAPIUpdate } from '@/views/hooks/internal/usePerformViewAPIUpdate'; import { useCanPersistViewChanges } from '@/views/hooks/useCanPersistViewChanges'; import { coreViewsState } from '@/views/states/coreViewState'; import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView'; @@ -20,7 +20,7 @@ export const useUpdateViewAggregate = () => { const currentViewId = useRecoilComponentValue( contextStoreCurrentViewIdComponentState, ); - const { updateView } = usePersistView(); + const { performViewAPIUpdate } = usePerformViewAPIUpdate(); const { loadRecordIndexStates } = useLoadRecordIndexStates(); const updateViewAggregate = useRecoilCallback( @@ -50,7 +50,7 @@ export const useUpdateViewAggregate = () => { return; } - const updatedViewResult = await updateView({ + const updatedViewResult = await performViewAPIUpdate({ id: currentViewId, input: { kanbanAggregateOperationFieldMetadataId, @@ -78,7 +78,12 @@ export const useUpdateViewAggregate = () => { loadRecordIndexStates(updatedView, objectMetadataItem); } }, - [canPersistChanges, currentViewId, updateView, loadRecordIndexStates], + [ + canPersistChanges, + currentViewId, + performViewAPIUpdate, + loadRecordIndexStates, + ], ); return { diff --git a/packages/twenty-front/src/modules/views/utils/convertCoreViewToView.ts b/packages/twenty-front/src/modules/views/utils/convertCoreViewToView.ts index 10ff2a85a50..c5032f6a03b 100644 --- a/packages/twenty-front/src/modules/views/utils/convertCoreViewToView.ts +++ b/packages/twenty-front/src/modules/views/utils/convertCoreViewToView.ts @@ -25,13 +25,13 @@ export const convertCoreViewToView = ( key: convertedKey, objectMetadataId: coreView.objectMetadataId, isCompact: coreView.isCompact, - viewFields: coreView.viewFields.map((viewField) => + viewFields: coreView.viewFields?.map((viewField) => convertCoreViewFieldToViewField(viewField), ), - viewGroups: coreView.viewGroups.map((viewGroup) => + viewGroups: coreView.viewGroups?.map((viewGroup) => convertCoreViewGroupToViewGroup(viewGroup), ), - viewFilters: coreView.viewFilters.map((viewFilter) => + viewFilters: coreView.viewFilters?.map((viewFilter) => convertCoreViewFilterToViewFilter(viewFilter), ), viewFilterGroups: coreView.viewFilterGroups?.map( diff --git a/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerListContent.tsx b/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerListContent.tsx index 07328e61963..1fb6b5ba39c 100644 --- a/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerListContent.tsx +++ b/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerListContent.tsx @@ -11,7 +11,7 @@ import { DropdownMenuSectionLabel } from '@/ui/layout/dropdown/components/Dropdo import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown'; import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState'; -import { usePersistView } from '@/views/hooks/internal/usePersistView'; +import { usePerformViewAPIUpdate } from '@/views/hooks/internal/usePerformViewAPIUpdate'; import { useChangeView } from '@/views/hooks/useChangeView'; import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly'; import { useOpenCreateViewDropdown } from '@/views/hooks/useOpenCreateViewDropown'; @@ -61,7 +61,7 @@ export const ViewPickerListContent = () => { const { setViewPickerMode } = useViewPickerMode(); - const { updateView } = usePersistView(); + const { performViewAPIUpdate } = usePerformViewAPIUpdate(); const { changeView } = useChangeView(); const { closeDropdown } = useCloseDropdown(); @@ -98,12 +98,15 @@ export const ViewPickerListContent = () => { Promise.all( viewsReordered.map(async (view, index) => { if (view.position !== index) { - await updateView({ id: view.id, input: { position: index } }); + await performViewAPIUpdate({ + id: view.id, + input: { position: index }, + }); } }), ); }, - [updateView, workspaceViews], + [performViewAPIUpdate, workspaceViews], ); const handleUnlistedDragEnd = useCallback( @@ -118,12 +121,15 @@ export const ViewPickerListContent = () => { Promise.all( viewsReordered.map(async (view, index) => { if (view.position !== index) { - await updateView({ id: view.id, input: { position: index } }); + await performViewAPIUpdate({ + id: view.id, + input: { position: index }, + }); } }), ); }, - [updateView, unlistedViews], + [performViewAPIUpdate, unlistedViews], ); return ( diff --git a/packages/twenty-front/src/modules/views/view-picker/hooks/useDestroyViewFromCurrentState.ts b/packages/twenty-front/src/modules/views/view-picker/hooks/useDestroyViewFromCurrentState.ts index 01ae7e5606e..cf3a6833780 100644 --- a/packages/twenty-front/src/modules/views/view-picker/hooks/useDestroyViewFromCurrentState.ts +++ b/packages/twenty-front/src/modules/views/view-picker/hooks/useDestroyViewFromCurrentState.ts @@ -3,7 +3,7 @@ import { useRecoilCallback, useRecoilValue } from 'recoil'; import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow'; import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState'; import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; -import { usePersistView } from '@/views/hooks/internal/usePersistView'; +import { usePerformViewAPIPersist } from '@/views/hooks/internal/usePerformViewAPIPersist'; import { useChangeView } from '@/views/hooks/useChangeView'; import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly'; import { coreViewsByObjectMetadataIdFamilySelector } from '@/views/states/selectors/coreViewsByObjectMetadataIdFamilySelector'; @@ -44,7 +44,7 @@ export const useDestroyViewFromCurrentState = (viewBarInstanceId?: string) => { const { changeView } = useChangeView(); - const { destroyView } = usePersistView(); + const { performViewAPIDestroy } = usePerformViewAPIPersist(); const destroyViewFromCurrentState = useRecoilCallback( ({ set, snapshot }) => @@ -74,14 +74,14 @@ export const useDestroyViewFromCurrentState = (viewBarInstanceId?: string) => { views.filter((view) => view.id !== viewPickerReferenceViewId), ); - await destroyView({ id: viewPickerReferenceViewId }); + await performViewAPIDestroy({ id: viewPickerReferenceViewId }); }, [ currentView, closeAndResetViewPicker, objectMetadataItem.id, changeView, - destroyView, + performViewAPIDestroy, viewPickerIsDirtyCallbackState, viewPickerIsPersistingCallbackState, viewPickerReferenceViewIdCallbackState, diff --git a/packages/twenty-front/src/modules/views/view-picker/hooks/useUpdateViewFromCurrentState.ts b/packages/twenty-front/src/modules/views/view-picker/hooks/useUpdateViewFromCurrentState.ts index 5e8443e8931..74c2ceb1c7a 100644 --- a/packages/twenty-front/src/modules/views/view-picker/hooks/useUpdateViewFromCurrentState.ts +++ b/packages/twenty-front/src/modules/views/view-picker/hooks/useUpdateViewFromCurrentState.ts @@ -1,8 +1,7 @@ import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState'; import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; -import { usePersistView } from '@/views/hooks/internal/usePersistView'; +import { usePerformViewAPIUpdate } from '@/views/hooks/internal/usePerformViewAPIUpdate'; import { useCanPersistViewChanges } from '@/views/hooks/useCanPersistViewChanges'; -import { useChangeView } from '@/views/hooks/useChangeView'; import { useCloseAndResetViewPicker } from '@/views/view-picker/hooks/useCloseAndResetViewPicker'; import { viewPickerInputNameComponentState } from '@/views/view-picker/states/viewPickerInputNameComponentState'; import { viewPickerIsDirtyComponentState } from '@/views/view-picker/states/viewPickerIsDirtyComponentState'; @@ -39,8 +38,7 @@ export const useUpdateViewFromCurrentState = () => { viewPickerVisibilityComponentState, ); - const { updateView } = usePersistView(); - const { changeView } = useChangeView(); + const { performViewAPIUpdate } = usePerformViewAPIUpdate(); const updateViewFromCurrentState = useRecoilCallback( ({ set, snapshot }) => @@ -71,7 +69,7 @@ export const useUpdateViewFromCurrentState = () => { viewPickerVisibilityCallbackState, ); - await updateView({ + await performViewAPIUpdate({ id: viewPickerReferenceViewId, input: { name: viewPickerInputName, @@ -79,7 +77,6 @@ export const useUpdateViewFromCurrentState = () => { visibility: visibility, }, }); - changeView(viewPickerReferenceViewId); }, [ canPersistChanges, @@ -90,8 +87,7 @@ export const useUpdateViewFromCurrentState = () => { viewPickerInputNameCallbackState, viewPickerSelectedIconCallbackState, viewPickerVisibilityCallbackState, - updateView, - changeView, + performViewAPIUpdate, ], ); diff --git a/packages/twenty-shared/src/utils/array/__tests__/upsertIntoArrayOfObjectComparingId.test.ts b/packages/twenty-shared/src/utils/array/__tests__/upsertIntoArrayOfObjectsComparingId.test.ts similarity index 96% rename from packages/twenty-shared/src/utils/array/__tests__/upsertIntoArrayOfObjectComparingId.test.ts rename to packages/twenty-shared/src/utils/array/__tests__/upsertIntoArrayOfObjectsComparingId.test.ts index 2dd64554b8f..78a4ccec8ec 100644 --- a/packages/twenty-shared/src/utils/array/__tests__/upsertIntoArrayOfObjectComparingId.test.ts +++ b/packages/twenty-shared/src/utils/array/__tests__/upsertIntoArrayOfObjectsComparingId.test.ts @@ -1,4 +1,4 @@ -import { upsertIntoArrayOfObjectsComparingId } from '@/utils/array/upsertIntoArrayOfObjectComparingId'; +import { upsertIntoArrayOfObjectsComparingId } from '@/utils/array/upsertIntoArrayOfObjectsComparingId'; type TestObject = { id: string; diff --git a/packages/twenty-shared/src/utils/array/__tests__/upsertPropertiesOfItemIntoArrayOfObjectsComparingId.test.ts b/packages/twenty-shared/src/utils/array/__tests__/upsertPropertiesOfItemIntoArrayOfObjectsComparingId.test.ts new file mode 100644 index 00000000000..c4d9acfb290 --- /dev/null +++ b/packages/twenty-shared/src/utils/array/__tests__/upsertPropertiesOfItemIntoArrayOfObjectsComparingId.test.ts @@ -0,0 +1,107 @@ +import { upsertPropertiesOfItemIntoArrayOfObjectsComparingId } from '@/utils/array/upsertPropertiesOfItemIntoArrayOfObjectsComparingId'; + +type TestObject = { + id: string; + name: string; + value: number; +}; + +const mockTestObjects: TestObject[] = [ + { + id: '1', + name: 'Test 1', + value: 100, + }, + { + id: '2', + name: 'Test 2', + value: 200, + }, + { + id: '3', + name: 'Test 3', + value: 300, + }, +]; + +describe('upsertPropertiesOfItemIntoArrayOfObjectsComparingId', () => { + it('should insert partial properties into empty array', () => { + const partialItem = { id: '1', name: 'New Item' }; + + expect( + upsertPropertiesOfItemIntoArrayOfObjectsComparingId( + [], + partialItem, + ), + ).toStrictEqual([partialItem]); + }); + + it('should append item when id does not exist', () => { + const newItem: TestObject = { + id: '4', + name: 'Test 4', + value: 400, + }; + + expect( + upsertPropertiesOfItemIntoArrayOfObjectsComparingId( + mockTestObjects, + newItem, + ), + ).toStrictEqual([...mockTestObjects, newItem]); + }); + + it('should merge partial properties into existing item', () => { + const partialUpdate = { id: '2', name: 'Updated Test 2' }; + + expect( + upsertPropertiesOfItemIntoArrayOfObjectsComparingId( + mockTestObjects, + partialUpdate, + ), + ).toStrictEqual([ + mockTestObjects[0], + { id: '2', name: 'Updated Test 2', value: 200 }, + mockTestObjects[2], + ]); + }); + + it('should replace all properties when full item is provided', () => { + const fullUpdate: TestObject = { + id: '1', + name: 'Replaced Test 1', + value: 999, + }; + + expect( + upsertPropertiesOfItemIntoArrayOfObjectsComparingId( + mockTestObjects, + fullUpdate, + ), + ).toStrictEqual([fullUpdate, mockTestObjects[1], mockTestObjects[2]]); + }); + + it('should not mutate original array when updating', () => { + const originalArray = [...mockTestObjects]; + const partialUpdate = { id: '2', value: 999 }; + + upsertPropertiesOfItemIntoArrayOfObjectsComparingId( + mockTestObjects, + partialUpdate, + ); + + expect(mockTestObjects).toStrictEqual(originalArray); + }); + + it('should not mutate original array when inserting', () => { + const originalArray = [...mockTestObjects]; + const newItem: TestObject = { id: '5', name: 'Test 5', value: 500 }; + + upsertPropertiesOfItemIntoArrayOfObjectsComparingId( + mockTestObjects, + newItem, + ); + + expect(mockTestObjects).toStrictEqual(originalArray); + }); +}); diff --git a/packages/twenty-shared/src/utils/array/upsertIntoArrayOfObjectComparingId.ts b/packages/twenty-shared/src/utils/array/upsertIntoArrayOfObjectsComparingId.ts similarity index 100% rename from packages/twenty-shared/src/utils/array/upsertIntoArrayOfObjectComparingId.ts rename to packages/twenty-shared/src/utils/array/upsertIntoArrayOfObjectsComparingId.ts diff --git a/packages/twenty-shared/src/utils/array/upsertPropertiesOfItemIntoArrayOfObjectsComparingId.ts b/packages/twenty-shared/src/utils/array/upsertPropertiesOfItemIntoArrayOfObjectsComparingId.ts new file mode 100644 index 00000000000..503ff2af070 --- /dev/null +++ b/packages/twenty-shared/src/utils/array/upsertPropertiesOfItemIntoArrayOfObjectsComparingId.ts @@ -0,0 +1,31 @@ +import { findById } from '@/utils/array/findById'; + +export const upsertPropertiesOfItemIntoArrayOfObjectsComparingId = < + T extends { id: string }, +>( + arrayToUpsertInto: T[], + propertiesToUpsert: Partial & { id: string }, +): T[] => { + const alreadyExistingItemIndex = arrayToUpsertInto.findIndex( + findById(propertiesToUpsert.id), + ); + + const shouldReplaceItem = alreadyExistingItemIndex > -1; + + if (shouldReplaceItem) { + const newArray = [...arrayToUpsertInto]; + + const itemToUpsert = { + ...arrayToUpsertInto[alreadyExistingItemIndex], + ...propertiesToUpsert, + } as T; + + newArray.splice(alreadyExistingItemIndex, 1, itemToUpsert); + + return newArray; + } else { + return arrayToUpsertInto.concat({ + ...propertiesToUpsert, + } as T); + } +}; diff --git a/packages/twenty-shared/src/utils/index.ts b/packages/twenty-shared/src/utils/index.ts index 3a0385a0405..6f0a7964f68 100644 --- a/packages/twenty-shared/src/utils/index.ts +++ b/packages/twenty-shared/src/utils/index.ts @@ -17,7 +17,8 @@ export { isNonEmptyArray } from './array/isNonEmptyArray'; export { mapById } from './array/mapById'; export { mapByProperty } from './array/mapByProperty'; export { sumByProperty } from './array/sumByProperty'; -export { upsertIntoArrayOfObjectsComparingId } from './array/upsertIntoArrayOfObjectComparingId'; +export { upsertIntoArrayOfObjectsComparingId } from './array/upsertIntoArrayOfObjectsComparingId'; +export { upsertPropertiesOfItemIntoArrayOfObjectsComparingId } from './array/upsertPropertiesOfItemIntoArrayOfObjectsComparingId'; export { assertUnreachable } from './assertUnreachable'; export { computeDiffBetweenObjects } from './compute-diff-between-objects'; export { isPlainDateAfter } from './date/isPlainDateAfter';