diff --git a/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownCalendarDateFieldsContent.tsx b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownCalendarDateFieldsContent.tsx new file mode 100644 index 0000000000..c7cc594566 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownCalendarDateFieldsContent.tsx @@ -0,0 +1,95 @@ +import { OBJECT_OPTIONS_DROPDOWN_ID } from '@/object-record/object-options-dropdown/constants/ObjectOptionsDropdownId'; +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 { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth'; +import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; +import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; +import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState'; +import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; +import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly'; +import { useLingui } from '@lingui/react/macro'; +import { isDefined } from 'twenty-shared/utils'; +import { IconCalendar, IconChevronLeft } from 'twenty-ui/icon'; +import { MenuItem } from 'twenty-ui/navigation'; + +export const ObjectOptionsDropdownCalendarDateFieldsContent = () => { + const { t } = useLingui(); + + const { objectMetadataItem, resetContent, onContentChange } = + useObjectOptionsDropdown(); + + const { currentView } = useGetCurrentViewOnly(); + + const selectedItemId = useAtomComponentStateValue( + selectedItemIdComponentState, + OBJECT_OPTIONS_DROPDOWN_ID, + ); + + if (!isDefined(currentView)) { + return null; + } + + const calendarFieldMetadata = objectMetadataItem.fields.find( + (field) => field.id === currentView.calendarFieldMetadataId, + ); + + const calendarEndFieldMetadata = objectMetadataItem.fields.find( + (field) => field.id === currentView.calendarEndFieldMetadataId, + ); + + const selectableItemIdArray = ['CalendarDateField', 'CalendarEndDateField']; + + return ( + + resetContent()} + Icon={IconChevronLeft} + /> + } + > + {t`Date fields`} + + + + onContentChange('calendarFields')} + > + onContentChange('calendarFields')} + LeftIcon={IconCalendar} + text={t`Date field`} + contextualText={calendarFieldMetadata?.label} + contextualTextPosition="right" + hasSubMenu + /> + + onContentChange('calendarEndFields')} + > + onContentChange('calendarEndFields')} + LeftIcon={IconCalendar} + text={t`End date field`} + contextualText={calendarEndFieldMetadata?.label ?? t`None`} + contextualTextPosition="right" + hasSubMenu + /> + + + + + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownCalendarEndFieldsContent.tsx b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownCalendarEndFieldsContent.tsx index 1329ff05e5..cf6f1a055d 100644 --- a/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownCalendarEndFieldsContent.tsx +++ b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownCalendarEndFieldsContent.tsx @@ -2,11 +2,13 @@ import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSe import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { useObjectOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsDropdown'; import { recordIndexCalendarEndFieldMetadataIdComponentState } from '@/object-record/record-index/states/recordIndexCalendarEndFieldMetadataIdComponentState'; +import { groupCalendarFieldMetadataItemsByType } from '@/object-record/record-calendar/utils/groupCalendarFieldMetadataItemsByType'; 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 { DropdownMenuSectionLabel } from '@/ui/layout/dropdown/components/DropdownMenuSectionLabel'; import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly'; import { useUpdateCurrentView } from '@/views/hooks/useUpdateCurrentView'; @@ -14,7 +16,7 @@ import { useGetAvailableFieldsForCalendar } from '@/views/view-picker/hooks/useG import { getAvailableCalendarEndFieldMetadataItems } from '@/views/view-picker/utils/getAvailableCalendarEndFieldMetadataItems'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useLingui } from '@lingui/react/macro'; -import { useState } from 'react'; +import { Fragment, useState } from 'react'; import { isDefined } from 'twenty-shared/utils'; import { IconChevronLeft, useIcons } from 'twenty-ui/icon'; import { MenuItemSelect } from 'twenty-ui/navigation'; @@ -28,7 +30,7 @@ export const ObjectOptionsDropdownCalendarEndFieldsContent = () => { FeatureFlagKey.IS_CALENDAR_WEEK_VIEW_ENABLED, ); - const { resetContent, closeDropdown } = useObjectOptionsDropdown(); + const { onContentChange, closeDropdown } = useObjectOptionsDropdown(); const { currentView } = useGetCurrentViewOnly(); const { updateCurrentView } = useUpdateCurrentView(); @@ -49,6 +51,10 @@ export const ObjectOptionsDropdownCalendarEndFieldsContent = () => { field.label.toLowerCase().includes(searchInput.toLowerCase()), ); + const calendarEndFieldGroups = groupCalendarFieldMetadataItemsByType( + filteredCalendarEndFields, + ); + const handleCalendarEndFieldChange = async ( fieldMetadataItem: FieldMetadataItem | null, ) => { @@ -72,7 +78,7 @@ export const ObjectOptionsDropdownCalendarEndFieldsContent = () => { resetContent()} + onClick={() => onContentChange('calendarDateFields')} Icon={IconChevronLeft} /> } @@ -92,16 +98,22 @@ export const ObjectOptionsDropdownCalendarEndFieldsContent = () => { onClick={() => handleCalendarEndFieldChange(null)} text={t`None`} /> - {filteredCalendarEndFields.map((fieldMetadataItem) => ( - handleCalendarEndFieldChange(fieldMetadataItem)} - LeftIcon={getIcon(fieldMetadataItem.icon)} - text={fieldMetadataItem.label} - /> + {calendarEndFieldGroups.map((group) => ( + + + {group.fields.map((fieldMetadataItem) => ( + handleCalendarEndFieldChange(fieldMetadataItem)} + LeftIcon={getIcon(fieldMetadataItem.icon)} + text={fieldMetadataItem.label} + /> + ))} + ))} 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 65c6439242..448d998439 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 @@ -3,27 +3,34 @@ import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataIte import { useObjectOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsDropdown'; import { recordIndexCalendarEndFieldMetadataIdComponentState } from '@/object-record/record-index/states/recordIndexCalendarEndFieldMetadataIdComponentState'; import { recordIndexCalendarFieldMetadataIdComponentState } from '@/object-record/record-index/states/recordIndexCalendarFieldMetadataIdComponentState'; +import { groupCalendarFieldMetadataItemsByType } from '@/object-record/record-calendar/utils/groupCalendarFieldMetadataItemsByType'; 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 { DropdownMenuSectionLabel } from '@/ui/layout/dropdown/components/DropdownMenuSectionLabel'; 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 { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useLingui } from '@lingui/react/macro'; -import { useState } from 'react'; +import { Fragment, useState } from 'react'; import { isDefined } from 'twenty-shared/utils'; import { IconChevronLeft, IconSettings, useIcons } from 'twenty-ui/icon'; import { MenuItem, MenuItemSelect } from 'twenty-ui/navigation'; +import { FeatureFlagKey } from '~/generated-metadata/graphql'; export const ObjectOptionsDropdownCalendarFieldsContent = () => { const { t } = useLingui(); const { getIcon } = useIcons(); const [searchInput, setSearchInput] = useState(''); + const isCalendarWeekViewEnabled = useIsFeatureEnabled( + FeatureFlagKey.IS_CALENDAR_WEEK_VIEW_ENABLED, + ); - const { objectMetadataItem, resetContent, closeDropdown } = + const { objectMetadataItem, resetContent, onContentChange, closeDropdown } = useObjectOptionsDropdown(); const { currentView } = useGetCurrentViewOnly(); @@ -50,10 +57,28 @@ export const ObjectOptionsDropdownCalendarFieldsContent = () => { ) : undefined; - const filteredCalendarFields = availableFieldsForCalendar.filter((field) => + const availableCalendarFields = isDefined(calendarEndFieldMetadata) + ? availableFieldsForCalendar.filter( + (field) => field.type === calendarEndFieldMetadata.type, + ) + : availableFieldsForCalendar; + + const filteredCalendarFields = availableCalendarFields.filter((field) => field.label.toLowerCase().includes(searchInput.toLowerCase()), ); + const calendarFieldGroups = groupCalendarFieldMetadataItemsByType( + filteredCalendarFields, + ); + + const handleBack = () => { + if (isCalendarWeekViewEnabled) { + onContentChange('calendarDateFields'); + return; + } + resetContent(); + }; + const handleCalendarFieldChange = async ( fieldMetadataItem: FieldMetadataItem, ) => { @@ -82,7 +107,7 @@ export const ObjectOptionsDropdownCalendarFieldsContent = () => { resetContent()} + onClick={handleBack} Icon={IconChevronLeft} /> } @@ -97,14 +122,19 @@ export const ObjectOptionsDropdownCalendarFieldsContent = () => { /> - {filteredCalendarFields.map((fieldMetadataItem) => ( - handleCalendarFieldChange(fieldMetadataItem)} - LeftIcon={getIcon(fieldMetadataItem.icon)} - text={fieldMetadataItem.label} - /> + {calendarFieldGroups.map((group) => ( + + + {group.fields.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 8917a5905d..15b5d0d449 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 { ObjectOptionsDropdownAddRecordGroupContent } from '@/object-record/object-options-dropdown/components/ObjectOptionsDropdownAddRecordGroupContent'; +import { ObjectOptionsDropdownCalendarDateFieldsContent } from '@/object-record/object-options-dropdown/components/ObjectOptionsDropdownCalendarDateFieldsContent'; import { ObjectOptionsDropdownCalendarFieldsContent } from '@/object-record/object-options-dropdown/components/ObjectOptionsDropdownCalendarFieldsContent'; import { ObjectOptionsDropdownCalendarEndFieldsContent } from '@/object-record/object-options-dropdown/components/ObjectOptionsDropdownCalendarEndFieldsContent'; import { ObjectOptionsDropdownCalendarViewContent } from '@/object-record/object-options-dropdown/components/ObjectOptionsDropdownCalendarViewContent'; @@ -43,6 +44,12 @@ export const ObjectOptionsDropdownContent = () => { return ; case 'calendarView': return ; + case 'calendarDateFields': + return isCalendarWeekViewEnabled ? ( + + ) : ( + + ); case 'calendarFields': return ; case 'calendarEndFields': 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 5fffbc3dfe..3c7ef91b6d 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 @@ -74,12 +74,6 @@ export const ObjectOptionsDropdownCustomView = ({ ) : undefined; - const calendarEndFieldMetadata = currentView?.calendarEndFieldMetadataId - ? objectMetadataItem.fields.find( - (field) => field.id === currentView.calendarEndFieldMetadataId, - ) - : undefined; - const viewsOnCurrentObject = useAtomFamilySelectorValue( viewsFromObjectMetadataItemFamilySelector, { objectMetadataItemId: objectMetadataItem.id }, @@ -129,8 +123,9 @@ export const ObjectOptionsDropdownCustomView = ({ 'Fields', ...(customViewData?.type === ViewType.CALENDAR ? [ - 'CalendarDateField', - ...(isCalendarWeekViewEnabled ? ['CalendarEndDateField'] : []), + isCalendarWeekViewEnabled + ? 'CalendarDateFields' + : 'CalendarDateField', 'CalendarView', ] : []), @@ -196,42 +191,43 @@ export const ObjectOptionsDropdownCustomView = ({ {customViewData?.type === ViewType.CALENDAR && ( <> -
- onContentChange('calendarFields')} - > - onContentChange('calendarFields')} - LeftIcon={IconCalendar} - text={t`Date field`} - contextualText={ - isDefaultView - ? t`Not available on Default View` - : calendarFieldMetadata?.label - } - contextualTextPosition="right" - hasSubMenu - disabled={isDefaultView} - /> - -
- {isCalendarWeekViewEnabled && ( -
+ {isCalendarWeekViewEnabled ? ( +
onContentChange('calendarEndFields')} + itemId="CalendarDateFields" + onEnter={() => onContentChange('calendarDateFields')} > onContentChange('calendarEndFields')} + focused={selectedItemId === 'CalendarDateFields'} + onClick={() => onContentChange('calendarDateFields')} LeftIcon={IconCalendar} - text={t`End date field`} + text={t`Date fields`} contextualText={ isDefaultView ? t`Not available on Default View` - : (calendarEndFieldMetadata?.label ?? t`None`) + : calendarFieldMetadata?.label + } + contextualTextPosition="right" + hasSubMenu + disabled={isDefaultView} + /> + +
+ ) : ( +
+ onContentChange('calendarFields')} + > + onContentChange('calendarFields')} + LeftIcon={IconCalendar} + text={t`Date field`} + contextualText={ + isDefaultView + ? t`Not available on Default View` + : calendarFieldMetadata?.label } contextualTextPosition="right" hasSubMenu 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 e5a35b1baa..cd0c3115e0 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 @@ -87,12 +87,6 @@ export const ObjectOptionsDropdownLayoutContent = () => { ) : undefined; - const calendarEndFieldMetadata = currentView?.calendarEndFieldMetadataId - ? objectMetadataItem.fields.find( - (field) => field.id === currentView.calendarEndFieldMetadataId, - ) - : undefined; - const { setAndPersistViewType } = useSetViewTypeFromLayoutOptionsMenu(); const { availableFieldsForGrouping, navigateToSelectSettings } = useGetAvailableFieldsToGroupRecordsBy(); @@ -140,8 +134,9 @@ export const ObjectOptionsDropdownLayoutContent = () => { ...(currentView?.type === ViewType.CALENDAR ? [ 'CalendarView', - 'CalendarDateField', - ...(isCalendarWeekViewEnabled ? ['CalendarEndDateField'] : []), + isCalendarWeekViewEnabled + ? 'CalendarDateFields' + : 'CalendarDateField', ] : []), ...(currentView?.type !== ViewType.TABLE ? ['Compact view'] : []), @@ -237,33 +232,32 @@ export const ObjectOptionsDropdownLayoutContent = () => { {currentView?.type === ViewType.CALENDAR && ( <> - onContentChange('calendarFields')} - > - onContentChange('calendarFields')} - LeftIcon={IconCalendar} - text={t`Date field`} - contextualText={calendarFieldMetadata?.label} - contextualTextPosition="right" - hasSubMenu - /> - - {isCalendarWeekViewEnabled && ( + {isCalendarWeekViewEnabled ? ( onContentChange('calendarEndFields')} + itemId="CalendarDateFields" + onEnter={() => onContentChange('calendarDateFields')} > onContentChange('calendarEndFields')} + focused={selectedItemId === 'CalendarDateFields'} + onClick={() => onContentChange('calendarDateFields')} LeftIcon={IconCalendar} - text={t`End date field`} - contextualText={ - calendarEndFieldMetadata?.label ?? t`None` - } + text={t`Date fields`} + contextualText={calendarFieldMetadata?.label} + contextualTextPosition="right" + hasSubMenu + /> + + ) : ( + onContentChange('calendarFields')} + > + onContentChange('calendarFields')} + LeftIcon={IconCalendar} + text={t`Date field`} + contextualText={calendarFieldMetadata?.label} contextualTextPosition="right" 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 ce83017f31..c75112d482 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 @@ -8,6 +8,7 @@ export type ObjectOptionsContentId = | 'recordGroupFields' | 'recordGroupSort' | 'addRecordGroup' + | 'calendarDateFields' | 'calendarFields' | 'calendarEndFields' | 'calendarView' diff --git a/packages/twenty-front/src/modules/object-record/record-calendar/utils/groupCalendarFieldMetadataItemsByType.ts b/packages/twenty-front/src/modules/object-record/record-calendar/utils/groupCalendarFieldMetadataItemsByType.ts new file mode 100644 index 0000000000..586e771292 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-calendar/utils/groupCalendarFieldMetadataItemsByType.ts @@ -0,0 +1,20 @@ +import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; +import { SETTINGS_NON_COMPOSITE_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsNonCompositeFieldTypeConfigs'; +import { FieldMetadataType } from '~/generated-metadata/graphql'; + +const CALENDAR_FIELD_METADATA_TYPES = [ + FieldMetadataType.DATE, + FieldMetadataType.DATE_TIME, +] as const; + +export const groupCalendarFieldMetadataItemsByType = < + T extends Pick, +>( + fieldMetadataItems: T[], +) => + CALENDAR_FIELD_METADATA_TYPES.map((fieldMetadataType) => ({ + label: SETTINGS_NON_COMPOSITE_FIELD_TYPE_CONFIGS[fieldMetadataType].label, + fields: fieldMetadataItems.filter( + (fieldMetadataItem) => fieldMetadataItem.type === fieldMetadataType, + ), + })).filter((group) => group.fields.length > 0);