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';