Add calendar field metadata id to view table (#14478)

This commit is contained in:
Weiko
2025-09-15 09:17:17 +02:00
committed by GitHub
parent 3b0aba77ac
commit eada8e2567
19 changed files with 249 additions and 24 deletions
File diff suppressed because one or more lines are too long
+10 -5
View File
@@ -529,6 +529,7 @@ export type ConnectionParametersOutput = {
export type CoreView = {
__typename?: 'CoreView';
anyFieldFilterValue?: Maybe<Scalars['String']>;
calendarFieldMetadataId?: Maybe<Scalars['UUID']>;
calendarLayout?: Maybe<ViewCalendarLayout>;
createdAt: Scalars['DateTime'];
deletedAt?: Maybe<Scalars['DateTime']>;
@@ -773,6 +774,7 @@ export type CreateViewGroupInput = {
export type CreateViewInput = {
anyFieldFilterValue?: InputMaybe<Scalars['String']>;
calendarFieldMetadataId?: InputMaybe<Scalars['UUID']>;
calendarLayout?: InputMaybe<ViewCalendarLayout>;
icon: Scalars['String'];
id?: InputMaybe<Scalars['UUID']>;
@@ -3495,6 +3497,7 @@ export type UpdateViewGroupInput = {
export type UpdateViewInput = {
anyFieldFilterValue?: InputMaybe<Scalars['String']>;
calendarFieldMetadataId?: InputMaybe<Scalars['UUID']>;
calendarLayout?: InputMaybe<ViewCalendarLayout>;
icon?: InputMaybe<Scalars['String']>;
id?: InputMaybe<Scalars['UUID']>;
@@ -3951,7 +3954,7 @@ export type ViewFilterFragmentFragment = { __typename?: 'CoreViewFilter', id: an
export type ViewFilterGroupFragmentFragment = { __typename?: 'CoreViewFilterGroup', id: any, parentViewFilterGroupId?: any | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: any };
export type ViewFragmentFragment = { __typename?: 'CoreView', id: any, name: string, objectMetadataId: any, type: ViewType, key?: ViewKey | null, icon: string, position: number, isCompact: boolean, openRecordIn: ViewOpenRecordIn, kanbanAggregateOperation?: AggregateOperations | null, kanbanAggregateOperationFieldMetadataId?: any | null, anyFieldFilterValue?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: any, fieldMetadataId: any, viewId: any, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFilters: Array<{ __typename?: 'CoreViewFilter', id: any, fieldMetadataId: any, operand: ViewFilterOperand, value: any, viewFilterGroupId?: any | null, positionInViewFilterGroup?: number | null, subFieldName?: string | null, viewId: any }>, viewFilterGroups: Array<{ __typename?: 'CoreViewFilterGroup', id: any, parentViewFilterGroupId?: any | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: any }>, viewSorts: Array<{ __typename?: 'CoreViewSort', id: any, fieldMetadataId: any, direction: ViewSortDirection, viewId: any }>, viewGroups: Array<{ __typename?: 'CoreViewGroup', id: any, fieldMetadataId: any, isVisible: boolean, fieldValue: string, position: number, viewId: any }> };
export type ViewFragmentFragment = { __typename?: 'CoreView', id: any, name: string, objectMetadataId: any, type: ViewType, key?: ViewKey | null, icon: string, position: number, isCompact: boolean, openRecordIn: ViewOpenRecordIn, kanbanAggregateOperation?: AggregateOperations | null, kanbanAggregateOperationFieldMetadataId?: any | null, anyFieldFilterValue?: string | null, calendarFieldMetadataId?: any | null, calendarLayout?: ViewCalendarLayout | null, viewFields: Array<{ __typename?: 'CoreViewField', id: any, fieldMetadataId: any, viewId: any, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFilters: Array<{ __typename?: 'CoreViewFilter', id: any, fieldMetadataId: any, operand: ViewFilterOperand, value: any, viewFilterGroupId?: any | null, positionInViewFilterGroup?: number | null, subFieldName?: string | null, viewId: any }>, viewFilterGroups: Array<{ __typename?: 'CoreViewFilterGroup', id: any, parentViewFilterGroupId?: any | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: any }>, viewSorts: Array<{ __typename?: 'CoreViewSort', id: any, fieldMetadataId: any, direction: ViewSortDirection, viewId: any }>, viewGroups: Array<{ __typename?: 'CoreViewGroup', id: any, fieldMetadataId: any, isVisible: boolean, fieldValue: string, position: number, viewId: any }> };
export type ViewGroupFragmentFragment = { __typename?: 'CoreViewGroup', id: any, fieldMetadataId: any, isVisible: boolean, fieldValue: string, position: number, viewId: any };
@@ -3962,7 +3965,7 @@ export type CreateCoreViewMutationVariables = Exact<{
}>;
export type CreateCoreViewMutation = { __typename?: 'Mutation', createCoreView: { __typename?: 'CoreView', id: any, name: string, objectMetadataId: any, type: ViewType, key?: ViewKey | null, icon: string, position: number, isCompact: boolean, openRecordIn: ViewOpenRecordIn, kanbanAggregateOperation?: AggregateOperations | null, kanbanAggregateOperationFieldMetadataId?: any | null, anyFieldFilterValue?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: any, fieldMetadataId: any, viewId: any, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFilters: Array<{ __typename?: 'CoreViewFilter', id: any, fieldMetadataId: any, operand: ViewFilterOperand, value: any, viewFilterGroupId?: any | null, positionInViewFilterGroup?: number | null, subFieldName?: string | null, viewId: any }>, viewFilterGroups: Array<{ __typename?: 'CoreViewFilterGroup', id: any, parentViewFilterGroupId?: any | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: any }>, viewSorts: Array<{ __typename?: 'CoreViewSort', id: any, fieldMetadataId: any, direction: ViewSortDirection, viewId: any }>, viewGroups: Array<{ __typename?: 'CoreViewGroup', id: any, fieldMetadataId: any, isVisible: boolean, fieldValue: string, position: number, viewId: any }> } };
export type CreateCoreViewMutation = { __typename?: 'Mutation', createCoreView: { __typename?: 'CoreView', id: any, name: string, objectMetadataId: any, type: ViewType, key?: ViewKey | null, icon: string, position: number, isCompact: boolean, openRecordIn: ViewOpenRecordIn, kanbanAggregateOperation?: AggregateOperations | null, kanbanAggregateOperationFieldMetadataId?: any | null, anyFieldFilterValue?: string | null, calendarFieldMetadataId?: any | null, calendarLayout?: ViewCalendarLayout | null, viewFields: Array<{ __typename?: 'CoreViewField', id: any, fieldMetadataId: any, viewId: any, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFilters: Array<{ __typename?: 'CoreViewFilter', id: any, fieldMetadataId: any, operand: ViewFilterOperand, value: any, viewFilterGroupId?: any | null, positionInViewFilterGroup?: number | null, subFieldName?: string | null, viewId: any }>, viewFilterGroups: Array<{ __typename?: 'CoreViewFilterGroup', id: any, parentViewFilterGroupId?: any | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: any }>, viewSorts: Array<{ __typename?: 'CoreViewSort', id: any, fieldMetadataId: any, direction: ViewSortDirection, viewId: any }>, viewGroups: Array<{ __typename?: 'CoreViewGroup', id: any, fieldMetadataId: any, isVisible: boolean, fieldValue: string, position: number, viewId: any }> } };
export type CreateCoreViewFieldMutationVariables = Exact<{
input: CreateViewFieldInput;
@@ -4089,7 +4092,7 @@ export type UpdateCoreViewMutationVariables = Exact<{
}>;
export type UpdateCoreViewMutation = { __typename?: 'Mutation', updateCoreView: { __typename?: 'CoreView', id: any, name: string, objectMetadataId: any, type: ViewType, key?: ViewKey | null, icon: string, position: number, isCompact: boolean, openRecordIn: ViewOpenRecordIn, kanbanAggregateOperation?: AggregateOperations | null, kanbanAggregateOperationFieldMetadataId?: any | null, anyFieldFilterValue?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: any, fieldMetadataId: any, viewId: any, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFilters: Array<{ __typename?: 'CoreViewFilter', id: any, fieldMetadataId: any, operand: ViewFilterOperand, value: any, viewFilterGroupId?: any | null, positionInViewFilterGroup?: number | null, subFieldName?: string | null, viewId: any }>, viewFilterGroups: Array<{ __typename?: 'CoreViewFilterGroup', id: any, parentViewFilterGroupId?: any | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: any }>, viewSorts: Array<{ __typename?: 'CoreViewSort', id: any, fieldMetadataId: any, direction: ViewSortDirection, viewId: any }>, viewGroups: Array<{ __typename?: 'CoreViewGroup', id: any, fieldMetadataId: any, isVisible: boolean, fieldValue: string, position: number, viewId: any }> } };
export type UpdateCoreViewMutation = { __typename?: 'Mutation', updateCoreView: { __typename?: 'CoreView', id: any, name: string, objectMetadataId: any, type: ViewType, key?: ViewKey | null, icon: string, position: number, isCompact: boolean, openRecordIn: ViewOpenRecordIn, kanbanAggregateOperation?: AggregateOperations | null, kanbanAggregateOperationFieldMetadataId?: any | null, anyFieldFilterValue?: string | null, calendarFieldMetadataId?: any | null, calendarLayout?: ViewCalendarLayout | null, viewFields: Array<{ __typename?: 'CoreViewField', id: any, fieldMetadataId: any, viewId: any, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFilters: Array<{ __typename?: 'CoreViewFilter', id: any, fieldMetadataId: any, operand: ViewFilterOperand, value: any, viewFilterGroupId?: any | null, positionInViewFilterGroup?: number | null, subFieldName?: string | null, viewId: any }>, viewFilterGroups: Array<{ __typename?: 'CoreViewFilterGroup', id: any, parentViewFilterGroupId?: any | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: any }>, viewSorts: Array<{ __typename?: 'CoreViewSort', id: any, fieldMetadataId: any, direction: ViewSortDirection, viewId: any }>, viewGroups: Array<{ __typename?: 'CoreViewGroup', id: any, fieldMetadataId: any, isVisible: boolean, fieldValue: string, position: number, viewId: any }> } };
export type UpdateCoreViewFieldMutationVariables = Exact<{
input: UpdateViewFieldInput;
@@ -4170,14 +4173,14 @@ export type FindManyCoreViewsQueryVariables = Exact<{
}>;
export type FindManyCoreViewsQuery = { __typename?: 'Query', getCoreViews: Array<{ __typename?: 'CoreView', id: any, name: string, objectMetadataId: any, type: ViewType, key?: ViewKey | null, icon: string, position: number, isCompact: boolean, openRecordIn: ViewOpenRecordIn, kanbanAggregateOperation?: AggregateOperations | null, kanbanAggregateOperationFieldMetadataId?: any | null, anyFieldFilterValue?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: any, fieldMetadataId: any, viewId: any, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFilters: Array<{ __typename?: 'CoreViewFilter', id: any, fieldMetadataId: any, operand: ViewFilterOperand, value: any, viewFilterGroupId?: any | null, positionInViewFilterGroup?: number | null, subFieldName?: string | null, viewId: any }>, viewFilterGroups: Array<{ __typename?: 'CoreViewFilterGroup', id: any, parentViewFilterGroupId?: any | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: any }>, viewSorts: Array<{ __typename?: 'CoreViewSort', id: any, fieldMetadataId: any, direction: ViewSortDirection, viewId: any }>, viewGroups: Array<{ __typename?: 'CoreViewGroup', id: any, fieldMetadataId: any, isVisible: boolean, fieldValue: string, position: number, viewId: any }> }> };
export type FindManyCoreViewsQuery = { __typename?: 'Query', getCoreViews: Array<{ __typename?: 'CoreView', id: any, name: string, objectMetadataId: any, type: ViewType, key?: ViewKey | null, icon: string, position: number, isCompact: boolean, openRecordIn: ViewOpenRecordIn, kanbanAggregateOperation?: AggregateOperations | null, kanbanAggregateOperationFieldMetadataId?: any | null, anyFieldFilterValue?: string | null, calendarFieldMetadataId?: any | null, calendarLayout?: ViewCalendarLayout | null, viewFields: Array<{ __typename?: 'CoreViewField', id: any, fieldMetadataId: any, viewId: any, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFilters: Array<{ __typename?: 'CoreViewFilter', id: any, fieldMetadataId: any, operand: ViewFilterOperand, value: any, viewFilterGroupId?: any | null, positionInViewFilterGroup?: number | null, subFieldName?: string | null, viewId: any }>, viewFilterGroups: Array<{ __typename?: 'CoreViewFilterGroup', id: any, parentViewFilterGroupId?: any | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: any }>, viewSorts: Array<{ __typename?: 'CoreViewSort', id: any, fieldMetadataId: any, direction: ViewSortDirection, viewId: any }>, viewGroups: Array<{ __typename?: 'CoreViewGroup', id: any, fieldMetadataId: any, isVisible: boolean, fieldValue: string, position: number, viewId: any }> }> };
export type FindOneCoreViewQueryVariables = Exact<{
id: Scalars['String'];
}>;
export type FindOneCoreViewQuery = { __typename?: 'Query', getCoreView?: { __typename?: 'CoreView', id: any, name: string, objectMetadataId: any, type: ViewType, key?: ViewKey | null, icon: string, position: number, isCompact: boolean, openRecordIn: ViewOpenRecordIn, kanbanAggregateOperation?: AggregateOperations | null, kanbanAggregateOperationFieldMetadataId?: any | null, anyFieldFilterValue?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: any, fieldMetadataId: any, viewId: any, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFilters: Array<{ __typename?: 'CoreViewFilter', id: any, fieldMetadataId: any, operand: ViewFilterOperand, value: any, viewFilterGroupId?: any | null, positionInViewFilterGroup?: number | null, subFieldName?: string | null, viewId: any }>, viewFilterGroups: Array<{ __typename?: 'CoreViewFilterGroup', id: any, parentViewFilterGroupId?: any | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: any }>, viewSorts: Array<{ __typename?: 'CoreViewSort', id: any, fieldMetadataId: any, direction: ViewSortDirection, viewId: any }>, viewGroups: Array<{ __typename?: 'CoreViewGroup', id: any, fieldMetadataId: any, isVisible: boolean, fieldValue: string, position: number, viewId: any }> } | null };
export type FindOneCoreViewQuery = { __typename?: 'Query', getCoreView?: { __typename?: 'CoreView', id: any, name: string, objectMetadataId: any, type: ViewType, key?: ViewKey | null, icon: string, position: number, isCompact: boolean, openRecordIn: ViewOpenRecordIn, kanbanAggregateOperation?: AggregateOperations | null, kanbanAggregateOperationFieldMetadataId?: any | null, anyFieldFilterValue?: string | null, calendarFieldMetadataId?: any | null, calendarLayout?: ViewCalendarLayout | null, viewFields: Array<{ __typename?: 'CoreViewField', id: any, fieldMetadataId: any, viewId: any, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFilters: Array<{ __typename?: 'CoreViewFilter', id: any, fieldMetadataId: any, operand: ViewFilterOperand, value: any, viewFilterGroupId?: any | null, positionInViewFilterGroup?: number | null, subFieldName?: string | null, viewId: any }>, viewFilterGroups: Array<{ __typename?: 'CoreViewFilterGroup', id: any, parentViewFilterGroupId?: any | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: any }>, viewSorts: Array<{ __typename?: 'CoreViewSort', id: any, fieldMetadataId: any, direction: ViewSortDirection, viewId: any }>, viewGroups: Array<{ __typename?: 'CoreViewGroup', id: any, fieldMetadataId: any, isVisible: boolean, fieldValue: string, position: number, viewId: any }> } | null };
export type FindOneCoreViewFieldQueryVariables = Exact<{
id: Scalars['String'];
@@ -4281,6 +4284,8 @@ export const ViewFragmentFragmentDoc = gql`
kanbanAggregateOperation
kanbanAggregateOperationFieldMetadataId
anyFieldFilterValue
calendarFieldMetadataId
calendarLayout
viewFields {
...ViewFieldFragment
}
@@ -17,6 +17,7 @@ import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/ho
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
import { ViewType, viewTypeIconMapping } from '@/views/types/ViewType';
import { useGetAvailableFieldsForCalendar } from '@/views/view-picker/hooks/useGetAvailableFieldsForCalendar';
import { useGetAvailableFieldsForKanban } from '@/views/view-picker/hooks/useGetAvailableFieldsForKanban';
import { useFeatureFlagsMap } from '@/workspace/hooks/useFeatureFlagsMap';
import { useLingui } from '@lingui/react/macro';
@@ -62,7 +63,8 @@ export const ObjectOptionsDropdownLayoutContent = () => {
const { setAndPersistViewType } = useSetViewTypeFromLayoutOptionsMenu();
const { availableFieldsForKanban, navigateToSelectSettings } =
useGetAvailableFieldsForKanban();
const { availableFieldsForCalendar, navigateToDateFieldSettings } =
useGetAvailableFieldsForCalendar();
const { closeDropdown } = useCloseDropdown();
const handleSelectKanbanViewType = async () => {
@@ -78,6 +80,19 @@ export const ObjectOptionsDropdownLayoutContent = () => {
}
};
const handleSelectCalendarViewType = async () => {
if (isDefaultView) {
return;
}
if (availableFieldsForCalendar.length === 0) {
navigateToDateFieldSettings();
closeDropdown(dropdownId);
}
if (currentView?.type !== ViewType.Calendar) {
await setAndPersistViewType(ViewType.Calendar);
}
};
const isDefaultView = currentView?.key === 'INDEX';
const nbsp = '\u00A0';
@@ -148,9 +163,7 @@ export const ObjectOptionsDropdownLayoutContent = () => {
text={t`Calendar`}
selected={currentView?.type === ViewType.Calendar}
focused={selectedItemId === ViewType.Calendar}
onClick={() => {
setAndPersistViewType(ViewType.Calendar);
}}
onClick={handleSelectCalendarViewType}
/>
</SelectableListItem>
)}
@@ -12,6 +12,7 @@ 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';
@@ -29,6 +30,8 @@ export const useSetViewTypeFromLayoutOptionsMenu = () => {
const { createViewGroupRecords } = usePersistViewGroupRecords();
const { availableFieldsForCalendar } = useGetAvailableFieldsForCalendar();
const createViewGroupAssociatedWithKanbanField = useCallback(
async (randomFieldForKanban: string, currentViewId: string) => {
const viewGroupsToCreate =
@@ -149,6 +152,12 @@ export const useSetViewTypeFromLayoutOptionsMenu = () => {
return await updateCurrentView(updateCurrentViewParams);
}
case ViewType.Calendar: {
if (availableFieldsForCalendar.length === 0) {
throw new Error('No date fields for calendar');
}
const calendarFieldMetadataId = availableFieldsForCalendar[0].id;
setRecordIndexViewType(viewType);
set(coreViewsState, [
...existingCoreViews.filter(
@@ -158,6 +167,7 @@ export const useSetViewTypeFromLayoutOptionsMenu = () => {
...currentCoreView,
type: convertViewTypeToCore(viewType),
calendarLayout: ViewCalendarLayout.MONTH,
calendarFieldMetadataId,
},
]);
@@ -166,6 +176,8 @@ export const useSetViewTypeFromLayoutOptionsMenu = () => {
viewTypeIconMapping(viewType).displayName;
}
updateCurrentViewParams.calendarLayout = ViewCalendarLayout.MONTH;
updateCurrentViewParams.calendarFieldMetadataId =
calendarFieldMetadataId;
return await updateCurrentView(updateCurrentViewParams);
}
default: {
@@ -180,6 +192,7 @@ export const useSetViewTypeFromLayoutOptionsMenu = () => {
setRecordIndexViewType,
createViewGroupAssociatedWithKanbanField,
loadRecordIndexStates,
availableFieldsForCalendar,
],
);
@@ -25,6 +25,8 @@ export const VIEW_FRAGMENT = gql`
kanbanAggregateOperation
kanbanAggregateOperationFieldMetadataId
anyFieldFilterValue
calendarFieldMetadataId
calendarLayout
viewFields {
...ViewFieldFragment
}
@@ -80,11 +80,17 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
name,
icon,
kanbanFieldMetadataId,
calendarFieldMetadataId,
type,
}: Partial<
Pick<
GraphQLView,
'id' | 'name' | 'icon' | 'kanbanFieldMetadataId' | 'type'
| 'id'
| 'name'
| 'icon'
| 'kanbanFieldMetadataId'
| 'calendarFieldMetadataId'
| 'type'
>
>,
shouldCopyFiltersAndSortsAndAggregate?: boolean,
@@ -138,6 +144,10 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
viewType === ViewType.Calendar
? ViewCalendarLayout.MONTH
: undefined,
calendarFieldMetadataId:
viewType === ViewType.Calendar
? calendarFieldMetadataId
: undefined,
},
},
});
@@ -32,4 +32,5 @@ export type GraphQLView = {
icon: string;
anyFieldFilterValue?: string | null;
calendarLayout?: ViewCalendarLayout | null;
calendarFieldMetadataId?: string | null;
};
@@ -37,6 +37,11 @@ export const convertUpdateViewInputToCore = (
...(convertedKey && { key: convertedKey }),
...(convertedOpenRecordIn && { openRecordIn: convertedOpenRecordIn }),
...(convertedType && { type: convertedType }),
...(view.calendarLayout && { calendarLayout: view.calendarLayout }),
...(isDefined(view.calendarLayout) && {
calendarLayout: view.calendarLayout,
}),
...(isDefined(view.calendarFieldMetadataId) && {
calendarFieldMetadataId: view.calendarFieldMetadataId,
}),
};
};
@@ -20,13 +20,16 @@ import { ViewPickerCreateButton } from '@/views/view-picker/components/ViewPicke
import { ViewPickerIconAndNameContainer } from '@/views/view-picker/components/ViewPickerIconAndNameContainer';
import { ViewPickerSaveButtonContainer } from '@/views/view-picker/components/ViewPickerSaveButtonContainer';
import { ViewPickerSelectContainer } from '@/views/view-picker/components/ViewPickerSelectContainer';
import { VIEW_PICKER_CALENDAR_FIELD_DROPDOWN_ID } from '@/views/view-picker/constants/ViewPickerCalendarFieldDropdownId';
import { VIEW_PICKER_DROPDOWN_ID } from '@/views/view-picker/constants/ViewPickerDropdownId';
import { VIEW_PICKER_KANBAN_FIELD_DROPDOWN_ID } from '@/views/view-picker/constants/ViewPickerKanbanFieldDropdownId';
import { VIEW_PICKER_TYPE_SELECT_OPTIONS } from '@/views/view-picker/constants/ViewPickerTypeSelectOptions';
import { VIEW_PICKER_VIEW_TYPE_DROPDOWN_ID } from '@/views/view-picker/constants/ViewPickerViewTypeDropdownId';
import { useCreateViewFromCurrentState } from '@/views/view-picker/hooks/useCreateViewFromCurrentState';
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 { viewPickerInputNameComponentState } from '@/views/view-picker/states/viewPickerInputNameComponentState';
import { viewPickerIsDirtyComponentState } from '@/views/view-picker/states/viewPickerIsDirtyComponentState';
import { viewPickerIsPersistingComponentState } from '@/views/view-picker/states/viewPickerIsPersistingComponentState';
@@ -39,7 +42,7 @@ import { useMemo, useState } from 'react';
import { IconX } from 'twenty-ui/display';
import { FeatureFlagKey } from '~/generated/graphql';
const StyledNoKanbanFieldAvailableContainer = styled.div`
const StyledFieldAvailableContainer = styled.div`
color: ${({ theme }) => theme.font.color.light};
display: flex;
margin: ${({ theme }) => theme.spacing(1, 2)};
@@ -76,6 +79,11 @@ export const ViewPickerContentCreateMode = () => {
const [viewPickerKanbanFieldMetadataId, setViewPickerKanbanFieldMetadataId] =
useRecoilComponentState(viewPickerKanbanFieldMetadataIdComponentState);
const [
viewPickerCalendarFieldMetadataId,
setViewPickerCalendarFieldMetadataId,
] = useRecoilComponentState(viewPickerCalendarFieldMetadataIdComponentState);
const [viewPickerType, setViewPickerType] = useRecoilComponentState(
viewPickerTypeComponentState,
);
@@ -84,6 +92,7 @@ export const ViewPickerContentCreateMode = () => {
const { availableFieldsForKanban } = useGetAvailableFieldsForKanban();
const { availableFieldsForCalendar } = useGetAvailableFieldsForCalendar();
const featureFlags = useFeatureFlagsMap();
const hasCalendarViewEnabled =
@@ -117,6 +126,7 @@ export const ViewPickerContentCreateMode = () => {
createViewFromCurrentState,
viewPickerType,
availableFieldsForKanban,
availableFieldsForCalendar,
],
});
@@ -207,10 +217,40 @@ export const ViewPickerContentCreateMode = () => {
/>
</ViewPickerSelectContainer>
{availableFieldsForKanban.length === 0 && (
<StyledNoKanbanFieldAvailableContainer>
<StyledFieldAvailableContainer>
Set up a Select field on {objectMetadataItem.labelPlural} to
create a Kanban
</StyledNoKanbanFieldAvailableContainer>
</StyledFieldAvailableContainer>
)}
</>
)}
{viewPickerType === ViewType.Calendar && (
<>
<ViewPickerSelectContainer>
<Select
label={t`Date field`}
fullWidth
value={viewPickerCalendarFieldMetadataId}
onChange={(value) => {
setViewPickerIsDirty(true);
setViewPickerCalendarFieldMetadataId(value);
}}
options={
availableFieldsForCalendar.length > 0
? availableFieldsForCalendar.map((field) => ({
value: field.id,
label: field.label,
}))
: [{ value: '', label: t`No Date field` }]
}
dropdownId={VIEW_PICKER_CALENDAR_FIELD_DROPDOWN_ID}
/>
</ViewPickerSelectContainer>
{availableFieldsForCalendar.length === 0 && (
<StyledFieldAvailableContainer>
Set up a Date field on {objectMetadataItem.labelPlural} to
create a Calendar
</StyledFieldAvailableContainer>
)}
</>
)}
@@ -0,0 +1,2 @@
export const VIEW_PICKER_CALENDAR_FIELD_DROPDOWN_ID =
'view-picker-calendar-field';
@@ -3,6 +3,7 @@ import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
import { useChangeView } from '@/views/hooks/useChangeView';
import { useCreateViewFromCurrentView } from '@/views/hooks/useCreateViewFromCurrentView';
import { useCloseAndResetViewPicker } from '@/views/view-picker/hooks/useCloseAndResetViewPicker';
import { viewPickerCalendarFieldMetadataIdComponentState } from '@/views/view-picker/states/viewPickerCalendarFieldMetadataIdComponentState';
import { viewPickerInputNameComponentState } from '@/views/view-picker/states/viewPickerInputNameComponentState';
import { viewPickerIsDirtyComponentState } from '@/views/view-picker/states/viewPickerIsDirtyComponentState';
import { viewPickerIsPersistingComponentState } from '@/views/view-picker/states/viewPickerIsPersistingComponentState';
@@ -33,6 +34,11 @@ export const useCreateViewFromCurrentState = () => {
viewPickerKanbanFieldMetadataIdComponentState,
);
const viewPickerCalendarFieldMetadataIdCallbackState =
useRecoilComponentCallbackState(
viewPickerCalendarFieldMetadataIdComponentState,
);
const viewPickerIsPersistingCallbackState = useRecoilComponentCallbackState(
viewPickerIsPersistingComponentState,
);
@@ -64,7 +70,10 @@ export const useCreateViewFromCurrentState = () => {
snapshot,
viewPickerKanbanFieldMetadataIdCallbackState,
);
const calendarFieldMetadataId = getSnapshotValue(
snapshot,
viewPickerCalendarFieldMetadataIdCallbackState,
);
const viewPickerMode = getSnapshotValue(
snapshot,
viewPickerModeCallbackState,
@@ -82,6 +91,7 @@ export const useCreateViewFromCurrentState = () => {
icon: iconKey,
type,
kanbanFieldMetadataId,
calendarFieldMetadataId,
},
shouldCopyFiltersAndSortsAndAggregate,
);
@@ -99,6 +109,7 @@ export const useCreateViewFromCurrentState = () => {
viewPickerIsDirtyCallbackState,
viewPickerIsPersistingCallbackState,
viewPickerKanbanFieldMetadataIdCallbackState,
viewPickerCalendarFieldMetadataIdCallbackState,
viewPickerSelectedIconCallbackState,
viewPickerTypeCallbackState,
viewPickerModeCallbackState,
@@ -0,0 +1,65 @@
import { useCallback } from 'react';
import { useLocation } from 'react-router-dom';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { viewObjectMetadataIdComponentState } from '@/views/states/viewObjectMetadataIdComponentState';
import { SettingsPath } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
export const useGetAvailableFieldsForCalendar = () => {
const viewObjectMetadataId = useRecoilComponentValue(
viewObjectMetadataIdComponentState,
);
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
const setNavigationMemorizedUrl = useSetRecoilState(
navigationMemorizedUrlState,
);
const location = useLocation();
const objectMetadataItem = objectMetadataItems.find(
(objectMetadata) => objectMetadata.id === viewObjectMetadataId,
);
const availableFieldsForCalendar =
objectMetadataItem?.readableFields.filter(
(field) =>
field.type === FieldMetadataType.DATE ||
field.type === FieldMetadataType.DATE_TIME,
) ?? [];
const navigate = useNavigateSettings();
const navigateToDateFieldSettings = useCallback(() => {
setNavigationMemorizedUrl(location.pathname + location.search);
if (isDefined(objectMetadataItem?.namePlural)) {
navigate(
SettingsPath.ObjectNewFieldConfigure,
{
objectNamePlural: objectMetadataItem.namePlural,
},
{
fieldType: FieldMetadataType.DATE,
},
);
} else {
navigate(SettingsPath.Objects);
}
}, [
setNavigationMemorizedUrl,
location.pathname,
location.search,
objectMetadataItem,
navigate,
]);
return {
availableFieldsForCalendar,
navigateToDateFieldSettings,
};
};
@@ -0,0 +1,9 @@
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
export const viewPickerCalendarFieldMetadataIdComponentState =
createComponentState<string>({
key: 'viewPickerCalendarFieldMetadataIdComponentState',
defaultValue: '',
componentInstanceContext: ViewComponentInstanceContext,
});
@@ -0,0 +1,31 @@
import { type MigrationInterface, type QueryRunner } from 'typeorm';
export class AddCalendarFieldMetadataIdToViewTable1757864696439
implements MigrationInterface
{
name = 'AddCalendarFieldMetadataIdToViewTable1757864696439';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "core"."view" DROP CONSTRAINT "CHK_VIEW_CALENDAR_LAYOUT_NOT_NULL_WHEN_TYPE_CALENDAR"`,
);
await queryRunner.query(
`ALTER TABLE "core"."view" ADD "calendarFieldMetadataId" uuid`,
);
await queryRunner.query(
`ALTER TABLE "core"."view" ADD CONSTRAINT "CHK_VIEW_CALENDAR_INTEGRITY" CHECK (("type" != 'CALENDAR' OR ("calendarLayout" IS NOT NULL AND "calendarFieldMetadataId" IS NOT NULL)))`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "core"."view" DROP CONSTRAINT "CHK_VIEW_CALENDAR_INTEGRITY"`,
);
await queryRunner.query(
`ALTER TABLE "core"."view" DROP COLUMN "calendarFieldMetadataId"`,
);
await queryRunner.query(
`ALTER TABLE "core"."view" ADD CONSTRAINT "CHK_VIEW_CALENDAR_LAYOUT_NOT_NULL_WHEN_TYPE_CALENDAR" CHECK (((type <> 'CALENDAR'::core.view_type_enum) OR ("calendarLayout" IS NOT NULL)))`,
);
}
}
@@ -50,4 +50,7 @@ export class CreateViewInput {
@Field(() => ViewCalendarLayout, { nullable: true })
calendarLayout?: ViewCalendarLayout;
@Field(() => UUIDScalarType, { nullable: true })
calendarFieldMetadataId?: string;
}
@@ -44,4 +44,7 @@ export class UpdateViewInput {
@Field(() => ViewCalendarLayout, { nullable: true })
calendarLayout?: ViewCalendarLayout;
@Field(() => UUIDScalarType, { nullable: true })
calendarFieldMetadataId?: string;
}
@@ -60,6 +60,9 @@ export class ViewDTO {
@Field(() => UUIDScalarType, { nullable: true })
kanbanAggregateOperationFieldMetadataId?: string | null;
@Field(() => UUIDScalarType, { nullable: true })
calendarFieldMetadataId?: string | null;
@Field(() => UUIDScalarType, { nullable: false })
workspaceId: string;
@@ -34,8 +34,8 @@ import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadat
'objectMetadataId',
])
@Check(
'CHK_VIEW_CALENDAR_LAYOUT_NOT_NULL_WHEN_TYPE_CALENDAR',
`("type" != 'CALENDAR' OR "calendarLayout" IS NOT NULL)`,
'CHK_VIEW_CALENDAR_INTEGRITY',
`("type" != 'CALENDAR' OR ("calendarLayout" IS NOT NULL AND "calendarFieldMetadataId" IS NOT NULL))`,
)
export class ViewEntity extends SyncableEntity implements Required<ViewEntity> {
@PrimaryGeneratedColumn('uuid')
@@ -108,6 +108,9 @@ export class ViewEntity extends SyncableEntity implements Required<ViewEntity> {
})
calendarLayout: ViewCalendarLayout | null;
@Column({ nullable: true, type: 'uuid' })
calendarFieldMetadataId: string | null;
@Column({ nullable: false, type: 'uuid' })
workspaceId: string;
@@ -33,5 +33,6 @@ export const fromPartialFlatViewToFlatViewWithDefault = (
universalIdentifier: partialFlatView.universalIdentifier ?? viewId,
viewFieldIds: partialFlatView.viewFieldIds ?? [],
calendarLayout: partialFlatView.calendarLayout ?? null,
calendarFieldMetadataId: partialFlatView.calendarFieldMetadataId ?? null,
};
};