diff --git a/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownCalendarFieldsContent.tsx b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownCalendarFieldsContent.tsx
new file mode 100644
index 0000000000..5f724f3b5f
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownCalendarFieldsContent.tsx
@@ -0,0 +1,111 @@
+import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
+import { useObjectOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsDropdown';
+import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
+import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
+import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
+import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
+import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
+import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
+import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
+import { useUpdateCurrentView } from '@/views/hooks/useUpdateCurrentView';
+import { useGetAvailableFieldsForCalendar } from '@/views/view-picker/hooks/useGetAvailableFieldsForCalendar';
+import { useLingui } from '@lingui/react/macro';
+import { useState } from 'react';
+import { isDefined } from 'twenty-shared/utils';
+import { IconChevronLeft, IconSettings, useIcons } from 'twenty-ui/display';
+import { MenuItem, MenuItemSelect } from 'twenty-ui/navigation';
+import { FieldMetadataType } from '~/generated-metadata/graphql';
+
+export const ObjectOptionsDropdownCalendarFieldsContent = () => {
+ const { t } = useLingui();
+ const { getIcon } = useIcons();
+ const [searchInput, setSearchInput] = useState('');
+
+ const { objectMetadataItem, resetContent, closeDropdown } =
+ useObjectOptionsDropdown();
+
+ const { currentView } = useGetCurrentViewOnly();
+ const { updateCurrentView } = useUpdateCurrentView();
+ const { navigateToDateFieldSettings } = useGetAvailableFieldsForCalendar();
+
+ const availableFieldsForCalendar = objectMetadataItem.fields.filter(
+ (field) =>
+ field.type === FieldMetadataType.DATE ||
+ field.type === FieldMetadataType.DATE_TIME,
+ );
+
+ const calendarFieldMetadata = currentView?.calendarFieldMetadataId
+ ? objectMetadataItem.fields.find(
+ (field) => field.id === currentView.calendarFieldMetadataId,
+ )
+ : undefined;
+
+ const filteredCalendarFields = availableFieldsForCalendar.filter((field) =>
+ field.label.toLowerCase().includes(searchInput.toLowerCase()),
+ );
+
+ const handleCalendarFieldChange = async (
+ fieldMetadataItem: FieldMetadataItem,
+ ) => {
+ await updateCurrentView({
+ calendarFieldMetadataId: fieldMetadataItem.id,
+ });
+ closeDropdown();
+ };
+
+ const handleResetCalendarField = async () => {
+ await updateCurrentView({
+ calendarFieldMetadataId: null,
+ });
+ closeDropdown();
+ };
+
+ return (
+
+ resetContent()}
+ Icon={IconChevronLeft}
+ />
+ }
+ >
+ {t`Date field`}
+
+ setSearchInput(event.target.value)}
+ />
+
+
+
+ {filteredCalendarFields.map((fieldMetadataItem) => (
+ handleCalendarFieldChange(fieldMetadataItem)}
+ LeftIcon={getIcon(fieldMetadataItem.icon)}
+ text={fieldMetadataItem.label}
+ />
+ ))}
+
+
+
+
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownContent.tsx b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownContent.tsx
index c45d8f5512..8eed2e017b 100644
--- a/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownContent.tsx
+++ b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownContent.tsx
@@ -1,3 +1,4 @@
+import { ObjectOptionsDropdownCalendarFieldsContent } from '@/object-record/object-options-dropdown/components/ObjectOptionsDropdownCalendarFieldsContent';
import { ObjectOptionsDropdownFieldsContent } from '@/object-record/object-options-dropdown/components/ObjectOptionsDropdownFieldsContent';
import { ObjectOptionsDropdownHiddenFieldsContent } from '@/object-record/object-options-dropdown/components/ObjectOptionsDropdownHiddenFieldsContent';
import { ObjectOptionsDropdownHiddenRecordGroupsContent } from '@/object-record/object-options-dropdown/components/ObjectOptionsDropdownHiddenRecordGroupsContent';
@@ -29,6 +30,8 @@ export const ObjectOptionsDropdownContent = () => {
return ;
case 'hiddenRecordGroups':
return ;
+ case 'calendarFields':
+ return ;
default:
return ;
}
diff --git a/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownCustomView.tsx b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownCustomView.tsx
index 8da219a604..dbc91780e0 100644
--- a/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownCustomView.tsx
+++ b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownCustomView.tsx
@@ -54,6 +54,12 @@ export const ObjectOptionsDropdownCustomView = ({
recordGroupFieldMetadataComponentState,
);
+ const calendarFieldMetadata = currentView?.calendarFieldMetadataId
+ ? objectMetadataItem.fields.find(
+ (field) => field.id === currentView.calendarFieldMetadataId,
+ )
+ : undefined;
+
const isDefaultView = currentView?.key === ViewKey.Index;
const { visibleBoardFields } = useObjectOptionsForBoard({
@@ -82,8 +88,11 @@ export const ObjectOptionsDropdownCustomView = ({
const selectableItemIdArray = [
'Layout',
- 'Fields',
- 'Group',
+ ...(customViewData?.type !== ViewType.Calendar ? ['Fields'] : []),
+ ...(customViewData?.type === ViewType.Calendar
+ ? ['CalendarDateField']
+ : []),
+ ...(customViewData?.type !== ViewType.Calendar ? ['Group'] : []),
'Copy link to view',
'Delete view',
];
@@ -125,47 +134,73 @@ export const ObjectOptionsDropdownCustomView = ({
- onContentChange('fields')}
- >
-
-
+ )}
{isDefaultView && (
{
recordGroupFieldMetadataComponentState,
);
+ const calendarFieldMetadata = currentView?.calendarFieldMetadataId
+ ? objectMetadataItem.fields.find(
+ (field) => field.id === currentView.calendarFieldMetadataId,
+ )
+ : undefined;
+
const { setAndPersistViewType } = useSetViewTypeFromLayoutOptionsMenu();
const { availableFieldsForKanban, navigateToSelectSettings } =
useGetAvailableFieldsForKanban();
@@ -106,6 +112,7 @@ export const ObjectOptionsDropdownLayoutContent = () => {
...(!isDefaultView && hasCalendarViewEnabled ? [ViewType.Calendar] : []),
ViewOpenRecordInType.SIDE_PANEL,
...(currentView?.type === ViewType.Kanban ? ['Group', 'Compact view'] : []),
+ ...(currentView?.type === ViewType.Calendar ? ['CalendarDateField'] : []),
];
const selectedItemId = useRecoilComponentValue(
@@ -271,6 +278,21 @@ export const ObjectOptionsDropdownLayoutContent = () => {
>
)}
+ {currentView?.type === ViewType.Calendar && (
+ onContentChange('calendarFields')}
+ >
+
+ )}
)}
diff --git a/packages/twenty-front/src/modules/object-record/object-options-dropdown/types/ObjectOptionsContentId.ts b/packages/twenty-front/src/modules/object-record/object-options-dropdown/types/ObjectOptionsContentId.ts
index 92ecb6fd92..4457c56b33 100644
--- a/packages/twenty-front/src/modules/object-record/object-options-dropdown/types/ObjectOptionsContentId.ts
+++ b/packages/twenty-front/src/modules/object-record/object-options-dropdown/types/ObjectOptionsContentId.ts
@@ -6,4 +6,5 @@ export type ObjectOptionsContentId =
| 'recordGroups'
| 'hiddenRecordGroups'
| 'recordGroupFields'
- | 'recordGroupSort';
+ | 'recordGroupSort'
+ | 'calendarFields';
diff --git a/packages/twenty-front/src/modules/views/types/CoreViewWithRelations.ts b/packages/twenty-front/src/modules/views/types/CoreViewWithRelations.ts
index c45c1f6a1e..a983753a7d 100644
--- a/packages/twenty-front/src/modules/views/types/CoreViewWithRelations.ts
+++ b/packages/twenty-front/src/modules/views/types/CoreViewWithRelations.ts
@@ -5,6 +5,7 @@ import {
type CoreViewFilter,
type CoreViewFilterGroup,
type CoreViewGroup,
+ type ViewCalendarLayout,
type ViewKey,
type ViewOpenRecordIn,
type ViewType,
@@ -30,6 +31,8 @@ export type CoreViewWithRelations = {
viewSorts: CoreViewSortEssential[];
kanbanAggregateOperation?: AggregateOperations | null;
kanbanAggregateOperationFieldMetadataId?: string | null;
+ calendarFieldMetadataId?: string | null;
+ calendarLayout?: ViewCalendarLayout | null;
position: number;
icon: string;
openRecordIn: ViewOpenRecordIn;
diff --git a/packages/twenty-front/src/modules/views/types/View.ts b/packages/twenty-front/src/modules/views/types/View.ts
index 7e335fe52d..bde7957f13 100644
--- a/packages/twenty-front/src/modules/views/types/View.ts
+++ b/packages/twenty-front/src/modules/views/types/View.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 View = {
id: string;
@@ -26,6 +27,8 @@ export type View = {
kanbanFieldMetadataId: string;
kanbanAggregateOperation: AggregateOperations | null;
kanbanAggregateOperationFieldMetadataId: string | null;
+ calendarFieldMetadataId?: string | null;
+ calendarLayout?: ViewCalendarLayout | null;
position: number;
icon: string;
openRecordIn: ViewOpenRecordInType;
diff --git a/packages/twenty-front/src/modules/views/utils/convertCoreViewToView.ts b/packages/twenty-front/src/modules/views/utils/convertCoreViewToView.ts
index c88d01c644..d1ed0fa67a 100644
--- a/packages/twenty-front/src/modules/views/utils/convertCoreViewToView.ts
+++ b/packages/twenty-front/src/modules/views/utils/convertCoreViewToView.ts
@@ -41,6 +41,8 @@ export const convertCoreViewToView = (
kanbanAggregateOperation: coreView.kanbanAggregateOperation ?? null,
kanbanAggregateOperationFieldMetadataId:
coreView.kanbanAggregateOperationFieldMetadataId ?? null,
+ calendarFieldMetadataId: coreView.calendarFieldMetadataId ?? null,
+ calendarLayout: coreView.calendarLayout ?? null,
position: coreView.position,
icon: coreView.icon,
openRecordIn: convertedOpenRecordIn,
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 4626955d46..1cacc63beb 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
@@ -2,8 +2,10 @@ import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/ho
import { ViewType } from '@/views/types/ViewType';
import { useCreateViewFromCurrentState } from '@/views/view-picker/hooks/useCreateViewFromCurrentState';
import { useDeleteViewFromCurrentState } from '@/views/view-picker/hooks/useDeleteViewFromCurrentState';
+import { useGetAvailableFieldsForCalendar } from '@/views/view-picker/hooks/useGetAvailableFieldsForCalendar';
import { useGetAvailableFieldsForKanban } from '@/views/view-picker/hooks/useGetAvailableFieldsForKanban';
import { useViewPickerMode } from '@/views/view-picker/hooks/useViewPickerMode';
+import { viewPickerCalendarFieldMetadataIdComponentState } from '@/views/view-picker/states/viewPickerCalendarFieldMetadataIdComponentState';
import { viewPickerIsPersistingComponentState } from '@/views/view-picker/states/viewPickerIsPersistingComponentState';
import { viewPickerKanbanFieldMetadataIdComponentState } from '@/views/view-picker/states/viewPickerKanbanFieldMetadataIdComponentState';
import { viewPickerTypeComponentState } from '@/views/view-picker/states/viewPickerTypeComponentState';
@@ -14,6 +16,8 @@ export const ViewPickerCreateButton = () => {
const { t } = useLingui();
const { availableFieldsForKanban, navigateToSelectSettings } =
useGetAvailableFieldsForKanban();
+ const { availableFieldsForCalendar, navigateToDateFieldSettings } =
+ useGetAvailableFieldsForCalendar();
const { viewPickerMode } = useViewPickerMode();
const viewPickerType = useRecoilComponentValue(viewPickerTypeComponentState);
@@ -23,6 +27,9 @@ export const ViewPickerCreateButton = () => {
const viewPickerKanbanFieldMetadataId = useRecoilComponentValue(
viewPickerKanbanFieldMetadataIdComponentState,
);
+ const viewPickerCalendarFieldMetadataId = useRecoilComponentValue(
+ viewPickerCalendarFieldMetadataIdComponentState,
+ );
const { createViewFromCurrentState } = useCreateViewFromCurrentState();
const { deleteViewFromCurrentState } = useDeleteViewFromCurrentState();
@@ -63,6 +70,22 @@ export const ViewPickerCreateButton = () => {
);
}
+ if (
+ viewPickerType === ViewType.Calendar &&
+ availableFieldsForCalendar.length === 0
+ ) {
+ return (
+
+ );
+ }
+
if (
viewPickerType !== ViewType.Kanban ||
viewPickerKanbanFieldMetadataId !== ''
@@ -78,7 +101,9 @@ export const ViewPickerCreateButton = () => {
disabled={
viewPickerIsPersisting ||
(viewPickerType === ViewType.Kanban &&
- viewPickerKanbanFieldMetadataId === '')
+ viewPickerKanbanFieldMetadataId === '') ||
+ (viewPickerType === ViewType.Calendar &&
+ viewPickerCalendarFieldMetadataId === '')
}
/>
);