diff --git a/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownLayoutContent.tsx b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownLayoutContent.tsx
index 02000a1ec0..23ad6767f5 100644
--- a/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownLayoutContent.tsx
+++ b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownLayoutContent.tsx
@@ -18,6 +18,7 @@ import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
import { ViewType, viewTypeIconMapping } from '@/views/types/ViewType';
import { useGetAvailableFieldsForKanban } from '@/views/view-picker/hooks/useGetAvailableFieldsForKanban';
+import { useFeatureFlagsMap } from '@/workspace/hooks/useFeatureFlagsMap';
import { useLingui } from '@lingui/react/macro';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
@@ -31,6 +32,7 @@ import {
OverflowingTextWithTooltip,
} from 'twenty-ui/display';
import { MenuItem, MenuItemSelect, MenuItemToggle } from 'twenty-ui/navigation';
+import { FeatureFlagKey } from '~/generated/graphql';
export const ObjectOptionsDropdownLayoutContent = () => {
const { t } = useLingui();
@@ -79,9 +81,14 @@ export const ObjectOptionsDropdownLayoutContent = () => {
const isDefaultView = currentView?.key === 'INDEX';
const nbsp = '\u00A0';
+ const featureFlags = useFeatureFlagsMap();
+ const hasCalendarViewEnabled =
+ featureFlags[FeatureFlagKey.IS_CALENDAR_VIEW_ENABLED];
+
const selectableItemIdArray = [
ViewType.Table,
...(isDefaultView ? [] : [ViewType.Kanban]),
+ ...(!isDefaultView && hasCalendarViewEnabled ? [ViewType.Calendar] : []),
ViewOpenRecordInType.SIDE_PANEL,
...(currentView?.type === ViewType.Kanban ? ['Group', 'Compact view'] : []),
];
@@ -129,6 +136,24 @@ export const ObjectOptionsDropdownLayoutContent = () => {
}}
/>
+ {hasCalendarViewEnabled && (
+ {
+ setAndPersistViewType(ViewType.Calendar);
+ }}
+ >
+ {
+ setAndPersistViewType(ViewType.Calendar);
+ }}
+ />
+
+ )}
{
diff --git a/packages/twenty-front/src/modules/object-record/object-options-dropdown/hooks/useSetViewTypeFromLayoutOptionsMenu.ts b/packages/twenty-front/src/modules/object-record/object-options-dropdown/hooks/useSetViewTypeFromLayoutOptionsMenu.ts
index 42dd5cc124..16f76173a2 100644
--- a/packages/twenty-front/src/modules/object-record/object-options-dropdown/hooks/useSetViewTypeFromLayoutOptionsMenu.ts
+++ b/packages/twenty-front/src/modules/object-record/object-options-dropdown/hooks/useSetViewTypeFromLayoutOptionsMenu.ts
@@ -17,6 +17,7 @@ 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();
@@ -129,7 +130,7 @@ export const useSetViewTypeFromLayoutOptionsMenu = () => {
}
return await updateCurrentView(updateCurrentViewParams);
}
- case ViewType.Table:
+ case ViewType.Table: {
setRecordIndexViewType(viewType);
set(coreViewsState, [
...existingCoreViews.filter(
@@ -146,6 +147,27 @@ export const useSetViewTypeFromLayoutOptionsMenu = () => {
viewTypeIconMapping(viewType).displayName;
}
return await updateCurrentView(updateCurrentViewParams);
+ }
+ case ViewType.Calendar: {
+ setRecordIndexViewType(viewType);
+ set(coreViewsState, [
+ ...existingCoreViews.filter(
+ (coreView) => coreView.id !== currentView.id,
+ ),
+ {
+ ...currentCoreView,
+ type: convertViewTypeToCore(viewType),
+ calendarLayout: ViewCalendarLayout.MONTH,
+ },
+ ]);
+
+ if (shouldChangeIcon(currentView.icon, currentView.type)) {
+ updateCurrentViewParams.icon =
+ viewTypeIconMapping(viewType).displayName;
+ }
+ updateCurrentViewParams.calendarLayout = ViewCalendarLayout.MONTH;
+ return await updateCurrentView(updateCurrentViewParams);
+ }
default: {
return assertUnreachable(viewType);
}
@@ -177,6 +199,12 @@ export const useSetViewTypeFromLayoutOptionsMenu = () => {
) {
return true;
}
+ if (
+ oldViewType === ViewType.Calendar &&
+ oldIcon === viewTypeIconMapping(ViewType.Calendar).displayName
+ ) {
+ return true;
+ }
return false;
};
diff --git a/packages/twenty-front/src/modules/views/hooks/useCreateViewFromCurrentView.ts b/packages/twenty-front/src/modules/views/hooks/useCreateViewFromCurrentView.ts
index 9205270ae0..84e5966031 100644
--- a/packages/twenty-front/src/modules/views/hooks/useCreateViewFromCurrentView.ts
+++ b/packages/twenty-front/src/modules/views/hooks/useCreateViewFromCurrentView.ts
@@ -27,6 +27,7 @@ import { mapRecordSortToViewSort } from '@/views/utils/mapRecordSortToViewSort';
import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { v4 } from 'uuid';
+import { ViewCalendarLayout } from '~/generated-metadata/graphql';
import {
type CreateCoreViewFieldMutationVariables,
useCreateCoreViewMutation,
@@ -111,6 +112,8 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
set(isPersistingViewFieldsState, true);
+ const viewType = type ?? sourceView.type;
+
const result = await createCoreViewMutation({
variables: {
input: {
@@ -125,12 +128,16 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
shouldCopyFiltersAndSortsAndAggregate
? sourceView.kanbanAggregateOperationFieldMetadataId
: undefined,
- type: convertViewTypeToCore(type ?? sourceView.type),
+ type: convertViewTypeToCore(viewType),
objectMetadataId: sourceView.objectMetadataId,
openRecordIn: convertViewOpenRecordInToCore(
sourceView.openRecordIn,
),
anyFieldFilterValue: anyFieldFilterValue,
+ calendarLayout:
+ viewType === ViewType.Calendar
+ ? ViewCalendarLayout.MONTH
+ : undefined,
},
},
});
diff --git a/packages/twenty-front/src/modules/views/types/GraphQLView.ts b/packages/twenty-front/src/modules/views/types/GraphQLView.ts
index 3d444929a4..590668647e 100644
--- a/packages/twenty-front/src/modules/views/types/GraphQLView.ts
+++ b/packages/twenty-front/src/modules/views/types/GraphQLView.ts
@@ -7,6 +7,7 @@ import { type ViewGroup } from '@/views/types/ViewGroup';
import { type ViewKey } from '@/views/types/ViewKey';
import { type ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
import { type ViewType } from '@/views/types/ViewType';
+import { type ViewCalendarLayout } from '~/generated/graphql';
export type GraphQLView = {
id: string;
@@ -30,4 +31,5 @@ export type GraphQLView = {
position: number;
icon: string;
anyFieldFilterValue?: string | null;
+ calendarLayout?: ViewCalendarLayout | null;
};
diff --git a/packages/twenty-front/src/modules/views/types/ViewType.ts b/packages/twenty-front/src/modules/views/types/ViewType.ts
index 8dc12d41b1..1a2e1fbb27 100644
--- a/packages/twenty-front/src/modules/views/types/ViewType.ts
+++ b/packages/twenty-front/src/modules/views/types/ViewType.ts
@@ -1,4 +1,5 @@
import {
+ IconCalendar,
type IconComponent,
IconLayoutKanban,
IconTable,
@@ -6,11 +7,13 @@ import {
export enum ViewType {
Table = 'table',
Kanban = 'kanban',
+ Calendar = 'calendar',
}
const VIEW_TYPE_ICON_MAPPING = [
{ icon: IconLayoutKanban, value: ViewType.Kanban },
{ icon: IconTable, value: ViewType.Table },
+ { icon: IconCalendar, value: ViewType.Calendar },
] as const satisfies {
icon: IconComponent;
value: ViewType;
diff --git a/packages/twenty-front/src/modules/views/utils/convertCoreViewTypeToViewType.ts b/packages/twenty-front/src/modules/views/utils/convertCoreViewTypeToViewType.ts
index ab2cf58e51..bb32be8f14 100644
--- a/packages/twenty-front/src/modules/views/utils/convertCoreViewTypeToViewType.ts
+++ b/packages/twenty-front/src/modules/views/utils/convertCoreViewTypeToViewType.ts
@@ -4,7 +4,15 @@ import { ViewType as CoreViewType } from '~/generated/graphql';
export const convertCoreViewTypeToViewType = (
coreViewType: CoreViewType,
): ViewType => {
- return coreViewType === CoreViewType.KANBAN
- ? ViewType.Kanban
- : ViewType.Table;
+ const coreViewTypeToViewTypeMapping = {
+ [CoreViewType.KANBAN]: ViewType.Kanban,
+ [CoreViewType.TABLE]: ViewType.Table,
+ [CoreViewType.CALENDAR]: ViewType.Calendar,
+ };
+
+ return (
+ coreViewTypeToViewTypeMapping[
+ coreViewType as keyof typeof coreViewTypeToViewTypeMapping
+ ] ?? ViewType.Table
+ );
};
diff --git a/packages/twenty-front/src/modules/views/utils/convertUpdateViewInputToCore.ts b/packages/twenty-front/src/modules/views/utils/convertUpdateViewInputToCore.ts
index 2ee0db0dd5..4669e4c18a 100644
--- a/packages/twenty-front/src/modules/views/utils/convertUpdateViewInputToCore.ts
+++ b/packages/twenty-front/src/modules/views/utils/convertUpdateViewInputToCore.ts
@@ -37,5 +37,6 @@ export const convertUpdateViewInputToCore = (
...(convertedKey && { key: convertedKey }),
...(convertedOpenRecordIn && { openRecordIn: convertedOpenRecordIn }),
...(convertedType && { type: convertedType }),
+ ...(view.calendarLayout && { calendarLayout: view.calendarLayout }),
};
};
diff --git a/packages/twenty-front/src/modules/views/utils/convertViewTypeToCore.ts b/packages/twenty-front/src/modules/views/utils/convertViewTypeToCore.ts
index fbc568c50d..c2e85e249c 100644
--- a/packages/twenty-front/src/modules/views/utils/convertViewTypeToCore.ts
+++ b/packages/twenty-front/src/modules/views/utils/convertViewTypeToCore.ts
@@ -1,5 +1,14 @@
import { ViewType } from '~/generated-metadata/graphql';
export const convertViewTypeToCore = (viewType: string): ViewType => {
- return viewType === 'kanban' ? ViewType.KANBAN : ViewType.TABLE;
+ const viewTypeToCoreMapping = {
+ kanban: ViewType.KANBAN,
+ table: ViewType.TABLE,
+ calendar: ViewType.CALENDAR,
+ };
+
+ return (
+ viewTypeToCoreMapping[viewType as keyof typeof viewTypeToCoreMapping] ??
+ ViewType.TABLE
+ );
};
diff --git a/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerContentCreateMode.tsx b/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerContentCreateMode.tsx
index 2dce44fc9b..aab45ae55c 100644
--- a/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerContentCreateMode.tsx
+++ b/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerContentCreateMode.tsx
@@ -33,9 +33,11 @@ import { viewPickerIsPersistingComponentState } from '@/views/view-picker/states
import { viewPickerKanbanFieldMetadataIdComponentState } from '@/views/view-picker/states/viewPickerKanbanFieldMetadataIdComponentState';
import { viewPickerSelectedIconComponentState } from '@/views/view-picker/states/viewPickerSelectedIconComponentState';
import { viewPickerTypeComponentState } from '@/views/view-picker/states/viewPickerTypeComponentState';
+import { useFeatureFlagsMap } from '@/workspace/hooks/useFeatureFlagsMap';
import { useLingui } from '@lingui/react/macro';
import { useMemo, useState } from 'react';
import { IconX } from 'twenty-ui/display';
+import { FeatureFlagKey } from '~/generated/graphql';
const StyledNoKanbanFieldAvailableContainer = styled.div`
color: ${({ theme }) => theme.font.color.light};
@@ -82,6 +84,17 @@ export const ViewPickerContentCreateMode = () => {
const { availableFieldsForKanban } = useGetAvailableFieldsForKanban();
+ const featureFlags = useFeatureFlagsMap();
+
+ const hasCalendarViewEnabled =
+ featureFlags[FeatureFlagKey.IS_CALENDAR_VIEW_ENABLED];
+
+ const viewPickerTypeOptions = VIEW_PICKER_TYPE_SELECT_OPTIONS.filter(
+ (option) => {
+ return option.value !== ViewType.Calendar || hasCalendarViewEnabled;
+ },
+ );
+
useHotkeysOnFocusedElement({
keys: [Key.Enter],
callback: async () => {
@@ -164,7 +177,7 @@ export const ViewPickerContentCreateMode = () => {
setViewPickerIsDirty(true);
setViewPickerType(value);
}}
- options={VIEW_PICKER_TYPE_SELECT_OPTIONS.map((option) => ({
+ options={viewPickerTypeOptions.map((option) => ({
...option,
label: t(option.label),
}))}
diff --git a/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerCreateButton.tsx b/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerCreateButton.tsx
index a57015f2e5..4626955d46 100644
--- a/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerCreateButton.tsx
+++ b/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerCreateButton.tsx
@@ -64,7 +64,7 @@ export const ViewPickerCreateButton = () => {
}
if (
- viewPickerType === ViewType.Table ||
+ viewPickerType !== ViewType.Kanban ||
viewPickerKanbanFieldMetadataId !== ''
) {
return (
diff --git a/packages/twenty-front/src/modules/views/view-picker/constants/ViewPickerTypeSelectOptions.ts b/packages/twenty-front/src/modules/views/view-picker/constants/ViewPickerTypeSelectOptions.ts
index 3025a19ba1..3caf6269b5 100644
--- a/packages/twenty-front/src/modules/views/view-picker/constants/ViewPickerTypeSelectOptions.ts
+++ b/packages/twenty-front/src/modules/views/view-picker/constants/ViewPickerTypeSelectOptions.ts
@@ -12,4 +12,9 @@ export const VIEW_PICKER_TYPE_SELECT_OPTIONS = [
label: msg`Kanban`,
Icon: viewTypeIconMapping(ViewType.Kanban),
},
+ {
+ value: ViewType.Calendar,
+ label: msg`Calendar`,
+ Icon: viewTypeIconMapping(ViewType.Calendar),
+ },
];