Add calendar view type in picker (#14477)
This commit is contained in:
+25
@@ -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 = () => {
|
||||
}}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
{hasCalendarViewEnabled && (
|
||||
<SelectableListItem
|
||||
itemId={ViewType.Calendar}
|
||||
onEnter={() => {
|
||||
setAndPersistViewType(ViewType.Calendar);
|
||||
}}
|
||||
>
|
||||
<MenuItemSelect
|
||||
LeftIcon={viewTypeIconMapping(ViewType.Calendar)}
|
||||
text={t`Calendar`}
|
||||
selected={currentView?.type === ViewType.Calendar}
|
||||
focused={selectedItemId === ViewType.Calendar}
|
||||
onClick={() => {
|
||||
setAndPersistViewType(ViewType.Calendar);
|
||||
}}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
)}
|
||||
<SelectableListItem
|
||||
itemId={ViewType.Kanban}
|
||||
onEnter={() => {
|
||||
|
||||
+29
-1
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
);
|
||||
};
|
||||
|
||||
@@ -37,5 +37,6 @@ export const convertUpdateViewInputToCore = (
|
||||
...(convertedKey && { key: convertedKey }),
|
||||
...(convertedOpenRecordIn && { openRecordIn: convertedOpenRecordIn }),
|
||||
...(convertedType && { type: convertedType }),
|
||||
...(view.calendarLayout && { calendarLayout: view.calendarLayout }),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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
|
||||
);
|
||||
};
|
||||
|
||||
+14
-1
@@ -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),
|
||||
}))}
|
||||
|
||||
+1
-1
@@ -64,7 +64,7 @@ export const ViewPickerCreateButton = () => {
|
||||
}
|
||||
|
||||
if (
|
||||
viewPickerType === ViewType.Table ||
|
||||
viewPickerType !== ViewType.Kanban ||
|
||||
viewPickerKanbanFieldMetadataId !== ''
|
||||
) {
|
||||
return (
|
||||
|
||||
+5
@@ -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),
|
||||
},
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user