Compare commits

...

4 Commits

Author SHA1 Message Date
Devessier 74e3dba98f Merge remote-tracking branch 'origin/main' into rpl-front-components 2026-04-02 14:25:41 +02:00
Devessier d225d132f9 Merge remote-tracking branch 'origin/main' into rpl-front-components 2026-03-25 14:38:41 +01:00
Devessier 020000b3c5 fixup 2026-03-25 14:37:44 +01:00
Devessier 0a94f93f0c feat: scaffolding 2026-03-25 14:37:41 +01:00
5 changed files with 176 additions and 8 deletions
@@ -6,6 +6,7 @@ export const FIND_MANY_FRONT_COMPONENTS = gql`
id
name
applicationId
isHeadless
}
}
`;
@@ -0,0 +1,70 @@
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
import { addWidgetToTab } from '@/page-layout/utils/addWidgetToTab';
import { createDefaultFrontComponentWidgetForVerticalList } from '@/page-layout/utils/createDefaultFrontComponentWidgetForVerticalList';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { useStore } from 'jotai';
import { useCallback } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { v4 as uuidv4 } from 'uuid';
export const useCreateRecordPageFrontComponentWidget = ({
pageLayoutId,
tabListInstanceId,
}: {
pageLayoutId: string;
tabListInstanceId: string;
}) => {
const activeTabId = useAtomComponentStateValue(
activeTabIdComponentState,
tabListInstanceId,
);
const pageLayoutDraft = useAtomComponentStateValue(
pageLayoutDraftComponentState,
pageLayoutId,
);
const pageLayoutDraftState = useAtomComponentStateCallbackState(
pageLayoutDraftComponentState,
pageLayoutId,
);
const store = useStore();
const createRecordPageFrontComponentWidget = useCallback(
(title: string, frontComponentId: string): PageLayoutWidget | undefined => {
if (!isDefined(activeTabId)) {
return undefined;
}
const activeTab = pageLayoutDraft.tabs.find(
(tab) => tab.id === activeTabId,
);
const existingWidgets = activeTab?.widgets ?? [];
const positionIndex = existingWidgets.length;
const widgetId = uuidv4();
const newWidget = createDefaultFrontComponentWidgetForVerticalList({
id: widgetId,
pageLayoutTabId: activeTabId,
title,
frontComponentId,
positionIndex,
});
store.set(pageLayoutDraftState, (prev) => ({
...prev,
tabs: addWidgetToTab(prev.tabs, activeTabId, newWidget),
}));
return newWidget;
},
[activeTabId, pageLayoutDraft.tabs, pageLayoutDraftState, store],
);
return { createRecordPageFrontComponentWidget };
};
@@ -0,0 +1,50 @@
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
import {
PageLayoutTabLayoutMode,
WidgetConfigurationType,
WidgetType,
} from '~/generated-metadata/graphql';
export const createDefaultFrontComponentWidgetForVerticalList = ({
id,
pageLayoutTabId,
title,
frontComponentId,
positionIndex,
}: {
id: string;
pageLayoutTabId: string;
title: string;
frontComponentId: string;
positionIndex: number;
}): PageLayoutWidget => {
return {
__typename: 'PageLayoutWidget',
id,
pageLayoutTabId,
title,
type: WidgetType.FRONT_COMPONENT,
configuration: {
__typename: 'FrontComponentConfiguration',
configurationType: WidgetConfigurationType.FRONT_COMPONENT,
frontComponentId,
},
gridPosition: {
__typename: 'GridPosition',
row: 0,
column: 0,
rowSpan: 1,
columnSpan: 12,
},
position: {
__typename: 'PageLayoutWidgetVerticalListPosition',
layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST,
index: positionIndex,
},
objectMetadataId: null,
isOverridden: false,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
deletedAt: null,
};
};
@@ -3,13 +3,18 @@ import { Suspense, lazy } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { usePageLayoutContentContext } from '@/page-layout/contexts/PageLayoutContentContext';
import { useIsPageLayoutInEditMode } from '@/page-layout/hooks/useIsPageLayoutInEditMode';
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
import { PageLayoutWidgetNoDataDisplay } from '@/page-layout/widgets/components/PageLayoutWidgetNoDataDisplay';
import { isWidgetConfigurationOfType } from '@/side-panel/pages/page-layout/utils/isWidgetConfigurationOfType';
import { PageLayoutTabLayoutMode } from '~/generated-metadata/graphql';
const StyledContainer = styled.div<{ isInEditMode: boolean }>`
height: 100%;
const StyledContainer = styled.div<{
isInEditMode: boolean;
isVerticalList: boolean;
}>`
height: ${({ isVerticalList }) => (isVerticalList ? '300px' : '100%')};
overflow: auto;
pointer-events: ${({ isInEditMode }) => (isInEditMode ? 'none' : 'auto')};
width: 100%;
@@ -29,6 +34,9 @@ export const FrontComponentWidgetRenderer = ({
widget,
}: FrontComponentWidgetRendererProps) => {
const isPageLayoutInEditMode = useIsPageLayoutInEditMode();
const { layoutMode } = usePageLayoutContentContext();
const isVerticalList = layoutMode === PageLayoutTabLayoutMode.VERTICAL_LIST;
const configuration = widget.configuration;
@@ -42,7 +50,10 @@ export const FrontComponentWidgetRenderer = ({
const frontComponentId = configuration.frontComponentId;
return (
<StyledContainer isInEditMode={isPageLayoutInEditMode}>
<StyledContainer
isInEditMode={isPageLayoutInEditMode}
isVerticalList={isVerticalList}
>
<Suspense fallback={null}>
<FrontComponentRenderer frontComponentId={frontComponentId} />
</Suspense>
@@ -5,10 +5,12 @@ import { useCreatePageLayoutGraphWidget } from '@/page-layout/hooks/useCreatePag
import { useCreatePageLayoutIframeWidget } from '@/page-layout/hooks/useCreatePageLayoutIframeWidget';
import { useCreatePageLayoutRecordTableWidget } from '@/page-layout/hooks/useCreatePageLayoutRecordTableWidget';
import { useCreatePageLayoutStandaloneRichTextWidget } from '@/page-layout/hooks/useCreatePageLayoutStandaloneRichTextWidget';
import { useCreateRecordPageFrontComponentWidget } from '@/page-layout/hooks/useCreateRecordPageFrontComponentWidget';
import { useOpportunityDefaultChartConfig } from '@/page-layout/hooks/useOpportunityDefaultChartConfig';
import { useRemovePageLayoutWidgetAndPreservePosition } from '@/page-layout/hooks/useRemovePageLayoutWidgetAndPreservePosition';
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
import { pageLayoutEditingWidgetIdComponentState } from '@/page-layout/states/pageLayoutEditingWidgetIdComponentState';
import { getTabLayoutMode } from '@/page-layout/utils/getTabLayoutMode';
import { getTabListInstanceIdFromPageLayoutAndRecord } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutAndRecord';
import { SidePanelGroup } from '@/side-panel/components/SidePanelGroup';
import { SidePanelList } from '@/side-panel/components/SidePanelList';
@@ -18,6 +20,7 @@ import { usePageLayoutIdFromContextStore } from '@/side-panel/pages/page-layout/
import { getFrontComponentWidgetTypeSelectItemId } from '@/side-panel/pages/page-layout/utils/getFrontComponentWidgetTypeSelectItemId';
import { isExistingWidgetMissingOrDifferentType } from '@/side-panel/pages/page-layout/utils/isExistingWidgetMissingOrDifferentType';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import { useAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentState';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
@@ -35,6 +38,7 @@ import {
import {
FeatureFlagKey,
type FrontComponent,
PageLayoutTabLayoutMode,
WidgetType,
} from '~/generated-metadata/graphql';
@@ -80,6 +84,27 @@ export const SidePanelPageLayoutWidgetTypeSelect = () => {
tabListInstanceId,
});
const { createRecordPageFrontComponentWidget } =
useCreateRecordPageFrontComponentWidget({
pageLayoutId,
tabListInstanceId,
});
const activeTabId = useAtomComponentStateValue(
activeTabIdComponentState,
tabListInstanceId,
);
const activeTab = pageLayoutDraft.tabs.find((tab) => tab.id === activeTabId);
const activeTabLayoutMode = getTabLayoutMode({
tab: activeTab,
pageLayoutType: pageLayoutDraft.type,
});
const isVerticalList =
activeTabLayoutMode === PageLayoutTabLayoutMode.VERTICAL_LIST;
const { createPageLayoutRecordTableWidget } =
useCreatePageLayoutRecordTableWidget(pageLayoutId);
@@ -94,7 +119,9 @@ export const SidePanelPageLayoutWidgetTypeSelect = () => {
frontComponents: FrontComponent[];
}>(FIND_MANY_FRONT_COMPONENTS);
const frontComponents = frontComponentsData?.frontComponents ?? [];
const frontComponents = (frontComponentsData?.frontComponents ?? []).filter(
(frontComponent) => !frontComponent.isHeadless,
);
const frontComponentsWithSelectItemId = frontComponents.map(
(frontComponent) => ({
@@ -212,10 +239,19 @@ export const SidePanelPageLayoutWidgetTypeSelect = () => {
removePageLayoutWidgetAndPreservePosition(pageLayoutEditingWidgetId);
}
const newWidget = createPageLayoutFrontComponentWidget(
frontComponent.name,
frontComponent.id,
);
const newWidget = isVerticalList
? createRecordPageFrontComponentWidget(
frontComponent.name,
frontComponent.id,
)
: createPageLayoutFrontComponentWidget(
frontComponent.name,
frontComponent.id,
);
if (!isDefined(newWidget)) {
return;
}
setPageLayoutEditingWidgetId(newWidget.id);
}