Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
86df188e83 |
+30
-69
@@ -1,75 +1,35 @@
|
||||
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
|
||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||
import { isSwitchingToKanbanViewTypeComponentState } from '@/object-record/record-board/states/isSwitchingToKanbanViewTypeComponentState';
|
||||
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { useLoadRecordIndexStates } from '@/object-record/record-index/hooks/useLoadRecordIndexStates';
|
||||
import { recordIndexViewTypeState } from '@/object-record/record-index/states/recordIndexViewTypeState';
|
||||
import { usePersistViewGroupRecords } from '@/views/hooks/internal/usePersistViewGroup';
|
||||
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
|
||||
import { useUpdateCurrentView } from '@/views/hooks/useUpdateCurrentView';
|
||||
import { coreViewsState } from '@/views/states/coreViewState';
|
||||
import { type GraphQLView } from '@/views/types/GraphQLView';
|
||||
import { type ViewGroup } from '@/views/types/ViewGroup';
|
||||
import { ViewType, viewTypeIconMapping } from '@/views/types/ViewType';
|
||||
import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView';
|
||||
import { convertViewTypeToCore } from '@/views/utils/convertViewTypeToCore';
|
||||
import { useGetAvailableFieldsForCalendar } from '@/views/view-picker/hooks/useGetAvailableFieldsForCalendar';
|
||||
import { useGetAvailableFieldsForKanban } from '@/views/view-picker/hooks/useGetAvailableFieldsForKanban';
|
||||
import { useCallback } from 'react';
|
||||
import { useRecoilCallback, useSetRecoilState } from 'recoil';
|
||||
import { assertUnreachable, isDefined } from 'twenty-shared/utils';
|
||||
import { v4 } from 'uuid';
|
||||
import { ViewCalendarLayout } from '~/generated/graphql';
|
||||
|
||||
export const useSetViewTypeFromLayoutOptionsMenu = () => {
|
||||
const { updateCurrentView } = useUpdateCurrentView();
|
||||
const setRecordIndexViewType = useSetRecoilState(recordIndexViewTypeState);
|
||||
const isSwitchingToKanbanViewTypeCallbackState =
|
||||
useRecoilComponentCallbackState(isSwitchingToKanbanViewTypeComponentState);
|
||||
const { availableFieldsForKanban } = useGetAvailableFieldsForKanban();
|
||||
const { objectMetadataItem } = useRecordIndexContextOrThrow();
|
||||
|
||||
const { loadRecordIndexStates } = useLoadRecordIndexStates();
|
||||
|
||||
const { createViewGroups } = usePersistViewGroupRecords();
|
||||
|
||||
const { availableFieldsForCalendar } = useGetAvailableFieldsForCalendar();
|
||||
|
||||
const createViewGroupAssociatedWithKanbanField = useCallback(
|
||||
async (randomFieldForKanban: string, currentViewId: string) => {
|
||||
const viewGroupsToCreate =
|
||||
objectMetadataItem.fields
|
||||
?.find((field) => field.id === randomFieldForKanban)
|
||||
?.options?.map(
|
||||
(option, index) =>
|
||||
({
|
||||
id: v4(),
|
||||
__typename: 'ViewGroup',
|
||||
fieldValue: option.value,
|
||||
isVisible: true,
|
||||
position: index,
|
||||
}) satisfies ViewGroup,
|
||||
) ?? [];
|
||||
|
||||
viewGroupsToCreate.push({
|
||||
__typename: 'ViewGroup',
|
||||
id: v4(),
|
||||
fieldValue: '',
|
||||
position: viewGroupsToCreate.length,
|
||||
isVisible: true,
|
||||
} satisfies ViewGroup);
|
||||
|
||||
await createViewGroups({
|
||||
createCoreViewGroupInputs: {
|
||||
inputs: viewGroupsToCreate.map(({ __typename, ...viewGroup }) => ({
|
||||
...viewGroup,
|
||||
viewId: currentViewId,
|
||||
})),
|
||||
},
|
||||
});
|
||||
|
||||
return viewGroupsToCreate;
|
||||
},
|
||||
[objectMetadataItem, createViewGroups],
|
||||
);
|
||||
|
||||
const setAndPersistViewType = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
async (viewType: ViewType) => {
|
||||
@@ -104,20 +64,20 @@ export const useSetViewTypeFromLayoutOptionsMenu = () => {
|
||||
|
||||
switch (viewType) {
|
||||
case ViewType.Kanban: {
|
||||
set(isSwitchingToKanbanViewTypeCallbackState, true);
|
||||
if (availableFieldsForKanban.length === 0) {
|
||||
throw new Error('No fields for kanban - should not happen');
|
||||
}
|
||||
|
||||
if (currentView.viewGroups.length === 0) {
|
||||
const viewGroups = await createViewGroupAssociatedWithKanbanField(
|
||||
availableFieldsForKanban[0].id,
|
||||
currentView.id,
|
||||
);
|
||||
loadRecordIndexStates(
|
||||
{ ...currentView, viewGroups },
|
||||
objectMetadataItem,
|
||||
);
|
||||
const mainGroupByFieldMetadataId = availableFieldsForKanban[0].id;
|
||||
updateCurrentViewParams.mainGroupByFieldMetadataId =
|
||||
mainGroupByFieldMetadataId;
|
||||
|
||||
if (shouldChangeIcon(currentView.icon, currentView.type)) {
|
||||
updateCurrentViewParams.icon =
|
||||
viewTypeIconMapping(viewType).displayName;
|
||||
}
|
||||
|
||||
setRecordIndexViewType(viewType);
|
||||
set(coreViewsState, [
|
||||
...existingCoreViews.filter(
|
||||
@@ -126,16 +86,19 @@ export const useSetViewTypeFromLayoutOptionsMenu = () => {
|
||||
{
|
||||
...currentCoreView,
|
||||
type: convertViewTypeToCore(viewType),
|
||||
mainGroupByFieldMetadataId,
|
||||
},
|
||||
]);
|
||||
|
||||
if (shouldChangeIcon(currentView.icon, currentView.type)) {
|
||||
updateCurrentViewParams.icon =
|
||||
viewTypeIconMapping(viewType).displayName;
|
||||
}
|
||||
return await updateCurrentView(updateCurrentViewParams);
|
||||
await updateCurrentView(updateCurrentViewParams);
|
||||
return;
|
||||
}
|
||||
case ViewType.Table: {
|
||||
if (shouldChangeIcon(currentView.icon, currentView.type)) {
|
||||
updateCurrentViewParams.icon =
|
||||
viewTypeIconMapping(viewType).displayName;
|
||||
}
|
||||
updateCurrentViewParams.mainGroupByFieldMetadataId = null;
|
||||
await updateCurrentView(updateCurrentViewParams);
|
||||
setRecordIndexViewType(viewType);
|
||||
set(coreViewsState, [
|
||||
...existingCoreViews.filter(
|
||||
@@ -143,15 +106,11 @@ export const useSetViewTypeFromLayoutOptionsMenu = () => {
|
||||
),
|
||||
{
|
||||
...currentCoreView,
|
||||
mainGroupByFieldMetadataId: null,
|
||||
type: convertViewTypeToCore(viewType),
|
||||
},
|
||||
]);
|
||||
|
||||
if (shouldChangeIcon(currentView.icon, currentView.type)) {
|
||||
updateCurrentViewParams.icon =
|
||||
viewTypeIconMapping(viewType).displayName;
|
||||
}
|
||||
return await updateCurrentView(updateCurrentViewParams);
|
||||
return;
|
||||
}
|
||||
case ViewType.Calendar: {
|
||||
if (availableFieldsForCalendar.length === 0) {
|
||||
@@ -167,6 +126,7 @@ export const useSetViewTypeFromLayoutOptionsMenu = () => {
|
||||
),
|
||||
{
|
||||
...currentCoreView,
|
||||
mainGroupByFieldMetadataId: null,
|
||||
type: convertViewTypeToCore(viewType),
|
||||
calendarLayout: ViewCalendarLayout.MONTH,
|
||||
calendarFieldMetadataId,
|
||||
@@ -190,6 +150,7 @@ export const useSetViewTypeFromLayoutOptionsMenu = () => {
|
||||
updateCurrentViewParams.calendarLayout = ViewCalendarLayout.MONTH;
|
||||
updateCurrentViewParams.calendarFieldMetadataId =
|
||||
calendarFieldMetadataId;
|
||||
updateCurrentViewParams.mainGroupByFieldMetadataId = null;
|
||||
return await updateCurrentView(updateCurrentViewParams);
|
||||
}
|
||||
default: {
|
||||
@@ -198,13 +159,13 @@ export const useSetViewTypeFromLayoutOptionsMenu = () => {
|
||||
}
|
||||
},
|
||||
[
|
||||
isSwitchingToKanbanViewTypeCallbackState,
|
||||
availableFieldsForKanban,
|
||||
objectMetadataItem,
|
||||
updateCurrentView,
|
||||
setRecordIndexViewType,
|
||||
createViewGroupAssociatedWithKanbanField,
|
||||
loadRecordIndexStates,
|
||||
updateCurrentView,
|
||||
availableFieldsForCalendar,
|
||||
loadRecordIndexStates,
|
||||
objectMetadataItem,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
+10
-1
@@ -1,5 +1,6 @@
|
||||
import { useTriggerRecordBoardFetchMore } from '@/object-record/record-board/hooks/useTriggerRecordBoardFetchMore';
|
||||
import { useTriggerRecordBoardInitialQuery } from '@/object-record/record-board/hooks/useTriggerRecordBoardInitialQuery';
|
||||
import { isSwitchingToKanbanViewTypeComponentState } from '@/object-record/record-board/states/isSwitchingToKanbanViewTypeComponentState';
|
||||
import { lastRecordBoardQueryIdentifierComponentState } from '@/object-record/record-board/states/lastRecordBoardQueryIdentifierComponentState';
|
||||
import { recordBoardCurrentGroupByQueryOffsetComponentState } from '@/object-record/record-board/states/recordBoardCurrentGroupByQueryOffsetComponentState';
|
||||
import { recordBoardIsFetchingMoreComponentState } from '@/object-record/record-board/states/recordBoardIsFetchingMoreComponentState';
|
||||
@@ -22,6 +23,11 @@ export const RecordBoardQueryEffect = () => {
|
||||
const [lastRecordBoardQueryIdentifier, setLastRecordBoardQueryIdentifier] =
|
||||
useRecoilComponentState(lastRecordBoardQueryIdentifierComponentState);
|
||||
|
||||
const [
|
||||
isSwitchingToKanbanViewTypeCallbackState,
|
||||
setIsSwitchingToKanbanViewTypeComponentState,
|
||||
] = useRecoilComponentState(isSwitchingToKanbanViewTypeComponentState);
|
||||
|
||||
const [recordIndexRecordGroupsAreInInitialLoading] = useRecoilComponentState(
|
||||
recordIndexRecordGroupsAreInInitialLoadingComponentState,
|
||||
);
|
||||
@@ -60,9 +66,10 @@ export const RecordBoardQueryEffect = () => {
|
||||
useEffect(() => {
|
||||
if (
|
||||
!recordIndexRecordGroupsAreInInitialLoading &&
|
||||
queryIdentifierHasChanged
|
||||
(queryIdentifierHasChanged || isSwitchingToKanbanViewTypeCallbackState)
|
||||
) {
|
||||
triggerRecordBoardInitialQuery();
|
||||
setIsSwitchingToKanbanViewTypeComponentState(false);
|
||||
} else if (
|
||||
!recordIndexRecordGroupsAreInInitialLoading &&
|
||||
shouldFetchMore &&
|
||||
@@ -82,6 +89,8 @@ export const RecordBoardQueryEffect = () => {
|
||||
shouldFetchMore,
|
||||
recordBoardIsFetchingMore,
|
||||
triggerRecordBoardFetchMore,
|
||||
isSwitchingToKanbanViewTypeCallbackState,
|
||||
setIsSwitchingToKanbanViewTypeComponentState,
|
||||
]);
|
||||
|
||||
return null;
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
|
||||
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
|
||||
|
||||
export const isSwitchingToKanbanViewTypeComponentState =
|
||||
createComponentState<boolean>({
|
||||
key: 'isSwitchingToKanbanViewTypeComponentState',
|
||||
componentInstanceContext: ContextStoreComponentInstanceContext,
|
||||
defaultValue: false,
|
||||
});
|
||||
+12
-27
@@ -5,8 +5,8 @@ import { useSetRecordGroups } from '@/object-record/record-group/hooks/useSetRec
|
||||
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 { usePersistViewGroupRecords } from '@/views/hooks/internal/usePersistViewGroup';
|
||||
import { useGetViewFromPrefetchState } from '@/views/hooks/useGetViewFromPrefetchState';
|
||||
import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId';
|
||||
import { type ViewGroup } from '@/views/types/ViewGroup';
|
||||
import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
@@ -16,8 +16,6 @@ import { type CoreView } from '~/generated/graphql';
|
||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||
|
||||
export const useHandleRecordGroupField = () => {
|
||||
const { deleteViewGroups } = usePersistViewGroupRecords();
|
||||
|
||||
const currentViewIdCallbackState = useRecoilComponentCallbackState(
|
||||
contextStoreCurrentViewIdComponentState,
|
||||
);
|
||||
@@ -30,6 +28,8 @@ export const useHandleRecordGroupField = () => {
|
||||
|
||||
const { updateView } = usePersistView();
|
||||
const { loadRecordIndexStates } = useLoadRecordIndexStates();
|
||||
const { refreshCoreViewsByObjectMetadataId } =
|
||||
useRefreshCoreViewsByObjectMetadataId();
|
||||
|
||||
const handleRecordGroupFieldChange = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
@@ -127,6 +127,8 @@ export const useHandleRecordGroupField = () => {
|
||||
viewGroups: newViewGroupsList,
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
await refreshCoreViewsByObjectMetadataId(objectMetadataItem.id);
|
||||
},
|
||||
[
|
||||
currentViewIdCallbackState,
|
||||
@@ -134,6 +136,7 @@ export const useHandleRecordGroupField = () => {
|
||||
updateView,
|
||||
setRecordGroupsFromViewGroups,
|
||||
objectMetadataItem,
|
||||
refreshCoreViewsByObjectMetadataId,
|
||||
loadRecordIndexStates,
|
||||
],
|
||||
);
|
||||
@@ -159,32 +162,14 @@ export const useHandleRecordGroupField = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
await deleteViewGroups(
|
||||
view.viewGroups.map((group) => ({
|
||||
input: {
|
||||
id: group.id,
|
||||
},
|
||||
})),
|
||||
);
|
||||
|
||||
if (!isDefined(view.mainGroupByFieldMetadataId)) {
|
||||
throw new Error('mainGroupByFieldMetadataId is required');
|
||||
}
|
||||
|
||||
setRecordGroupsFromViewGroups({
|
||||
viewId: view.id,
|
||||
mainGroupByFieldMetadataId: view.mainGroupByFieldMetadataId,
|
||||
viewGroups: [],
|
||||
objectMetadataItem,
|
||||
await updateView({
|
||||
id: view.id,
|
||||
input: {
|
||||
mainGroupByFieldMetadataId: null,
|
||||
},
|
||||
});
|
||||
},
|
||||
[
|
||||
deleteViewGroups,
|
||||
currentViewIdCallbackState,
|
||||
getViewFromPrefetchState,
|
||||
setRecordGroupsFromViewGroups,
|
||||
objectMetadataItem,
|
||||
],
|
||||
[currentViewIdCallbackState, getViewFromPrefetchState, updateView],
|
||||
);
|
||||
|
||||
return { handleRecordGroupFieldChange, resetRecordGroupField };
|
||||
|
||||
@@ -3,8 +3,11 @@ 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 { useViewsSideEffectsOnViewGroups } from '@/views/hooks/useViewsSideEffectsOnViewGroups';
|
||||
import { ApolloError } from '@apollo/client';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { v4 } from 'uuid';
|
||||
import {
|
||||
type CreateCoreViewMutationVariables,
|
||||
type DeleteCoreViewMutationVariables,
|
||||
@@ -12,12 +15,15 @@ import {
|
||||
useCreateCoreViewMutation,
|
||||
useDeleteCoreViewMutation,
|
||||
useUpdateCoreViewMutation,
|
||||
ViewType,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
export const usePersistView = () => {
|
||||
const [createCoreViewMutation] = useCreateCoreViewMutation();
|
||||
const [updateCoreViewMutation] = useUpdateCoreViewMutation();
|
||||
const [deleteCoreViewMutation] = useDeleteCoreViewMutation();
|
||||
const { triggerViewGroupOptimisticEffectAtViewCreation } =
|
||||
useViewsSideEffectsOnViewGroups();
|
||||
|
||||
const { handleMetadataError } = useMetadataErrorHandler();
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
@@ -25,14 +31,39 @@ export const usePersistView = () => {
|
||||
const createView = useCallback(
|
||||
async (
|
||||
variables: CreateCoreViewMutationVariables,
|
||||
objectMetadataItemId: string,
|
||||
): Promise<
|
||||
MetadataRequestResult<Awaited<ReturnType<typeof createCoreViewMutation>>>
|
||||
> => {
|
||||
try {
|
||||
const newViewId = variables.input.id ?? v4();
|
||||
if (variables.input.type === ViewType.KANBAN) {
|
||||
triggerViewGroupOptimisticEffectAtViewCreation({
|
||||
newViewId,
|
||||
objectMetadataItemId: objectMetadataItemId,
|
||||
mainGroupByFieldMetadataId:
|
||||
variables.input.mainGroupByFieldMetadataId,
|
||||
});
|
||||
}
|
||||
|
||||
const result = await createCoreViewMutation({
|
||||
variables,
|
||||
variables: {
|
||||
input: {
|
||||
...variables.input,
|
||||
id: newViewId,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const newView = result.data?.createCoreView;
|
||||
|
||||
if (!isDefined(newView)) {
|
||||
return {
|
||||
status: 'failed',
|
||||
error: new Error('Failed to create view'),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'successful',
|
||||
response: result,
|
||||
@@ -52,7 +83,12 @@ export const usePersistView = () => {
|
||||
};
|
||||
}
|
||||
},
|
||||
[createCoreViewMutation, handleMetadataError, enqueueErrorSnackBar],
|
||||
[
|
||||
createCoreViewMutation,
|
||||
triggerViewGroupOptimisticEffectAtViewCreation,
|
||||
handleMetadataError,
|
||||
enqueueErrorSnackBar,
|
||||
],
|
||||
);
|
||||
|
||||
const updateView = useCallback(
|
||||
|
||||
@@ -8,13 +8,7 @@ import { ApolloError } from '@apollo/client';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
type CreateManyCoreViewGroupsMutationVariables,
|
||||
type DeleteCoreViewGroupMutationVariables,
|
||||
type DestroyCoreViewGroupMutationVariables,
|
||||
type UpdateCoreViewGroupMutationVariables,
|
||||
useCreateManyCoreViewGroupsMutation,
|
||||
useDeleteCoreViewGroupMutation,
|
||||
useDestroyCoreViewGroupMutation,
|
||||
useUpdateCoreViewGroupMutation,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
@@ -22,77 +16,11 @@ export const usePersistViewGroupRecords = () => {
|
||||
const { triggerViewGroupOptimisticEffect } =
|
||||
useTriggerViewGroupOptimisticEffect();
|
||||
|
||||
const [createManyCoreViewGroupsMutation] =
|
||||
useCreateManyCoreViewGroupsMutation();
|
||||
const [updateCoreViewGroupMutation] = useUpdateCoreViewGroupMutation();
|
||||
const [deleteCoreViewGroupMutation] = useDeleteCoreViewGroupMutation();
|
||||
const [destroyCoreViewGroupMutation] = useDestroyCoreViewGroupMutation();
|
||||
|
||||
const { handleMetadataError } = useMetadataErrorHandler();
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
|
||||
const createViewGroups = useCallback(
|
||||
async ({
|
||||
createCoreViewGroupInputs,
|
||||
}: {
|
||||
createCoreViewGroupInputs: CreateManyCoreViewGroupsMutationVariables;
|
||||
}): Promise<
|
||||
MetadataRequestResult<Awaited<
|
||||
ReturnType<typeof createManyCoreViewGroupsMutation>
|
||||
> | null>
|
||||
> => {
|
||||
if (
|
||||
!Array.isArray(createCoreViewGroupInputs.inputs) ||
|
||||
createCoreViewGroupInputs.inputs.length === 0
|
||||
) {
|
||||
return {
|
||||
status: 'successful',
|
||||
response: null,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await createManyCoreViewGroupsMutation({
|
||||
variables: createCoreViewGroupInputs,
|
||||
update: (_cache, { data }) => {
|
||||
const createdViewGroups = data?.createManyCoreViewGroups;
|
||||
if (!isDefined(createdViewGroups)) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerViewGroupOptimisticEffect({
|
||||
createdViewGroups,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
status: 'successful',
|
||||
response: result,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof ApolloError) {
|
||||
handleMetadataError(error, {
|
||||
primaryMetadataName: 'viewGroup',
|
||||
});
|
||||
} else {
|
||||
enqueueErrorSnackBar({ message: t`An error occurred.` });
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'failed',
|
||||
error,
|
||||
};
|
||||
}
|
||||
},
|
||||
[
|
||||
triggerViewGroupOptimisticEffect,
|
||||
createManyCoreViewGroupsMutation,
|
||||
handleMetadataError,
|
||||
enqueueErrorSnackBar,
|
||||
],
|
||||
);
|
||||
|
||||
const updateViewGroups = useCallback(
|
||||
async (
|
||||
updateCoreViewGroupInputs: UpdateCoreViewGroupMutationVariables[],
|
||||
@@ -154,117 +82,7 @@ export const usePersistViewGroupRecords = () => {
|
||||
],
|
||||
);
|
||||
|
||||
const deleteViewGroups = useCallback(
|
||||
async (
|
||||
deleteCoreViewGroupInputs: DeleteCoreViewGroupMutationVariables[],
|
||||
): Promise<
|
||||
MetadataRequestResult<
|
||||
Awaited<ReturnType<typeof deleteCoreViewGroupMutation>>[]
|
||||
>
|
||||
> => {
|
||||
if (deleteCoreViewGroupInputs.length === 0) {
|
||||
return {
|
||||
status: 'successful',
|
||||
response: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const results = await Promise.all(
|
||||
deleteCoreViewGroupInputs.map((variables) =>
|
||||
deleteCoreViewGroupMutation({
|
||||
variables,
|
||||
update: (_cache, { data }) => {
|
||||
const deletedViewGroup = data?.deleteCoreViewGroup;
|
||||
if (!isDefined(deletedViewGroup)) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerViewGroupOptimisticEffect({
|
||||
deletedViewGroups: [deletedViewGroup],
|
||||
});
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
return {
|
||||
status: 'successful',
|
||||
response: results,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof ApolloError) {
|
||||
handleMetadataError(error, {
|
||||
primaryMetadataName: 'viewGroup',
|
||||
});
|
||||
} else {
|
||||
enqueueErrorSnackBar({ message: t`An error occurred.` });
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'failed',
|
||||
error,
|
||||
};
|
||||
}
|
||||
},
|
||||
[
|
||||
triggerViewGroupOptimisticEffect,
|
||||
deleteCoreViewGroupMutation,
|
||||
handleMetadataError,
|
||||
enqueueErrorSnackBar,
|
||||
],
|
||||
);
|
||||
|
||||
const destroyViewGroups = useCallback(
|
||||
async (
|
||||
destroyCoreViewGroupInputs: DestroyCoreViewGroupMutationVariables[],
|
||||
): Promise<
|
||||
MetadataRequestResult<
|
||||
Awaited<ReturnType<typeof destroyCoreViewGroupMutation>>[]
|
||||
>
|
||||
> => {
|
||||
if (destroyCoreViewGroupInputs.length === 0) {
|
||||
return {
|
||||
status: 'successful',
|
||||
response: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const results = await Promise.all(
|
||||
destroyCoreViewGroupInputs.map((variables) =>
|
||||
destroyCoreViewGroupMutation({
|
||||
variables,
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
return {
|
||||
status: 'successful',
|
||||
response: results,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof ApolloError) {
|
||||
handleMetadataError(error, {
|
||||
primaryMetadataName: 'viewGroup',
|
||||
});
|
||||
} else {
|
||||
enqueueErrorSnackBar({ message: t`An error occurred.` });
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'failed',
|
||||
error,
|
||||
};
|
||||
}
|
||||
},
|
||||
[destroyCoreViewGroupMutation, handleMetadataError, enqueueErrorSnackBar],
|
||||
);
|
||||
|
||||
return {
|
||||
createViewGroups,
|
||||
updateViewGroups,
|
||||
deleteViewGroups,
|
||||
destroyViewGroups,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -13,11 +13,8 @@ import { usePersistViewFilterRecords } from '@/views/hooks/internal/usePersistVi
|
||||
import { usePersistViewFilterGroupRecords } from '@/views/hooks/internal/usePersistViewFilterGroup';
|
||||
import { usePersistViewSortRecords } from '@/views/hooks/internal/usePersistViewSort';
|
||||
import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId';
|
||||
import { useTriggerViewGroupOptimisticEffect } from '@/views/optimistic-effects/hooks/useTriggerViewGroupOptimisticEffect';
|
||||
import { isPersistingViewFieldsState } from '@/views/states/isPersistingViewFieldsState';
|
||||
import { coreViewFromViewIdFamilySelector } from '@/views/states/selectors/coreViewFromViewIdFamilySelector';
|
||||
import { type GraphQLView } from '@/views/types/GraphQLView';
|
||||
import { type ViewGroup } from '@/views/types/ViewGroup';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
import { convertViewOpenRecordInToCore } from '@/views/utils/convertViewOpenRecordInToCore';
|
||||
import { convertViewTypeToCore } from '@/views/utils/convertViewTypeToCore';
|
||||
@@ -28,10 +25,7 @@ import { mapRecordSortToViewSort } from '@/views/utils/mapRecordSortToViewSort';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { v4 } from 'uuid';
|
||||
import {
|
||||
type CoreViewGroup,
|
||||
ViewCalendarLayout,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { ViewCalendarLayout } from '~/generated-metadata/graphql';
|
||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||
|
||||
export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
|
||||
@@ -50,9 +44,6 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
|
||||
|
||||
const { createViewSorts } = usePersistViewSortRecords();
|
||||
|
||||
const { triggerViewGroupOptimisticEffect } =
|
||||
useTriggerViewGroupOptimisticEffect();
|
||||
|
||||
const { createViewFilters } = usePersistViewFilterRecords();
|
||||
|
||||
const { createViewFilterGroups } = usePersistViewFilterGroupRecords();
|
||||
@@ -75,7 +66,7 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
|
||||
);
|
||||
|
||||
const createViewFromCurrentView = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
({ snapshot }) =>
|
||||
async (
|
||||
{
|
||||
id,
|
||||
@@ -120,53 +111,52 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
set(isPersistingViewFieldsState, true);
|
||||
|
||||
const viewType = type ?? sourceView.type;
|
||||
|
||||
const result = await createView({
|
||||
input: {
|
||||
id: id ?? v4(),
|
||||
name: name ?? sourceView.name,
|
||||
icon: icon ?? sourceView.icon,
|
||||
key: null,
|
||||
kanbanAggregateOperation: shouldCopyFiltersAndSortsAndAggregate
|
||||
? sourceView.kanbanAggregateOperation
|
||||
: undefined,
|
||||
kanbanAggregateOperationFieldMetadataId:
|
||||
shouldCopyFiltersAndSortsAndAggregate
|
||||
? sourceView.kanbanAggregateOperationFieldMetadataId
|
||||
const result = await createView(
|
||||
{
|
||||
input: {
|
||||
id: id ?? v4(),
|
||||
name: name ?? sourceView.name,
|
||||
icon: icon ?? sourceView.icon,
|
||||
key: null,
|
||||
kanbanAggregateOperation: shouldCopyFiltersAndSortsAndAggregate
|
||||
? sourceView.kanbanAggregateOperation
|
||||
: undefined,
|
||||
mainGroupByFieldMetadataId: shouldCopyFiltersAndSortsAndAggregate
|
||||
? sourceView.mainGroupByFieldMetadataId
|
||||
: mainGroupByFieldMetadataId,
|
||||
type: convertViewTypeToCore(viewType),
|
||||
objectMetadataId: sourceView.objectMetadataId,
|
||||
openRecordIn: convertViewOpenRecordInToCore(
|
||||
sourceView.openRecordIn,
|
||||
),
|
||||
anyFieldFilterValue: anyFieldFilterValue,
|
||||
calendarLayout:
|
||||
viewType === ViewType.Calendar
|
||||
? ViewCalendarLayout.MONTH
|
||||
: undefined,
|
||||
calendarFieldMetadataId:
|
||||
viewType === ViewType.Calendar
|
||||
? calendarFieldMetadataId
|
||||
: undefined,
|
||||
visibility,
|
||||
kanbanAggregateOperationFieldMetadataId:
|
||||
shouldCopyFiltersAndSortsAndAggregate
|
||||
? sourceView.kanbanAggregateOperationFieldMetadataId
|
||||
: undefined,
|
||||
mainGroupByFieldMetadataId: shouldCopyFiltersAndSortsAndAggregate
|
||||
? sourceView.mainGroupByFieldMetadataId
|
||||
: mainGroupByFieldMetadataId,
|
||||
type: convertViewTypeToCore(viewType),
|
||||
objectMetadataId: sourceView.objectMetadataId,
|
||||
openRecordIn: convertViewOpenRecordInToCore(
|
||||
sourceView.openRecordIn,
|
||||
),
|
||||
anyFieldFilterValue: anyFieldFilterValue,
|
||||
calendarLayout:
|
||||
viewType === ViewType.Calendar
|
||||
? ViewCalendarLayout.MONTH
|
||||
: undefined,
|
||||
calendarFieldMetadataId:
|
||||
viewType === ViewType.Calendar
|
||||
? calendarFieldMetadataId
|
||||
: undefined,
|
||||
visibility,
|
||||
},
|
||||
},
|
||||
});
|
||||
objectMetadataItem.id,
|
||||
);
|
||||
|
||||
if (result.status === 'failed') {
|
||||
set(isPersistingViewFieldsState, false);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const newViewId = result.response.data?.createCoreView.id;
|
||||
|
||||
if (isUndefinedOrNull(newViewId)) {
|
||||
set(isPersistingViewFieldsState, false);
|
||||
throw new Error('Failed to create view');
|
||||
}
|
||||
|
||||
@@ -181,60 +171,9 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
|
||||
});
|
||||
|
||||
if (fieldResult.status === 'failed') {
|
||||
set(isPersistingViewFieldsState, false);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (type === ViewType.Kanban) {
|
||||
if (
|
||||
!isDefined(mainGroupByFieldMetadataId) ||
|
||||
mainGroupByFieldMetadataId === ''
|
||||
) {
|
||||
throw new Error('Kanban view must have a kanban field');
|
||||
}
|
||||
|
||||
const viewGroupsToCreate =
|
||||
objectMetadataItem.fields
|
||||
?.find((field) => field.id === mainGroupByFieldMetadataId)
|
||||
?.options?.map(
|
||||
(option, index) =>
|
||||
({
|
||||
id: v4(),
|
||||
__typename: 'ViewGroup',
|
||||
fieldValue: option.value,
|
||||
isVisible: true,
|
||||
position: index,
|
||||
}) satisfies ViewGroup,
|
||||
) ?? [];
|
||||
|
||||
if (
|
||||
objectMetadataItem.fields.find(
|
||||
(field) => field.id === mainGroupByFieldMetadataId,
|
||||
)?.isNullable === true
|
||||
) {
|
||||
viewGroupsToCreate.push({
|
||||
__typename: 'ViewGroup',
|
||||
id: v4(),
|
||||
fieldValue: '',
|
||||
position: viewGroupsToCreate.length,
|
||||
isVisible: true,
|
||||
} satisfies ViewGroup);
|
||||
}
|
||||
|
||||
triggerViewGroupOptimisticEffect({
|
||||
createdViewGroups: viewGroupsToCreate.map(
|
||||
({ __typename, ...viewGroup }) =>
|
||||
({
|
||||
...viewGroup,
|
||||
viewId: newViewId,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
deletedAt: null,
|
||||
}) as Omit<CoreViewGroup, 'workspaceId'>,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
if (shouldCopyFiltersAndSortsAndAggregate === true) {
|
||||
const viewFilterGroupsToCopy = currentRecordFilterGroups.map(
|
||||
(recordFilterGroup) =>
|
||||
@@ -285,7 +224,6 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
|
||||
const filterResult = await createViewFilters(createViewFilterInputs);
|
||||
|
||||
if (filterResult.status === 'failed') {
|
||||
set(isPersistingViewFieldsState, false);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -294,24 +232,21 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
|
||||
|
||||
await refreshCoreViewsByObjectMetadataId(objectMetadataItem.id);
|
||||
|
||||
set(isPersistingViewFieldsState, false);
|
||||
return newViewId;
|
||||
},
|
||||
[
|
||||
currentViewIdCallbackState,
|
||||
createViewFields,
|
||||
createView,
|
||||
anyFieldFilterValue,
|
||||
objectMetadataItem.fields,
|
||||
objectMetadataItem.id,
|
||||
objectMetadataItem,
|
||||
createViewFields,
|
||||
refreshCoreViewsByObjectMetadataId,
|
||||
currentRecordFilterGroups,
|
||||
currentRecordFilters,
|
||||
currentRecordSorts,
|
||||
createViewFilterGroups,
|
||||
createViewFilters,
|
||||
createViewSorts,
|
||||
refreshCoreViewsByObjectMetadataId,
|
||||
triggerViewGroupOptimisticEffect,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-
|
||||
import { usePersistViewField } from '@/views/hooks/internal/usePersistViewField';
|
||||
import { useCanPersistViewChanges } from '@/views/hooks/useCanPersistViewChanges';
|
||||
import { useGetViewFromPrefetchState } from '@/views/hooks/useGetViewFromPrefetchState';
|
||||
import { isPersistingViewFieldsState } from '@/views/states/isPersistingViewFieldsState';
|
||||
import { type ViewField } from '@/views/types/ViewField';
|
||||
import {
|
||||
type CreateViewFieldInput,
|
||||
@@ -25,7 +24,7 @@ export const useSaveCurrentViewFields = () => {
|
||||
);
|
||||
|
||||
const saveViewFields = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
({ snapshot }) =>
|
||||
async (viewFieldsToSave: Omit<ViewField, 'definition'>[]) => {
|
||||
if (!canPersistChanges) {
|
||||
return;
|
||||
@@ -39,8 +38,6 @@ export const useSaveCurrentViewFields = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
set(isPersistingViewFieldsState, true);
|
||||
|
||||
const view = getViewFromPrefetchState(currentViewId);
|
||||
|
||||
if (isUndefinedOrNull(view)) {
|
||||
@@ -127,8 +124,6 @@ export const useSaveCurrentViewFields = () => {
|
||||
createViewFields({ inputs: viewFieldsToCreate }),
|
||||
updateViewFields(viewFieldsToUpdate),
|
||||
]);
|
||||
|
||||
set(isPersistingViewFieldsState, false);
|
||||
},
|
||||
[
|
||||
canPersistChanges,
|
||||
|
||||
@@ -12,7 +12,7 @@ import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||
|
||||
export const useSaveCurrentViewGroups = () => {
|
||||
const { canPersistChanges } = useCanPersistViewChanges();
|
||||
const { createViewGroups, updateViewGroups } = usePersistViewGroupRecords();
|
||||
const { updateViewGroups } = usePersistViewGroupRecords();
|
||||
|
||||
const { getViewFromPrefetchState } = useGetViewFromPrefetchState();
|
||||
|
||||
@@ -150,33 +150,14 @@ export const useSaveCurrentViewGroups = () => {
|
||||
})
|
||||
.filter(isDefined);
|
||||
|
||||
const viewGroupsToCreate = viewGroupsToSave.filter(
|
||||
(viewFieldToSave) =>
|
||||
!currentViewGroups.some(
|
||||
(currentViewGroup) =>
|
||||
currentViewGroup.fieldValue === viewFieldToSave.fieldValue,
|
||||
),
|
||||
);
|
||||
|
||||
if (!isDefined(view.mainGroupByFieldMetadataId)) {
|
||||
throw new Error('mainGroupByFieldMetadataId is required');
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
createViewGroups({
|
||||
createCoreViewGroupInputs: {
|
||||
inputs: viewGroupsToCreate.map((viewGroupToCreate) => ({
|
||||
...viewGroupToCreate,
|
||||
viewId: view.id,
|
||||
})),
|
||||
},
|
||||
}),
|
||||
updateViewGroups(viewGroupsToUpdate),
|
||||
]);
|
||||
await updateViewGroups(viewGroupsToUpdate);
|
||||
},
|
||||
[
|
||||
canPersistChanges,
|
||||
createViewGroups,
|
||||
currentViewIdCallbackState,
|
||||
getViewFromPrefetchState,
|
||||
updateViewGroups,
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { useRecoilCallback, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
|
||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||
import { useLoadRecordIndexStates } from '@/object-record/record-index/hooks/useLoadRecordIndexStates';
|
||||
import { recordIndexViewTypeState } from '@/object-record/record-index/states/recordIndexViewTypeState';
|
||||
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
|
||||
import { useCanPersistViewChanges } from '@/views/hooks/useCanPersistViewChanges';
|
||||
import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId';
|
||||
import { coreViewFromViewIdFamilySelector } from '@/views/states/selectors/coreViewFromViewIdFamilySelector';
|
||||
import { type GraphQLView } from '@/views/types/GraphQLView';
|
||||
import { type View } from '@/views/types/View';
|
||||
import { type ViewGroup } from '@/views/types/ViewGroup';
|
||||
import { type ViewType } from '@/views/types/ViewType';
|
||||
import { convertUpdateViewInputToCore } from '@/views/utils/convertUpdateViewInputToCore';
|
||||
import { useMemo } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { v4 } from 'uuid';
|
||||
import { useUpdateCoreViewMutation } from '~/generated-metadata/graphql';
|
||||
|
||||
export const useUpdateCurrentView = () => {
|
||||
@@ -15,15 +23,70 @@ export const useUpdateCurrentView = () => {
|
||||
const currentViewIdCallbackState = useRecoilComponentCallbackState(
|
||||
contextStoreCurrentViewIdComponentState,
|
||||
);
|
||||
const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
|
||||
const { loadRecordIndexStates } = useLoadRecordIndexStates();
|
||||
const setRecordIndexViewType = useSetRecoilState(recordIndexViewTypeState);
|
||||
|
||||
const [updateOneCoreView] = useUpdateCoreViewMutation();
|
||||
const { refreshCoreViewsByObjectMetadataId } =
|
||||
useRefreshCoreViewsByObjectMetadataId();
|
||||
|
||||
const [updateOneCoreView] = useUpdateCoreViewMutation();
|
||||
const getViewGroupsToCreateAtViewUpdate = useMemo(() => {
|
||||
return ({
|
||||
existingView,
|
||||
newMainGroupByFieldMetadataId,
|
||||
}: {
|
||||
existingView: View;
|
||||
newMainGroupByFieldMetadataId?: string | null;
|
||||
}) => {
|
||||
if (newMainGroupByFieldMetadataId === undefined) {
|
||||
return {};
|
||||
}
|
||||
|
||||
let viewGroupsToCreate: ViewGroup[] = [];
|
||||
|
||||
if (
|
||||
newMainGroupByFieldMetadataId !==
|
||||
existingView.mainGroupByFieldMetadataId
|
||||
) {
|
||||
if (newMainGroupByFieldMetadataId !== null) {
|
||||
viewGroupsToCreate =
|
||||
objectMetadataItem.fields
|
||||
?.find((field) => field.id === newMainGroupByFieldMetadataId)
|
||||
?.options?.map(
|
||||
(option, index) =>
|
||||
({
|
||||
id: v4(),
|
||||
__typename: 'ViewGroup',
|
||||
fieldValue: option.value,
|
||||
isVisible: true,
|
||||
position: index,
|
||||
}) satisfies ViewGroup,
|
||||
) ?? [];
|
||||
|
||||
if (
|
||||
objectMetadataItem.fields.find(
|
||||
(field) => field.id === newMainGroupByFieldMetadataId,
|
||||
)?.isNullable === true
|
||||
) {
|
||||
viewGroupsToCreate.push({
|
||||
__typename: 'ViewGroup',
|
||||
id: v4(),
|
||||
fieldValue: '',
|
||||
position: viewGroupsToCreate.length,
|
||||
isVisible: true,
|
||||
} satisfies ViewGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { viewGroupsToCreate };
|
||||
};
|
||||
}, [objectMetadataItem.fields]);
|
||||
|
||||
const updateCurrentView = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
async (view: Partial<GraphQLView>) => {
|
||||
async (view: Partial<GraphQLView> & { type?: ViewType }) => {
|
||||
if (!canPersistChanges) {
|
||||
return;
|
||||
}
|
||||
@@ -53,6 +116,31 @@ export const useUpdateCurrentView = () => {
|
||||
input,
|
||||
},
|
||||
});
|
||||
|
||||
if (
|
||||
input.mainGroupByFieldMetadataId !== undefined &&
|
||||
currentView.mainGroupByFieldMetadataId !==
|
||||
input.mainGroupByFieldMetadataId
|
||||
) {
|
||||
const { viewGroupsToCreate } = getViewGroupsToCreateAtViewUpdate({
|
||||
existingView: currentView,
|
||||
newMainGroupByFieldMetadataId: input.mainGroupByFieldMetadataId,
|
||||
});
|
||||
|
||||
loadRecordIndexStates(
|
||||
{
|
||||
...currentView,
|
||||
mainGroupByFieldMetadataId: input.mainGroupByFieldMetadataId,
|
||||
viewGroups: viewGroupsToCreate ?? [],
|
||||
},
|
||||
objectMetadataItem,
|
||||
);
|
||||
}
|
||||
|
||||
if (isDefined(view.type)) {
|
||||
setRecordIndexViewType(view.type);
|
||||
}
|
||||
|
||||
await refreshCoreViewsByObjectMetadataId(
|
||||
currentView.objectMetadataId,
|
||||
);
|
||||
@@ -61,7 +149,11 @@ export const useUpdateCurrentView = () => {
|
||||
[
|
||||
canPersistChanges,
|
||||
currentViewIdCallbackState,
|
||||
getViewGroupsToCreateAtViewUpdate,
|
||||
loadRecordIndexStates,
|
||||
objectMetadataItem,
|
||||
refreshCoreViewsByObjectMetadataId,
|
||||
setRecordIndexViewType,
|
||||
updateOneCoreView,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { useTriggerViewGroupOptimisticEffect } from '@/views/optimistic-effects/hooks/useTriggerViewGroupOptimisticEffect';
|
||||
import { type ViewGroup } from '@/views/types/ViewGroup';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { v4 } from 'uuid';
|
||||
import { type CoreViewGroup } from '~/generated/graphql';
|
||||
|
||||
const useViewsSideEffectsOnViewGroups = () => {
|
||||
const { triggerViewGroupOptimisticEffect } =
|
||||
useTriggerViewGroupOptimisticEffect();
|
||||
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
|
||||
const triggerViewGroupOptimisticEffectAtViewCreation = ({
|
||||
newViewId,
|
||||
mainGroupByFieldMetadataId,
|
||||
objectMetadataItemId,
|
||||
}: {
|
||||
newViewId: string;
|
||||
mainGroupByFieldMetadataId?: string | null;
|
||||
objectMetadataItemId: string;
|
||||
}) => {
|
||||
if (!isDefined(mainGroupByFieldMetadataId)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const objectMetadataItem = objectMetadataItems.find(
|
||||
(objectMetadataItem) => objectMetadataItem.id === objectMetadataItemId,
|
||||
);
|
||||
|
||||
if (!isDefined(objectMetadataItem)) {
|
||||
throw new Error('Object metadata item not found');
|
||||
}
|
||||
|
||||
let viewGroupsToCreate: ViewGroup[] = [];
|
||||
|
||||
viewGroupsToCreate =
|
||||
objectMetadataItem.fields
|
||||
?.find((field) => field.id === mainGroupByFieldMetadataId)
|
||||
?.options?.map(
|
||||
(option, index) =>
|
||||
({
|
||||
id: v4(),
|
||||
__typename: 'ViewGroup',
|
||||
fieldValue: option.value,
|
||||
isVisible: true,
|
||||
position: index,
|
||||
}) satisfies ViewGroup,
|
||||
) ?? [];
|
||||
|
||||
if (
|
||||
objectMetadataItem.fields.find(
|
||||
(field) => field.id === mainGroupByFieldMetadataId,
|
||||
)?.isNullable === true
|
||||
) {
|
||||
viewGroupsToCreate.push({
|
||||
__typename: 'ViewGroup',
|
||||
id: v4(),
|
||||
fieldValue: '',
|
||||
position: viewGroupsToCreate.length,
|
||||
isVisible: true,
|
||||
} satisfies ViewGroup);
|
||||
}
|
||||
|
||||
triggerViewGroupOptimisticEffect({
|
||||
createdViewGroups: viewGroupsToCreate.map(
|
||||
({ __typename, ...viewGroup }) =>
|
||||
({
|
||||
...viewGroup,
|
||||
viewId: newViewId,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
deletedAt: null,
|
||||
}) as Omit<CoreViewGroup, 'workspaceId'>,
|
||||
),
|
||||
});
|
||||
|
||||
return { viewGroupsToCreate };
|
||||
};
|
||||
|
||||
return {
|
||||
triggerViewGroupOptimisticEffectAtViewCreation,
|
||||
};
|
||||
};
|
||||
|
||||
export { useViewsSideEffectsOnViewGroups };
|
||||
@@ -1,5 +0,0 @@
|
||||
import { createState } from 'twenty-ui/utilities';
|
||||
export const isPersistingViewFieldsState = createState<boolean>({
|
||||
key: 'isPersistingViewFieldsState',
|
||||
defaultValue: false,
|
||||
});
|
||||
@@ -46,5 +46,8 @@ export const convertUpdateViewInputToCore = (
|
||||
calendarFieldMetadataId: view.calendarFieldMetadataId,
|
||||
}),
|
||||
...(isDefined(view.visibility) && { visibility: view.visibility }),
|
||||
...(view.mainGroupByFieldMetadataId !== undefined && {
|
||||
mainGroupByFieldMetadataId: view.mainGroupByFieldMetadataId,
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user