From 1cdfc0fd629c69ebff4f1d8a3d854048f28bc701 Mon Sep 17 00:00:00 2001 From: Weiko Date: Mon, 15 Sep 2025 13:58:06 +0200 Subject: [PATCH] Add calendar view layout picker in view options menu (#14495) Screenshot 2025-09-15 at 12 55 20 Screenshot 2025-09-15 at 12 55 12 Screenshot 2025-09-15 at 12 55 08 --- ...ctOptionsDropdownCalendarFieldsContent.tsx | 13 -- ...jectOptionsDropdownCalendarViewContent.tsx | 115 ++++++++++++++++ .../ObjectOptionsDropdownContent.tsx | 3 + .../ObjectOptionsDropdownCustomView.tsx | 129 +++++++++++------- .../ObjectOptionsDropdownLayoutContent.tsx | 64 ++++++--- .../types/ObjectOptionsContentId.ts | 3 +- .../states/recordIndexCalendarLayoutState.ts | 7 + .../display/icon/components/TablerIcons.ts | 2 + packages/twenty-ui/src/display/index.ts | 2 + 9 files changed, 256 insertions(+), 82 deletions(-) create mode 100644 packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownCalendarViewContent.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-index/states/recordIndexCalendarLayoutState.ts 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 index 5f724f3b5f..aa8c1e55a9 100644 --- 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 @@ -11,7 +11,6 @@ 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'; @@ -53,13 +52,6 @@ export const ObjectOptionsDropdownCalendarFieldsContent = () => { closeDropdown(); }; - const handleResetCalendarField = async () => { - await updateCurrentView({ - calendarFieldMetadataId: null, - }); - closeDropdown(); - }; - return ( { /> - {filteredCalendarFields.map((fieldMetadataItem) => ( { + const { resetContent } = useObjectOptionsDropdown(); + const recordIndexCalendarLayout = useRecoilValue( + recordIndexCalendarLayoutState, + ); + const setRecordIndexCalendarLayout = useSetRecoilState( + recordIndexCalendarLayoutState, + ); + const { updateCurrentView } = useUpdateCurrentView(); + + const selectedItemId = useRecoilComponentValue( + selectedItemIdComponentState, + OBJECT_OPTIONS_DROPDOWN_ID, + ); + + const { closeDropdown } = useObjectOptionsDropdown(); + + const selectableItemIdArray = [ + ViewCalendarLayout.WEEK, + ViewCalendarLayout.MONTH, + ViewCalendarLayout.DAY, + ]; + + const handleCalendarViewChange = async (calendarView: ViewCalendarLayout) => { + setRecordIndexCalendarLayout(calendarView); + await updateCurrentView({ + calendarLayout: calendarView, + }); + closeDropdown(); + }; + + return ( + + + } + > + {t`Calendar View`} + + + + { + handleCalendarViewChange(ViewCalendarLayout.WEEK); + }} + > + + + handleCalendarViewChange(ViewCalendarLayout.MONTH)} + > + handleCalendarViewChange(ViewCalendarLayout.MONTH)} + focused={selectedItemId === ViewCalendarLayout.MONTH} + /> + + handleCalendarViewChange(ViewCalendarLayout.DAY)} + > + + + + + + ); +}; 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 8eed2e017b..44e796e514 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,4 +1,5 @@ import { ObjectOptionsDropdownCalendarFieldsContent } from '@/object-record/object-options-dropdown/components/ObjectOptionsDropdownCalendarFieldsContent'; +import { ObjectOptionsDropdownCalendarViewContent } from '@/object-record/object-options-dropdown/components/ObjectOptionsDropdownCalendarViewContent'; 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'; @@ -30,6 +31,8 @@ export const ObjectOptionsDropdownContent = () => { return ; case 'hiddenRecordGroups': return ; + case 'calendarView': + return ; case 'calendarFields': return ; default: 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 dbc91780e0..a60abf1059 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 @@ -3,6 +3,7 @@ import { OBJECT_OPTIONS_DROPDOWN_ID } from '@/object-record/object-options-dropd import { useObjectOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsDropdown'; import { useObjectOptionsForBoard } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForBoard'; import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState'; +import { recordIndexCalendarLayoutState } from '@/object-record/record-index/states/recordIndexCalendarLayoutState'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; @@ -19,15 +20,19 @@ import { useDeleteViewFromCurrentState } from '@/views/view-picker/hooks/useDele import { viewPickerReferenceViewIdComponentState } from '@/views/view-picker/states/viewPickerReferenceViewIdComponentState'; import { useTheme } from '@emotion/react'; import { useLingui } from '@lingui/react/macro'; +import { useRecoilValue } from 'recoil'; import { capitalize, isDefined } from 'twenty-shared/utils'; import { AppTooltip, + IconCalendar, + IconCalendarWeek, IconCopy, IconLayoutList, IconListDetails, IconTrash, } from 'twenty-ui/display'; import { MenuItem } from 'twenty-ui/navigation'; +import { ViewCalendarLayout } from '~/generated/graphql'; interface ObjectOptionsDropdownCustomViewProps { onBackToDefault?: () => void; @@ -62,6 +67,10 @@ export const ObjectOptionsDropdownCustomView = ({ const isDefaultView = currentView?.key === ViewKey.Index; + const recordIndexCalendarLayout = useRecoilValue( + recordIndexCalendarLayoutState, + ); + const { visibleBoardFields } = useObjectOptionsForBoard({ objectNameSingular: objectMetadataItem.nameSingular, recordBoardId: recordIndexId, @@ -90,7 +99,7 @@ export const ObjectOptionsDropdownCustomView = ({ 'Layout', ...(customViewData?.type !== ViewType.Calendar ? ['Fields'] : []), ...(customViewData?.type === ViewType.Calendar - ? ['CalendarDateField'] + ? ['CalendarDateField', 'CalendarView'] : []), ...(customViewData?.type !== ViewType.Calendar ? ['Group'] : []), 'Copy link to view', @@ -134,72 +143,92 @@ export const ObjectOptionsDropdownCustomView = ({ - {customViewData?.type !== ViewType.Calendar && ( - onContentChange('fields')} - > - onContentChange('fields')} - LeftIcon={IconListDetails} - text={t`Fields`} - contextualText={`${visibleBoardFields.length} shown`} - hasSubMenu - /> - - )} {customViewData?.type === ViewType.Calendar && ( -
+ <> +
+ onContentChange('calendarFields')} + > + onContentChange('calendarFields')} + LeftIcon={IconCalendar} + text={t`Date field`} + contextualText={ + isDefaultView + ? t`Not available on Default View` + : calendarFieldMetadata?.label + } + hasSubMenu + disabled={isDefaultView} + /> + +
onContentChange('calendarFields')} + itemId="CalendarView" + onEnter={() => onContentChange('calendarView')} > onContentChange('calendarFields')} - LeftIcon={IconLayoutList} - text={t`Date field`} + focused={selectedItemId === 'CalendarView'} + onClick={() => onContentChange('calendarView')} + LeftIcon={IconCalendarWeek} + text={t`Calendar view`} contextualText={ - isDefaultView - ? t`Not available on Default View` - : calendarFieldMetadata?.label + recordIndexCalendarLayout === ViewCalendarLayout.MONTH + ? t`Month` + : recordIndexCalendarLayout === ViewCalendarLayout.WEEK + ? t`Week` + : t`Day` } - hasSubMenu - disabled={isDefaultView} /> -
+ )} {customViewData?.type !== ViewType.Calendar && ( -
+ <> - isDefined(recordGroupFieldMetadata) - ? onContentChange('recordGroups') - : onContentChange('recordGroupFields') - } + itemId="Fields" + onEnter={() => onContentChange('fields')} > + focused={selectedItemId === 'Fields'} + onClick={() => onContentChange('fields')} + LeftIcon={IconListDetails} + text={t`Fields`} + contextualText={`${visibleBoardFields.length} shown`} + hasSubMenu + /> + +
+ isDefined(recordGroupFieldMetadata) ? onContentChange('recordGroups') : onContentChange('recordGroupFields') } - LeftIcon={IconLayoutList} - text={t`Group`} - contextualText={ - isDefaultView - ? t`Not available on Default View` - : recordGroupFieldMetadata?.label - } - hasSubMenu - disabled={isDefaultView} - /> - -
+ > + + isDefined(recordGroupFieldMetadata) + ? onContentChange('recordGroups') + : onContentChange('recordGroupFields') + } + LeftIcon={IconLayoutList} + text={t`Group`} + contextualText={ + isDefaultView + ? t`Not available on Default View` + : recordGroupFieldMetadata?.label + } + hasSubMenu + disabled={isDefaultView} + /> + +
+ )} {isDefaultView && ( { const { t } = useLingui(); @@ -55,7 +58,9 @@ export const ObjectOptionsDropdownLayoutContent = () => { }); const recordIndexOpenRecordIn = useRecoilValue(recordIndexOpenRecordInState); - + const recordIndexCalendarLayout = useRecoilValue( + recordIndexCalendarLayoutState, + ); const recordGroupFieldMetadata = useRecoilComponentValue( recordGroupFieldMetadataComponentState, ); @@ -112,7 +117,9 @@ export const ObjectOptionsDropdownLayoutContent = () => { ...(!isDefaultView && hasCalendarViewEnabled ? [ViewType.Calendar] : []), ViewOpenRecordInType.SIDE_PANEL, ...(currentView?.type === ViewType.Kanban ? ['Group', 'Compact view'] : []), - ...(currentView?.type === ViewType.Calendar ? ['CalendarDateField'] : []), + ...(currentView?.type === ViewType.Calendar + ? ['CalendarView', 'CalendarDateField'] + : []), ]; const selectedItemId = useRecoilComponentValue( @@ -204,6 +211,42 @@ export const ObjectOptionsDropdownLayoutContent = () => {
+ {currentView?.type === ViewType.Calendar && ( + <> + onContentChange('calendarFields')} + > + onContentChange('calendarFields')} + LeftIcon={IconCalendar} + text={t`Date field`} + contextualText={calendarFieldMetadata?.label} + hasSubMenu + /> + + onContentChange('calendarView')} + > + onContentChange('calendarView')} + LeftIcon={IconCalendarWeek} + text={t`Calendar view`} + contextualText={ + recordIndexCalendarLayout === ViewCalendarLayout.MONTH + ? t`Month` + : recordIndexCalendarLayout === ViewCalendarLayout.WEEK + ? t`Week` + : t`Day` + } + hasSubMenu + /> + + + )} { @@ -278,21 +321,6 @@ export const ObjectOptionsDropdownLayoutContent = () => { )} - {currentView?.type === ViewType.Calendar && ( - onContentChange('calendarFields')} - > - onContentChange('calendarFields')} - LeftIcon={IconLayoutList} - text={t`Date field`} - contextualText={calendarFieldMetadata?.label} - hasSubMenu - /> - - )} )} 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 4457c56b33..5dd37d7dff 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 @@ -7,4 +7,5 @@ export type ObjectOptionsContentId = | 'hiddenRecordGroups' | 'recordGroupFields' | 'recordGroupSort' - | 'calendarFields'; + | 'calendarFields' + | 'calendarView'; diff --git a/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexCalendarLayoutState.ts b/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexCalendarLayoutState.ts new file mode 100644 index 0000000000..fc76fa282f --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexCalendarLayoutState.ts @@ -0,0 +1,7 @@ +import { createState } from 'twenty-ui/utilities'; +import { ViewCalendarLayout } from '~/generated-metadata/graphql'; + +export const recordIndexCalendarLayoutState = createState({ + key: 'recordIndexCalendarLayoutState', + defaultValue: ViewCalendarLayout.MONTH, +}); diff --git a/packages/twenty-ui/src/display/icon/components/TablerIcons.ts b/packages/twenty-ui/src/display/icon/components/TablerIcons.ts index f944c70aea..81433b7ea6 100644 --- a/packages/twenty-ui/src/display/icon/components/TablerIcons.ts +++ b/packages/twenty-ui/src/display/icon/components/TablerIcons.ts @@ -44,6 +44,8 @@ export { IconCalendarRepeat, IconCalendarTime, IconCalendarX, + IconCalendarMonth, + IconCalendarWeek, IconChartBar, IconChartCandle, IconChartDots3, diff --git a/packages/twenty-ui/src/display/index.ts b/packages/twenty-ui/src/display/index.ts index 63a5e984cf..be42bb306f 100644 --- a/packages/twenty-ui/src/display/index.ts +++ b/packages/twenty-ui/src/display/index.ts @@ -106,6 +106,8 @@ export { IconCalendarRepeat, IconCalendarTime, IconCalendarX, + IconCalendarMonth, + IconCalendarWeek, IconChartBar, IconChartCandle, IconChartDots3,