Create edit dashboard action (#14564)
Closes https://github.com/twentyhq/core-team-issues/issues/1435 Introduced a new pattern to force to register an action or not. Discussed this with @charlesBochet
This commit is contained in:
+74
@@ -0,0 +1,74 @@
|
||||
import { EditDashboardSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/dashboard-actions/components/EditDashboardSingleRecordAction';
|
||||
import { DashboardSingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/dashboard-actions/types/DashboardSingleRecordActionKeys';
|
||||
import { SingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/types/SingleRecordActionsKey';
|
||||
import { inheritActionsFromDefaultConfig } from '@/action-menu/actions/record-actions/utils/inheritActionsFromDefaultConfig';
|
||||
import { ActionScope } from '@/action-menu/actions/types/ActionScope';
|
||||
import { ActionType } from '@/action-menu/actions/types/ActionType';
|
||||
import { ActionViewType } from '@/action-menu/actions/types/ActionViewType';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IconPencil } from 'twenty-ui/display';
|
||||
|
||||
export const DASHBOARD_ACTIONS_CONFIG = inheritActionsFromDefaultConfig({
|
||||
config: {
|
||||
[DashboardSingleRecordActionKeys.EDIT_DASHBOARD]: {
|
||||
key: DashboardSingleRecordActionKeys.EDIT_DASHBOARD,
|
||||
label: msg`Edit Dashboard`,
|
||||
shortLabel: msg`Edit`,
|
||||
isPinned: true,
|
||||
position: 1,
|
||||
Icon: IconPencil,
|
||||
type: ActionType.Standard,
|
||||
scope: ActionScope.RecordSelection,
|
||||
shouldBeRegistered: ({ selectedRecord }) =>
|
||||
isDefined(selectedRecord) &&
|
||||
!selectedRecord?.isRemote &&
|
||||
!isDefined(selectedRecord?.deletedAt) &&
|
||||
isDefined(selectedRecord?.pageLayoutId),
|
||||
availableOn: [ActionViewType.SHOW_PAGE],
|
||||
component: <EditDashboardSingleRecordAction />,
|
||||
},
|
||||
},
|
||||
actionKeys: [
|
||||
SingleRecordActionKeys.ADD_TO_FAVORITES,
|
||||
SingleRecordActionKeys.REMOVE_FROM_FAVORITES,
|
||||
SingleRecordActionKeys.DELETE,
|
||||
SingleRecordActionKeys.DESTROY,
|
||||
SingleRecordActionKeys.RESTORE,
|
||||
SingleRecordActionKeys.EXPORT_FROM_RECORD_SHOW,
|
||||
SingleRecordActionKeys.NAVIGATE_TO_PREVIOUS_RECORD,
|
||||
SingleRecordActionKeys.NAVIGATE_TO_NEXT_RECORD,
|
||||
],
|
||||
propertiesToOverwrite: {
|
||||
[SingleRecordActionKeys.ADD_TO_FAVORITES]: {
|
||||
position: 2,
|
||||
},
|
||||
[SingleRecordActionKeys.REMOVE_FROM_FAVORITES]: {
|
||||
position: 3,
|
||||
},
|
||||
[SingleRecordActionKeys.DELETE]: {
|
||||
position: 4,
|
||||
label: msg`Delete dashboard`,
|
||||
},
|
||||
[SingleRecordActionKeys.EXPORT_FROM_RECORD_SHOW]: {
|
||||
position: 5,
|
||||
label: msg`Export dashboard`,
|
||||
},
|
||||
[SingleRecordActionKeys.DESTROY]: {
|
||||
position: 6,
|
||||
label: msg`Permanently destroy dashboard`,
|
||||
},
|
||||
[SingleRecordActionKeys.RESTORE]: {
|
||||
position: 7,
|
||||
label: msg`Restore dashboard`,
|
||||
},
|
||||
[SingleRecordActionKeys.NAVIGATE_TO_PREVIOUS_RECORD]: {
|
||||
position: 8,
|
||||
label: msg`Navigate to previous dashboard`,
|
||||
},
|
||||
[SingleRecordActionKeys.NAVIGATE_TO_NEXT_RECORD]: {
|
||||
position: 9,
|
||||
label: msg`Navigate to next dashboard`,
|
||||
},
|
||||
},
|
||||
});
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import { ActionLink } from '@/action-menu/actions/components/ActionLink';
|
||||
import { MultipleRecordsActionKeys } from '@/action-menu/actions/record-actions/multiple-records/types/MultipleRecordsActionKeys';
|
||||
import { NoSelectionRecordActionKeys } from '@/action-menu/actions/record-actions/no-selection/types/NoSelectionRecordActionsKeys';
|
||||
import { NoSelectionWorkflowRecordActionKeys } from '@/action-menu/actions/record-actions/no-selection/workflow-actions/types/NoSelectionWorkflowRecordActionsKeys';
|
||||
import { NoSelectionWorkflowRecordActionKeys } from '@/action-menu/actions/record-actions/no-selection/workflow-actions/types/NoSelectionWorkflowRecordActionKeys';
|
||||
import { SingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/types/SingleRecordActionsKey';
|
||||
import { ActivateWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/components/ActivateWorkflowSingleRecordAction';
|
||||
import { DeactivateWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/components/DeactivateWorkflowSingleRecordAction';
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import { ActionLink } from '@/action-menu/actions/components/ActionLink';
|
||||
import { MultipleRecordsActionKeys } from '@/action-menu/actions/record-actions/multiple-records/types/MultipleRecordsActionKeys';
|
||||
import { NoSelectionRecordActionKeys } from '@/action-menu/actions/record-actions/no-selection/types/NoSelectionRecordActionsKeys';
|
||||
import { NoSelectionWorkflowRecordActionKeys } from '@/action-menu/actions/record-actions/no-selection/workflow-actions/types/NoSelectionWorkflowRecordActionsKeys';
|
||||
import { NoSelectionWorkflowRecordActionKeys } from '@/action-menu/actions/record-actions/no-selection/workflow-actions/types/NoSelectionWorkflowRecordActionKeys';
|
||||
import { SingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/types/SingleRecordActionsKey';
|
||||
import { SeeRunsWorkflowVersionSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-version-actions/components/SeeRunsWorkflowVersionSingleRecordAction';
|
||||
import { SeeVersionsWorkflowVersionSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-version-actions/components/SeeVersionsWorkflowVersionSingleRecordAction';
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
import { Action } from '@/action-menu/actions/components/Action';
|
||||
import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow';
|
||||
import { useSetIsDashboardInEditMode } from '@/dashboards/hooks/useSetDashboardInEditMode';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
export const EditDashboardSingleRecordAction = () => {
|
||||
const recordId = useSelectedRecordIdOrThrow();
|
||||
|
||||
const selectedRecord = useRecoilValue(recordStoreFamilyState(recordId));
|
||||
|
||||
const pageLayoutId = selectedRecord?.pageLayoutId;
|
||||
|
||||
const { setIsDashboardInEditMode } =
|
||||
useSetIsDashboardInEditMode(pageLayoutId);
|
||||
|
||||
const handleClick = () => {
|
||||
setIsDashboardInEditMode(true);
|
||||
};
|
||||
|
||||
return <Action onClick={handleClick} />;
|
||||
};
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
export enum DashboardSingleRecordActionKeys {
|
||||
EDIT_DASHBOARD = 'edit-dashboard-single-record',
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
|
||||
|
||||
export const forceRegisteredActionsByKeyComponentState = createComponentState<
|
||||
Record<string, boolean | undefined>
|
||||
>({
|
||||
key: 'forceRegisteredActionsByKeyComponentState',
|
||||
defaultValue: {},
|
||||
componentInstanceContext: ActionMenuComponentInstanceContext,
|
||||
});
|
||||
+1
@@ -25,4 +25,5 @@ export type ShouldBeRegisteredFunctionParams = {
|
||||
getTargetObjectWritePermission: (
|
||||
objectMetadataItemNameSingular: string,
|
||||
) => boolean;
|
||||
forceRegisteredActionsByKey: Record<string, boolean | undefined>;
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { DASHBOARD_ACTIONS_CONFIG } from '@/action-menu/actions/record-actions/constants/DashboardActionsConfig';
|
||||
import { DEFAULT_RECORD_ACTIONS_CONFIG } from '@/action-menu/actions/record-actions/constants/DefaultRecordActionsConfig';
|
||||
import { WORKFLOW_ACTIONS_CONFIG } from '@/action-menu/actions/record-actions/constants/WorkflowActionsConfig';
|
||||
import { WORKFLOW_RUNS_ACTIONS_CONFIG } from '@/action-menu/actions/record-actions/constants/WorkflowRunsActionsConfig';
|
||||
@@ -17,6 +18,9 @@ export const getActionConfig = ({
|
||||
}
|
||||
|
||||
switch (objectMetadataItem.nameSingular) {
|
||||
case CoreObjectNameSingular.Dashboard: {
|
||||
return DASHBOARD_ACTIONS_CONFIG;
|
||||
}
|
||||
case CoreObjectNameSingular.Workflow: {
|
||||
return WORKFLOW_ACTIONS_CONFIG;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,8 @@ import { useIcons } from 'twenty-ui/display';
|
||||
export const useRegisteredActions = (
|
||||
shouldBeRegisteredParams: ShouldBeRegisteredFunctionParams,
|
||||
) => {
|
||||
const { objectMetadataItem } = shouldBeRegisteredParams;
|
||||
const { objectMetadataItem, forceRegisteredActionsByKey } =
|
||||
shouldBeRegisteredParams;
|
||||
|
||||
const { getIcon } = useIcons();
|
||||
|
||||
@@ -67,6 +68,15 @@ export const useRegisteredActions = (
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
const forcedShouldBeRegistered = forceRegisteredActionsByKey[action.key];
|
||||
|
||||
if (isDefined(forcedShouldBeRegistered)) {
|
||||
return (
|
||||
forcedShouldBeRegistered &&
|
||||
action.shouldBeRegistered(shouldBeRegisteredParams)
|
||||
);
|
||||
}
|
||||
|
||||
return action.shouldBeRegistered(shouldBeRegisteredParams);
|
||||
})
|
||||
.sort((a, b) => a.position - b.position);
|
||||
|
||||
+6
@@ -1,3 +1,4 @@
|
||||
import { forceRegisteredActionsByKeyComponentState } from '@/action-menu/actions/states/forceRegisteredActionsMapComponentState';
|
||||
import { type ShouldBeRegisteredFunctionParams } from '@/action-menu/actions/types/ShouldBeRegisteredFunctionParams';
|
||||
import { getActionViewType } from '@/action-menu/actions/utils/getActionViewType';
|
||||
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
|
||||
@@ -100,6 +101,10 @@ export const useShouldActionBeRegisteredParams = ({
|
||||
[],
|
||||
);
|
||||
|
||||
const forceRegisteredActionsByKey = useRecoilComponentValue(
|
||||
forceRegisteredActionsByKeyComponentState,
|
||||
);
|
||||
|
||||
return {
|
||||
objectMetadataItem,
|
||||
isFavorite,
|
||||
@@ -113,5 +118,6 @@ export const useShouldActionBeRegisteredParams = ({
|
||||
viewType: viewType ?? undefined,
|
||||
getTargetObjectReadPermission: getObjectReadPermission,
|
||||
getTargetObjectWritePermission: getObjectWritePermission,
|
||||
forceRegisteredActionsByKey,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { type Dashboard } from '@/dashboards/components/types/Dashboard';
|
||||
import { useSetIsDashboardInEditMode } from '@/dashboards/hooks/useSetDashboardInEditMode';
|
||||
import { PageLayoutRenderer } from '@/page-layout/components/PageLayoutRenderer';
|
||||
import { type PageLayoutWithData } from '@/page-layout/types/pageLayoutTypes';
|
||||
import { isPageLayoutEmpty } from '@/page-layout/utils/isPageLayoutEmpty';
|
||||
|
||||
type DashboardContentRendererProps = {
|
||||
dashboard: Dashboard;
|
||||
};
|
||||
|
||||
export const DashboardContentRenderer = ({
|
||||
dashboard,
|
||||
}: DashboardContentRendererProps) => {
|
||||
const pageLayoutId = dashboard.pageLayoutId;
|
||||
|
||||
const { setIsDashboardInEditMode } =
|
||||
useSetIsDashboardInEditMode(pageLayoutId);
|
||||
|
||||
const onInitialized = (pageLayout: PageLayoutWithData) => {
|
||||
if (isPageLayoutEmpty(pageLayout)) {
|
||||
setIsDashboardInEditMode(true);
|
||||
} else {
|
||||
setIsDashboardInEditMode(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<PageLayoutRenderer
|
||||
pageLayoutId={pageLayoutId}
|
||||
onInitialized={onInitialized}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DashboardContentRenderer } from '@/dashboards/components/DashboardContentRenderer';
|
||||
import { type Dashboard } from '@/dashboards/components/types/Dashboard';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
|
||||
import { PageLayoutRenderer } from '@/page-layout/components/PageLayoutRenderer';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
type DashboardRendererProps = {
|
||||
@@ -18,5 +18,5 @@ export const DashboardRenderer = ({ recordId }: DashboardRendererProps) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <PageLayoutRenderer pageLayoutId={dashboard.pageLayoutId} />;
|
||||
return <DashboardContentRenderer dashboard={dashboard} />;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { DashboardSingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/dashboard-actions/types/DashboardSingleRecordActionKeys';
|
||||
import { forceRegisteredActionsByKeyComponentState } from '@/action-menu/actions/states/forceRegisteredActionsMapComponentState';
|
||||
import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext';
|
||||
import { isPageLayoutInEditModeComponentState } from '@/page-layout/states/isPageLayoutInEditModeComponentState';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
export const useSetIsDashboardInEditMode = (pageLayoutIdFromProps: string) => {
|
||||
const pageLayoutId = useAvailableComponentInstanceIdOrThrow(
|
||||
PageLayoutComponentInstanceContext,
|
||||
pageLayoutIdFromProps,
|
||||
);
|
||||
|
||||
const isPageLayoutInEditModeState = useRecoilComponentCallbackState(
|
||||
isPageLayoutInEditModeComponentState,
|
||||
pageLayoutId,
|
||||
);
|
||||
|
||||
const forceRegisteredActionsByKeyState = useRecoilComponentCallbackState(
|
||||
forceRegisteredActionsByKeyComponentState,
|
||||
);
|
||||
|
||||
const setIsDashboardInEditMode = useRecoilCallback(
|
||||
({ set }) =>
|
||||
(value: boolean) => {
|
||||
set(isPageLayoutInEditModeState, value);
|
||||
set(forceRegisteredActionsByKeyState, (prev) => ({
|
||||
...prev,
|
||||
[DashboardSingleRecordActionKeys.EDIT_DASHBOARD]: !value,
|
||||
}));
|
||||
},
|
||||
[forceRegisteredActionsByKeyState, isPageLayoutInEditModeState],
|
||||
);
|
||||
|
||||
return { setIsDashboardInEditMode };
|
||||
};
|
||||
+4
-1
@@ -14,10 +14,12 @@ import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
|
||||
type PageLayoutInitializationQueryEffectProps = {
|
||||
pageLayoutId: string;
|
||||
onInitialized: (pageLayout: PageLayoutWithData) => void;
|
||||
};
|
||||
|
||||
export const PageLayoutInitializationQueryEffect = ({
|
||||
pageLayoutId,
|
||||
onInitialized,
|
||||
}: PageLayoutInitializationQueryEffectProps) => {
|
||||
const [isInitialized, setIsInitialized] = useState(false);
|
||||
|
||||
@@ -88,9 +90,10 @@ export const PageLayoutInitializationQueryEffect = ({
|
||||
useEffect(() => {
|
||||
if (!isInitialized && isDefined(pageLayout)) {
|
||||
initializePageLayout(pageLayout);
|
||||
onInitialized?.(pageLayout);
|
||||
setIsInitialized(true);
|
||||
}
|
||||
}, [initializePageLayout, isInitialized, pageLayout]);
|
||||
}, [initializePageLayout, isInitialized, pageLayout, onInitialized]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import { PageLayoutInitializationQueryEffect } from '@/page-layout/components/Pa
|
||||
|
||||
import { PageLayoutRendererContent } from '@/page-layout/components/PageLayoutRendererContent';
|
||||
import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext';
|
||||
import { type PageLayoutWithData } from '@/page-layout/types/pageLayoutTypes';
|
||||
import { getTabListInstanceIdFromPageLayoutId } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutId';
|
||||
import { TabListComponentInstanceContext } from '@/ui/layout/tab-list/states/contexts/TabListComponentInstanceContext';
|
||||
import 'react-grid-layout/css/styles.css';
|
||||
@@ -9,10 +10,12 @@ import 'react-resizable/css/styles.css';
|
||||
|
||||
type PageLayoutRendererProps = {
|
||||
pageLayoutId: string;
|
||||
onInitialized: (pageLayout: PageLayoutWithData) => void;
|
||||
};
|
||||
|
||||
export const PageLayoutRenderer = ({
|
||||
pageLayoutId,
|
||||
onInitialized,
|
||||
}: PageLayoutRendererProps) => {
|
||||
return (
|
||||
<PageLayoutComponentInstanceContext.Provider
|
||||
@@ -25,7 +28,10 @@ export const PageLayoutRenderer = ({
|
||||
instanceId: getTabListInstanceIdFromPageLayoutId(pageLayoutId),
|
||||
}}
|
||||
>
|
||||
<PageLayoutInitializationQueryEffect pageLayoutId={pageLayoutId} />
|
||||
<PageLayoutInitializationQueryEffect
|
||||
pageLayoutId={pageLayoutId}
|
||||
onInitialized={onInitialized}
|
||||
/>
|
||||
<PageLayoutRendererContent />
|
||||
</TabListComponentInstanceContext.Provider>
|
||||
</PageLayoutComponentInstanceContext.Provider>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { type PageLayoutWithData } from '~/modules/page-layout/types/pageLayoutTypes';
|
||||
|
||||
export const isPageLayoutEmpty = (pageLayout: PageLayoutWithData): boolean => {
|
||||
return (
|
||||
pageLayout.tabs.length === 1 && pageLayout.tabs[0].widgets.length === 0
|
||||
);
|
||||
};
|
||||
@@ -26,7 +26,6 @@ export const WidgetRenderer = ({ widget }: WidgetRendererProps) => {
|
||||
|
||||
const isPageLayoutInEditMode = useRecoilComponentValue(
|
||||
isPageLayoutInEditModeComponentState,
|
||||
widget.id,
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user