diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/ChartFilters.tsx b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/ChartFilters.tsx
deleted file mode 100644
index a2544f35f1..0000000000
--- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/ChartFilters.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import { CommandMenuSubPageNavigationHeader } from '@/command-menu/pages/common/components/CommandMenuSubPageNavigationHeader';
-import { useNavigatePageLayoutCommandMenu } from '@/command-menu/pages/page-layout/hooks/useNavigatePageLayoutCommandMenu';
-import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
-import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
-import { AdvancedFilterCommandMenuContainer } from '@/object-record/advanced-filter/command-menu/components/AdvancedFilterCommandMenuContainer';
-import { RecordFilterGroupsComponentInstanceContext } from '@/object-record/record-filter-group/states/context/RecordFilterGroupsComponentInstanceContext';
-import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
-import { InputLabel } from '@/ui/input/components/InputLabel';
-import styled from '@emotion/styled';
-import { t } from '@lingui/core/macro';
-import { type PageLayoutWidget } from '~/generated/graphql';
-
-const StyledChartFiltersPageContainer = styled.div`
- display: flex;
- flex-direction: column;
-
- gap: ${({ theme }) => theme.spacing(2)};
-
- padding: ${({ theme }) => theme.spacing(3)};
-`;
-
-const StyledFiltersContainer = styled.div``;
-
-export const ChartFilters = ({
- objectMetadataItem,
- widget,
-}: {
- objectMetadataItem: ObjectMetadataItem;
- widget: PageLayoutWidget;
-}) => {
- const { navigatePageLayoutCommandMenu } = useNavigatePageLayoutCommandMenu();
-
- const handleBackClick = () => {
- navigatePageLayoutCommandMenu({
- commandMenuPage: CommandMenuPages.PageLayoutGraphTypeSelect,
- });
- };
-
- const instanceId = `chart-filters-widget${widget.id}-${objectMetadataItem.id}`;
-
- return (
-
-
-
- {t`Conditions`}
-
-
- {}}
- objectMetadataItem={objectMetadataItem}
- isWorkflowFindRecords={false}
- />
- {/* TODO: create chart record filter effect to persist filters
- */}
-
-
-
-
- );
-};
diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/ChartFiltersSettings.tsx b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/ChartFiltersSettings.tsx
new file mode 100644
index 0000000000..9da140a03d
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/ChartFiltersSettings.tsx
@@ -0,0 +1,131 @@
+import { CommandMenuSubPageNavigationHeader } from '@/command-menu/pages/common/components/CommandMenuSubPageNavigationHeader';
+import { ChartFiltersSettingsInitializeStateEffect } from '@/command-menu/pages/page-layout/components/ChartFiltersSettingsInitializeStateEffect';
+import { useNavigatePageLayoutCommandMenu } from '@/command-menu/pages/page-layout/hooks/useNavigatePageLayoutCommandMenu';
+import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord';
+import { useUpdateCurrentWidgetConfig } from '@/command-menu/pages/page-layout/hooks/useUpdateCurrentWidgetConfig';
+import { type ChartWidget } from '@/command-menu/pages/page-layout/types/ChartWidget';
+import { type ChartWidgetConfiguration } from '@/command-menu/pages/page-layout/types/ChartWidgetConfiguration';
+import { getChartFiltersSettingsInstanceId } from '@/command-menu/pages/page-layout/utils/getChartFiltersSettingsInstanceId';
+
+import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
+import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
+import { AdvancedFilterCommandMenuContainer } from '@/object-record/advanced-filter/command-menu/components/AdvancedFilterCommandMenuContainer';
+import { RecordFilterGroupsComponentInstanceContext } from '@/object-record/record-filter-group/states/context/RecordFilterGroupsComponentInstanceContext';
+import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
+import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
+import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
+import { InputLabel } from '@/ui/input/components/InputLabel';
+import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
+import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
+import styled from '@emotion/styled';
+import { t } from '@lingui/core/macro';
+import { useRecoilCallback } from 'recoil';
+
+const StyledChartFiltersPageContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+
+ gap: ${({ theme }) => theme.spacing(2)};
+
+ padding: ${({ theme }) => theme.spacing(3)};
+`;
+
+export type ChartFiltersSettingsProps = {
+ objectMetadataItem: ObjectMetadataItem;
+ widget: ChartWidget;
+};
+
+export const ChartFiltersSettings = ({
+ objectMetadataItem,
+ widget,
+}: ChartFiltersSettingsProps) => {
+ const { navigatePageLayoutCommandMenu } = useNavigatePageLayoutCommandMenu();
+
+ const handleBackClick = () => {
+ navigatePageLayoutCommandMenu({
+ commandMenuPage: CommandMenuPages.PageLayoutGraphTypeSelect,
+ });
+ };
+
+ const { instanceId } = getChartFiltersSettingsInstanceId({
+ widgetId: widget.id,
+ objectMetadataItemId: objectMetadataItem.id,
+ });
+
+ const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord();
+
+ const { updateCurrentWidgetConfig } =
+ useUpdateCurrentWidgetConfig(pageLayoutId);
+
+ const currentRecordFiltersCallbackState = useRecoilComponentCallbackState(
+ currentRecordFiltersComponentState,
+ instanceId,
+ );
+
+ const currentRecordFilterGroupsCallbackState =
+ useRecoilComponentCallbackState(
+ currentRecordFilterGroupsComponentState,
+ instanceId,
+ );
+
+ const chartWidgetConfiguration = widget.configuration;
+
+ const handleFiltersUpdate = useRecoilCallback(
+ ({ snapshot }) =>
+ () => {
+ const currentRecordFilters = getSnapshotValue(
+ snapshot,
+ currentRecordFiltersCallbackState,
+ );
+
+ const currentRecordFilterGroups = getSnapshotValue(
+ snapshot,
+ currentRecordFilterGroupsCallbackState,
+ );
+
+ updateCurrentWidgetConfig({
+ objectMetadataId: objectMetadataItem.id,
+ configToUpdate: {
+ filter: {
+ recordFilters: currentRecordFilters,
+ recordFilterGroups: currentRecordFilterGroups,
+ },
+ } satisfies Partial,
+ });
+ },
+ [
+ currentRecordFiltersCallbackState,
+ currentRecordFilterGroupsCallbackState,
+ objectMetadataItem,
+ updateCurrentWidgetConfig,
+ ],
+ );
+
+ return (
+
+
+
+
{t`Conditions`}
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/ChartFiltersSettingsInitializeStateEffect.tsx b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/ChartFiltersSettingsInitializeStateEffect.tsx
new file mode 100644
index 0000000000..1004fd56f0
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/ChartFiltersSettingsInitializeStateEffect.tsx
@@ -0,0 +1,63 @@
+import { hasInitializedChartFiltersComponentState } from '@/command-menu/pages/page-layout/states/hasInitializedChartFiltersComponentState';
+import { type ChartFilters } from '@/command-menu/pages/page-layout/types/ChartFilters';
+import { useSetAdvancedFilterDropdownStates } from '@/object-record/advanced-filter/hooks/useSetAdvancedFilterDropdownAllRowsStates';
+import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
+import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
+import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
+import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
+import { useEffect, useState } from 'react';
+import { isDefined } from 'twenty-shared/utils';
+
+export type ChartFiltersSettingsInitializeStateEffectProps = {
+ initialChartFilters?: ChartFilters;
+};
+
+export const ChartFiltersSettingsInitializeStateEffect = ({
+ initialChartFilters,
+}: ChartFiltersSettingsInitializeStateEffectProps) => {
+ const [hasInitializedChartFilters, setHasInitializedChartFilters] =
+ useRecoilComponentState(hasInitializedChartFiltersComponentState);
+
+ const setCurrentRecordFilters = useSetRecoilComponentState(
+ currentRecordFiltersComponentState,
+ );
+
+ const setCurrentRecordFilterGroups = useSetRecoilComponentState(
+ currentRecordFilterGroupsComponentState,
+ );
+
+ const { setAdvancedFilterDropdownStates } =
+ useSetAdvancedFilterDropdownStates();
+
+ const [
+ shouldSetAdvancedFilterDropdownStates,
+ setShouldSetAdvancedFilterDropdownStates,
+ ] = useState(false);
+
+ useEffect(() => {
+ if (!hasInitializedChartFilters && isDefined(initialChartFilters)) {
+ setCurrentRecordFilters(initialChartFilters.recordFilters ?? []);
+ setCurrentRecordFilterGroups(
+ initialChartFilters.recordFilterGroups ?? [],
+ );
+
+ setShouldSetAdvancedFilterDropdownStates(true);
+ setHasInitializedChartFilters(true);
+ }
+ }, [
+ setCurrentRecordFilters,
+ setCurrentRecordFilterGroups,
+ setHasInitializedChartFilters,
+ hasInitializedChartFilters,
+ initialChartFilters,
+ ]);
+
+ useEffect(() => {
+ if (shouldSetAdvancedFilterDropdownStates) {
+ setAdvancedFilterDropdownStates();
+ setShouldSetAdvancedFilterDropdownStates(false);
+ }
+ }, [shouldSetAdvancedFilterDropdownStates, setAdvancedFilterDropdownStates]);
+
+ return null;
+};
diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/ChartSettings.tsx b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/ChartSettings.tsx
index 95b7123dfa..21158c2ce4 100644
--- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/ChartSettings.tsx
+++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/ChartSettings.tsx
@@ -4,12 +4,12 @@ import { CommandMenuItemDropdown } from '@/command-menu/components/CommandMenuIt
import { CommandMenuItemToggle } from '@/command-menu/components/CommandMenuItemToggle';
import { CommandMenuList } from '@/command-menu/components/CommandMenuList';
import { COMMAND_MENU_LIST_SELECTABLE_LIST_ID } from '@/command-menu/constants/CommandMenuListSelectableListId';
-import { useNavigateCommandMenu } from '@/command-menu/hooks/useNavigateCommandMenu';
import { useUpdateCommandMenuPageInfo } from '@/command-menu/hooks/useUpdateCommandMenuPageInfo';
import { ChartTypeSelectionSection } from '@/command-menu/pages/page-layout/components/ChartTypeSelectionSection';
import { GRAPH_TYPE_INFORMATION } from '@/command-menu/pages/page-layout/constants/GraphTypeInformation';
import { GRAPH_TYPE_TO_CONFIG_TYPENAME } from '@/command-menu/pages/page-layout/constants/GraphTypeToConfigTypename';
import { useChartSettingsValues } from '@/command-menu/pages/page-layout/hooks/useChartSettingsValues';
+import { useNavigatePageLayoutCommandMenu } from '@/command-menu/pages/page-layout/hooks/useNavigatePageLayoutCommandMenu';
import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord';
import { useUpdateCurrentWidgetConfig } from '@/command-menu/pages/page-layout/hooks/useUpdateCurrentWidgetConfig';
import { type ChartConfiguration } from '@/command-menu/pages/page-layout/types/ChartConfiguration';
@@ -24,7 +24,6 @@ import { SelectableListItem } from '@/ui/layout/selectable-list/components/Selec
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { t } from '@lingui/core/macro';
import { isNonEmptyString } from '@sniptt/guards';
-import { IconFilter } from 'twenty-ui/display';
import {
BarChartGroupMode,
@@ -35,7 +34,7 @@ import {
export const ChartSettings = ({ widget }: { widget: PageLayoutWidget }) => {
const { updateCommandMenuPageInfo } = useUpdateCommandMenuPageInfo();
- const { navigateCommandMenu } = useNavigateCommandMenu();
+ const { navigatePageLayoutCommandMenu } = useNavigatePageLayoutCommandMenu();
const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord();
@@ -144,10 +143,8 @@ export const ChartSettings = ({ widget }: { widget: PageLayoutWidget }) => {
};
const handleFilterSettingsClick = () => {
- navigateCommandMenu({
- page: CommandMenuPages.PageLayoutGraphFilter,
- pageTitle: t`Filters`,
- pageIcon: IconFilter,
+ navigatePageLayoutCommandMenu({
+ commandMenuPage: CommandMenuPages.PageLayoutGraphFilter,
});
};
diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutGraphFilter.tsx b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutGraphFilter.tsx
index 949de965d2..c731cfb476 100644
--- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutGraphFilter.tsx
+++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutGraphFilter.tsx
@@ -1,7 +1,8 @@
import { SidePanelHeader } from '@/command-menu/components/SidePanelHeader';
-import { ChartFilters } from '@/command-menu/pages/page-layout/components/ChartFilters';
+import { ChartFiltersSettings } from '@/command-menu/pages/page-layout/components/ChartFiltersSettings';
import { GRAPH_TYPE_INFORMATION } from '@/command-menu/pages/page-layout/constants/GraphTypeInformation';
import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord';
+import { isChartWidget } from '@/command-menu/pages/page-layout/utils/isChartWidget';
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
import { pageLayoutEditingWidgetIdComponentState } from '@/page-layout/states/pageLayoutEditingWidgetIdComponentState';
@@ -11,6 +12,8 @@ import { t } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils';
export const CommandMenuPageLayoutGraphFilter = () => {
+ const theme = useTheme();
+
const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord();
const draftPageLayout = useRecoilComponentValue(
@@ -27,26 +30,25 @@ export const CommandMenuPageLayoutGraphFilter = () => {
.flatMap((tab) => tab.widgets)
.find((widget) => widget.id === pageLayoutEditingWidgetId);
- const { objectMetadataItem } = useObjectMetadataItemById({
- objectId: widgetInEditMode?.objectMetadataId,
- });
-
- if (!isDefined(pageLayoutEditingWidgetId)) {
- throw new Error('Widget ID must be present while editing the widget');
- }
-
if (!isDefined(widgetInEditMode)) {
throw new Error(
`Widget with ID ${pageLayoutEditingWidgetId} not found in page layout`,
);
}
- const theme = useTheme();
+ if (!isDefined(widgetInEditMode?.objectMetadataId)) {
+ throw new Error('No data source in chart');
+ }
- if (
- !isDefined(widgetInEditMode.configuration) ||
- !('graphType' in widgetInEditMode.configuration)
- ) {
+ const { objectMetadataItem } = useObjectMetadataItemById({
+ objectId: widgetInEditMode.objectMetadataId,
+ });
+
+ if (!isDefined(pageLayoutEditingWidgetId)) {
+ throw new Error('Widget ID must be present while editing the widget');
+ }
+
+ if (!isChartWidget(widgetInEditMode)) {
return null;
}
@@ -62,7 +64,7 @@ export const CommandMenuPageLayoutGraphFilter = () => {
headerType={t`${graphTypeLabel} Chart`}
disabled
/>
-
diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartDataSourceDropdownContent.tsx b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartDataSourceDropdownContent.tsx
index f2c3d76cc6..132dcd4d62 100644
--- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartDataSourceDropdownContent.tsx
+++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartDataSourceDropdownContent.tsx
@@ -1,4 +1,5 @@
import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord';
+import { useResetChartDraftFiltersSettings } from '@/command-menu/pages/page-layout/hooks/useResetChartDraftFiltersSettings';
import { useUpdateCurrentWidgetConfig } from '@/command-menu/pages/page-layout/hooks/useUpdateCurrentWidgetConfig';
import { useWidgetInEditMode } from '@/command-menu/pages/page-layout/hooks/useWidgetInEditMode';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
@@ -35,7 +36,9 @@ export const ChartDataSourceDropdownContent = () => {
const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId);
- const currentSource = widgetInEditMode?.objectMetadataId;
+ const currentObjectMetadataItemId = widgetInEditMode?.objectMetadataId as
+ | string
+ | undefined;
const dropdownId = useAvailableComponentInstanceIdOrThrow(
DropdownComponentInstanceContext,
@@ -80,10 +83,13 @@ export const ChartDataSourceDropdownContent = () => {
const { getIcon } = useIcons();
- const handleSelectSource = (objectMetadataId: string) => {
- if (currentSource !== objectMetadataId) {
+ const { resetChartDraftFiltersSettings } =
+ useResetChartDraftFiltersSettings();
+
+ const handleSelectSource = (newObjectMetadataItemId: string) => {
+ if (currentObjectMetadataItemId !== newObjectMetadataItemId) {
updateCurrentWidgetConfig({
- objectMetadataId,
+ objectMetadataId: newObjectMetadataItemId,
configToUpdate: {
aggregateFieldMetadataId: null,
primaryAxisGroupByFieldMetadataId: null,
@@ -94,8 +100,13 @@ export const ChartDataSourceDropdownContent = () => {
secondaryAxisOrderBy: null,
groupByFieldMetadataId: null,
groupBySubFieldName: null,
+ filter: {},
},
});
+
+ if (isDefined(currentObjectMetadataItemId)) {
+ resetChartDraftFiltersSettings(currentObjectMetadataItemId);
+ }
}
closeDropdown();
};
@@ -155,7 +166,7 @@ export const ChartDataSourceDropdownContent = () => {
>
{
diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartSortByGroupByFieldDropdownContent.tsx b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartSortByGroupByFieldDropdownContent.tsx
index e0c11941b1..04b52072bd 100644
--- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartSortByGroupByFieldDropdownContent.tsx
+++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartSortByGroupByFieldDropdownContent.tsx
@@ -11,6 +11,7 @@ import { SelectableListItem } from '@/ui/layout/selectable-list/components/Selec
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
+import { isDefined } from 'twenty-shared/utils';
import { MenuItemSelect } from 'twenty-ui/navigation';
import { type GraphOrderBy } from '~/generated/graphql';
@@ -28,6 +29,9 @@ export const ChartSortByGroupByFieldDropdownContent = () => {
throw new Error('Invalid configuration type');
}
+ if (!isDefined(widgetInEditMode?.objectMetadataId)) {
+ throw new Error('No data source in chart');
+ }
const dropdownId = useAvailableComponentInstanceIdOrThrow(
DropdownComponentInstanceContext,
);
@@ -50,7 +54,7 @@ export const ChartSortByGroupByFieldDropdownContent = () => {
};
const { getGroupBySortOptionLabel } = useGraphGroupBySortOptionLabels({
- objectMetadataId: widgetInEditMode?.objectMetadataId,
+ objectMetadataId: widgetInEditMode.objectMetadataId,
});
return (
diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartXAxisSortBySelectionDropdownContent.tsx b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartXAxisSortBySelectionDropdownContent.tsx
index c15e2b9948..6fb2d062b8 100644
--- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartXAxisSortBySelectionDropdownContent.tsx
+++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartXAxisSortBySelectionDropdownContent.tsx
@@ -12,6 +12,7 @@ import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { type CompositeFieldSubFieldName } from 'twenty-shared/types';
+import { isDefined } from 'twenty-shared/utils';
import { MenuItemSelect } from 'twenty-ui/navigation';
import { type GraphOrderBy } from '~/generated/graphql';
@@ -27,6 +28,10 @@ export const ChartXAxisSortBySelectionDropdownContent = () => {
throw new Error('Invalid configuration type');
}
+ if (!isDefined(widgetInEditMode?.objectMetadataId)) {
+ throw new Error('No data source in chart');
+ }
+
const dropdownId = useAvailableComponentInstanceIdOrThrow(
DropdownComponentInstanceContext,
);
@@ -41,7 +46,7 @@ export const ChartXAxisSortBySelectionDropdownContent = () => {
const { closeDropdown } = useCloseDropdown();
const { getXSortOptionLabel } = useGraphXSortOptionLabels({
- objectMetadataId: widgetInEditMode?.objectMetadataId,
+ objectMetadataId: widgetInEditMode.objectMetadataId,
});
const handleSelect = (orderBy: GraphOrderBy) => {
diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/useNavigatePageLayoutCommandMenu.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/useNavigatePageLayoutCommandMenu.ts
index edb678df24..684c1c1039 100644
--- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/useNavigatePageLayoutCommandMenu.ts
+++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/useNavigatePageLayoutCommandMenu.ts
@@ -1,16 +1,13 @@
import { useNavigateCommandMenu } from '@/command-menu/hooks/useNavigateCommandMenu';
+import { type PageLayoutCommandMenuPage } from '@/command-menu/pages/page-layout/types/PageLayoutCommandMenuPage';
import { getPageLayoutIcon } from '@/command-menu/pages/page-layout/utils/getPageLayoutIcon';
import { getPageLayoutPageTitle } from '@/command-menu/pages/page-layout/utils/getPageLayoutPageTitle';
-import { type CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { type IconComponent } from 'twenty-ui/display';
type NavigatePageLayoutCommandMenuProps = {
- commandMenuPage:
- | CommandMenuPages.PageLayoutWidgetTypeSelect
- | CommandMenuPages.PageLayoutGraphTypeSelect
- | CommandMenuPages.PageLayoutIframeConfig;
+ commandMenuPage: PageLayoutCommandMenuPage;
pageTitle?: string;
pageIcon?: IconComponent;
};
diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/useResetChartDraftFiltersSettings.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/useResetChartDraftFiltersSettings.ts
new file mode 100644
index 0000000000..5457484401
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/useResetChartDraftFiltersSettings.ts
@@ -0,0 +1,58 @@
+import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord';
+import { useWidgetInEditMode } from '@/command-menu/pages/page-layout/hooks/useWidgetInEditMode';
+import { getChartFiltersSettingsInstanceId } from '@/command-menu/pages/page-layout/utils/getChartFiltersSettingsInstanceId';
+import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
+import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
+import { hasInitializedCurrentRecordFilterGroupsComponentFamilyState } from '@/views/states/hasInitializedCurrentRecordFilterGroupsComponentFamilyState';
+import { hasInitializedCurrentRecordFiltersComponentFamilyState } from '@/views/states/hasInitializedCurrentRecordFiltersComponentFamilyState';
+import { useRecoilCallback } from 'recoil';
+import { isDefined } from 'twenty-shared/utils';
+
+export const useResetChartDraftFiltersSettings = () => {
+ const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord();
+
+ const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId);
+
+ const resetChartDraftFiltersSettings = useRecoilCallback(
+ ({ set }) =>
+ (objectMetadataItemId: string) => {
+ if (!isDefined(widgetInEditMode)) {
+ return;
+ }
+
+ const { instanceId } = getChartFiltersSettingsInstanceId({
+ widgetId: widgetInEditMode.id,
+ objectMetadataItemId: objectMetadataItemId,
+ });
+
+ set(
+ hasInitializedCurrentRecordFilterGroupsComponentFamilyState.atomFamily(
+ {
+ familyKey: {},
+ instanceId,
+ },
+ ),
+ false,
+ );
+
+ set(
+ hasInitializedCurrentRecordFiltersComponentFamilyState.atomFamily({
+ familyKey: {},
+ instanceId,
+ }),
+ false,
+ );
+
+ set(currentRecordFiltersComponentState.atomFamily({ instanceId }), []);
+ set(
+ currentRecordFilterGroupsComponentState.atomFamily({ instanceId }),
+ [],
+ );
+ },
+ [widgetInEditMode],
+ );
+
+ return {
+ resetChartDraftFiltersSettings,
+ };
+};
diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/states/hasInitializedChartFiltersComponentState.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/states/hasInitializedChartFiltersComponentState.ts
new file mode 100644
index 0000000000..05ece29100
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/states/hasInitializedChartFiltersComponentState.ts
@@ -0,0 +1,9 @@
+import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
+import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
+
+export const hasInitializedChartFiltersComponentState =
+ createComponentState({
+ key: 'hasInitializedCurrentRecordFiltersComponentFamilyState',
+ defaultValue: false,
+ componentInstanceContext: RecordFiltersComponentInstanceContext,
+ });
diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/types/ChartFilters.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/types/ChartFilters.ts
new file mode 100644
index 0000000000..b3a66973fb
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/types/ChartFilters.ts
@@ -0,0 +1,7 @@
+import { type RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup';
+import { type RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
+
+export type ChartFilters = {
+ recordFilters?: RecordFilter[];
+ recordFilterGroups?: RecordFilterGroup[];
+};
diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/types/ChartWidget.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/types/ChartWidget.ts
new file mode 100644
index 0000000000..e8e087852b
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/types/ChartWidget.ts
@@ -0,0 +1,15 @@
+import { type ChartFilters } from '@/command-menu/pages/page-layout/types/ChartFilters';
+import { type ChartWidgetConfiguration } from '@/command-menu/pages/page-layout/types/ChartWidgetConfiguration';
+import { type ModifiedProperties } from 'twenty-shared/types';
+import { type PageLayoutWidget, type WidgetType } from '~/generated/graphql';
+
+export type ChartWidget = ModifiedProperties<
+ PageLayoutWidget,
+ {
+ type: WidgetType.GRAPH;
+ configuration: ModifiedProperties<
+ ChartWidgetConfiguration,
+ { filter?: ChartFilters }
+ >;
+ }
+>;
diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/types/ChartWidgetConfiguration.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/types/ChartWidgetConfiguration.ts
new file mode 100644
index 0000000000..6828497b7e
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/types/ChartWidgetConfiguration.ts
@@ -0,0 +1,14 @@
+import {
+ type BarChartConfiguration,
+ type GaugeChartConfiguration,
+ type LineChartConfiguration,
+ type NumberChartConfiguration,
+ type PieChartConfiguration,
+} from '~/generated/graphql';
+
+export type ChartWidgetConfiguration =
+ | BarChartConfiguration
+ | GaugeChartConfiguration
+ | LineChartConfiguration
+ | NumberChartConfiguration
+ | PieChartConfiguration;
diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/types/PageLayoutCommandMenuPage.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/types/PageLayoutCommandMenuPage.ts
index a6ac401b19..7fc0342dc2 100644
--- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/types/PageLayoutCommandMenuPage.ts
+++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/types/PageLayoutCommandMenuPage.ts
@@ -3,4 +3,5 @@ import { type CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
export type PageLayoutCommandMenuPage =
| CommandMenuPages.PageLayoutWidgetTypeSelect
| CommandMenuPages.PageLayoutGraphTypeSelect
- | CommandMenuPages.PageLayoutIframeConfig;
+ | CommandMenuPages.PageLayoutIframeConfig
+ | CommandMenuPages.PageLayoutGraphFilter;
diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/getChartFiltersSettingsInstanceId.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/getChartFiltersSettingsInstanceId.ts
new file mode 100644
index 0000000000..cb19518d7a
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/getChartFiltersSettingsInstanceId.ts
@@ -0,0 +1,13 @@
+export const getChartFiltersSettingsInstanceId = ({
+ widgetId,
+ objectMetadataItemId,
+}: {
+ widgetId: string;
+ objectMetadataItemId: string;
+}) => {
+ const instanceId = `chart-filters-widget-${widgetId}-${objectMetadataItemId}`;
+
+ return {
+ instanceId,
+ };
+};
diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/getPageLayoutIcon.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/getPageLayoutIcon.ts
index ee3de81e40..c0516e9e4e 100644
--- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/getPageLayoutIcon.ts
+++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/getPageLayoutIcon.ts
@@ -1,7 +1,12 @@
import { type PageLayoutCommandMenuPage } from '@/command-menu/pages/page-layout/types/PageLayoutCommandMenuPage';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { assertUnreachable } from 'twenty-shared/utils';
-import { IconAppWindow, IconChartPie, IconFrame } from 'twenty-ui/display';
+import {
+ IconAppWindow,
+ IconChartPie,
+ IconFilter,
+ IconFrame,
+} from 'twenty-ui/display';
export const getPageLayoutIcon = (page: PageLayoutCommandMenuPage) => {
switch (page) {
@@ -11,6 +16,8 @@ export const getPageLayoutIcon = (page: PageLayoutCommandMenuPage) => {
return IconChartPie;
case CommandMenuPages.PageLayoutIframeConfig:
return IconFrame;
+ case CommandMenuPages.PageLayoutGraphFilter:
+ return IconFilter;
default:
assertUnreachable(page);
}
diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/getPageLayoutPageTitle.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/getPageLayoutPageTitle.ts
index 6f85f874c1..3e18df4e3e 100644
--- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/getPageLayoutPageTitle.ts
+++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/getPageLayoutPageTitle.ts
@@ -11,6 +11,8 @@ export const getPageLayoutPageTitle = (page: PageLayoutCommandMenuPage) => {
return t`Select Graph Type`;
case CommandMenuPages.PageLayoutIframeConfig:
return t`Configure iFrame`;
+ case CommandMenuPages.PageLayoutGraphFilter:
+ return t`Configure filters`;
default:
assertUnreachable(page);
}
diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/isChartWidget.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/isChartWidget.ts
new file mode 100644
index 0000000000..b7f0165c27
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/isChartWidget.ts
@@ -0,0 +1,8 @@
+import { type ChartWidget } from '@/command-menu/pages/page-layout/types/ChartWidget';
+import { WidgetType } from '~/generated/graphql';
+
+export const isChartWidget = (
+ pageLayoutWidget: any,
+): pageLayoutWidget is ChartWidget => {
+ return pageLayoutWidget.type === WidgetType.GRAPH;
+};
diff --git a/packages/twenty-front/src/modules/page-layout/types/PageLayoutTab.ts b/packages/twenty-front/src/modules/page-layout/types/PageLayoutTab.ts
index cf3dd8f133..31cb1d4ab5 100644
--- a/packages/twenty-front/src/modules/page-layout/types/PageLayoutTab.ts
+++ b/packages/twenty-front/src/modules/page-layout/types/PageLayoutTab.ts
@@ -1,10 +1,14 @@
+import { type ModifiedProperties, type Nullable } from 'twenty-shared/types';
import {
type PageLayoutTab as PageLayoutTabGenerated,
type PageLayoutWidget,
} from '~/generated/graphql';
export type PageLayoutTab = Omit & {
- widgets: PageLayoutWidget[];
+ widgets: ModifiedProperties<
+ PageLayoutWidget,
+ { objectMetadataId?: Nullable }
+ >[];
/**
* Only available behind IS_RECORD_PAGE_LAYOUT_ENABLED for now.
*/
diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/hooks/useGraphWidgetGroupByQuery.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/hooks/useGraphWidgetGroupByQuery.ts
index 378cce5e1a..abad746a64 100644
--- a/packages/twenty-front/src/modules/page-layout/widgets/graph/hooks/useGraphWidgetGroupByQuery.ts
+++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/hooks/useGraphWidgetGroupByQuery.ts
@@ -5,7 +5,10 @@ import { generateGroupByQuery } from '@/page-layout/widgets/graph/utils/generate
import { generateGroupByQueryVariablesFromBarChartConfiguration } from '@/page-layout/widgets/graph/utils/generateGroupByQueryVariablesFromBarChartConfiguration';
import { useQuery } from '@apollo/client';
import { useMemo } from 'react';
-import { isDefined } from 'twenty-shared/utils';
+import {
+ computeRecordGqlOperationFilter,
+ isDefined,
+} from 'twenty-shared/utils';
import { type BarChartConfiguration } from '~/generated-metadata/graphql';
export const useGraphWidgetGroupByQuery = ({
@@ -46,17 +49,34 @@ export const useGraphWidgetGroupByQuery = ({
throw new Error('Aggregate operation not found');
}
+ const gqlOperationFilter = computeRecordGqlOperationFilter({
+ fields: objectMetadataItem.fields,
+ filterValueDependencies: {},
+ recordFilters: configuration.filter?.recordFilters ?? [],
+ recordFilterGroups: configuration.filter?.recordFilterGroups ?? [],
+ });
+
+ const filterQueryVariables = {
+ filter: gqlOperationFilter,
+ };
+
+ const groupByQueryVariables =
+ generateGroupByQueryVariablesFromBarChartConfiguration({
+ objectMetadataItem,
+ barChartConfiguration: configuration,
+ aggregateOperation,
+ });
+
+ const variables = {
+ ...groupByQueryVariables,
+ ...filterQueryVariables,
+ };
+
const query = generateGroupByQuery({
objectMetadataItem,
aggregateOperations: [aggregateOperation],
});
- const variables = generateGroupByQueryVariablesFromBarChartConfiguration({
- objectMetadataItem,
- barChartConfiguration: configuration,
- aggregateOperation,
- });
-
const apolloCoreClient = useApolloCoreClient();
const { data, loading, error, refetch } = useQuery(query, {
@@ -69,7 +89,6 @@ export const useGraphWidgetGroupByQuery = ({
loading,
error,
refetch,
- variables,
aggregateOperation,
};
};
diff --git a/packages/twenty-shared/src/types/ModifiedProperties.ts b/packages/twenty-shared/src/types/ModifiedProperties.ts
new file mode 100644
index 0000000000..959febcac1
--- /dev/null
+++ b/packages/twenty-shared/src/types/ModifiedProperties.ts
@@ -0,0 +1 @@
+export type ModifiedProperties = Omit & R;
diff --git a/packages/twenty-shared/src/types/index.ts b/packages/twenty-shared/src/types/index.ts
index d4f4d1134f..b5f56232cf 100644
--- a/packages/twenty-shared/src/types/index.ts
+++ b/packages/twenty-shared/src/types/index.ts
@@ -29,6 +29,7 @@ export { FILTERABLE_FIELD_TYPES } from './FilterableFieldType';
export type { FromTo } from './FromToType';
export type { IsEmptyRecord } from './IsEmptyRecord.type';
export type { IsExactly } from './IsExactly';
+export type { ModifiedProperties } from './ModifiedProperties';
export type { NonNullableRequired } from './NonNullableRequired';
export type { Nullable } from './Nullable';
export type { NullablePartial } from './NullablePartial';