Added dashboard chart advanced filter components (#15095)

This PR adds the components for advanced filters on chart settings.

It also refactors what's in common between this new command menu page
and the workflow advanced filters.
This commit is contained in:
Lucas Bordeau
2025-10-15 16:06:15 +02:00
committed by GitHub
parent bd16c77fed
commit 7f8ec35b5d
24 changed files with 445 additions and 190 deletions
@@ -3,6 +3,7 @@ import { CommandMenuAIChatThreadsPage } from '@/command-menu/pages/AIChatThreads
import { CommandMenuAskAIPage } from '@/command-menu/pages/ask-ai/components/CommandMenuAskAIPage';
import { CommandMenuCalendarEventPage } from '@/command-menu/pages/calendar-event/components/CommandMenuCalendarEventPage';
import { CommandMenuMessageThreadPage } from '@/command-menu/pages/message-thread/components/CommandMenuMessageThreadPage';
import { CommandMenuPageLayoutGraphFilter } from '@/command-menu/pages/page-layout/components/CommandMenuPageLayoutGraphFilter';
import { CommandMenuPageLayoutGraphTypeSelect } from '@/command-menu/pages/page-layout/components/CommandMenuPageLayoutGraphTypeSelect';
import { CommandMenuPageLayoutIframeConfig } from '@/command-menu/pages/page-layout/components/CommandMenuPageLayoutIframeConfig';
import { CommandMenuPageLayoutWidgetTypeSelect } from '@/command-menu/pages/page-layout/components/CommandMenuPageLayoutWidgetTypeSelect';
@@ -48,6 +49,10 @@ export const COMMAND_MENU_PAGES_CONFIG = new Map<
CommandMenuPages.PageLayoutGraphTypeSelect,
<CommandMenuPageLayoutGraphTypeSelect />,
],
[
CommandMenuPages.PageLayoutGraphFilter,
<CommandMenuPageLayoutGraphFilter />,
],
[
CommandMenuPages.PageLayoutIframeConfig,
<CommandMenuPageLayoutIframeConfig />,
@@ -0,0 +1,39 @@
import styled from '@emotion/styled';
import { IconChevronLeft } from 'twenty-ui/display';
import { IconButton } from 'twenty-ui/input';
const StyledContainer = styled.div`
align-items: center;
display: flex;
flex-direction: row;
`;
const StyledTextContainer = styled.div`
color: ${({ theme }) => theme.font.color.primary};
font-size: ${({ theme }) => theme.font.size.md};
padding-left: ${({ theme }) => theme.spacing(1)};
padding-bottom: ${({ theme }) => theme.spacing(0.2)};
`;
type CommandMenuSubPageNavigationHeaderProps = {
title: string;
onBackClick: () => void;
};
export const CommandMenuSubPageNavigationHeader = ({
onBackClick,
title,
}: CommandMenuSubPageNavigationHeaderProps) => {
return (
<StyledContainer>
<IconButton
onClick={onBackClick}
Icon={IconChevronLeft}
variant="tertiary"
/>
<StyledTextContainer>{title}</StyledTextContainer>
</StyledContainer>
);
};
@@ -0,0 +1,67 @@
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 (
<StyledChartFiltersPageContainer>
<CommandMenuSubPageNavigationHeader
title={t`Filter`}
onBackClick={handleBackClick}
/>
<StyledFiltersContainer>
<InputLabel>{t`Conditions`}</InputLabel>
<RecordFilterGroupsComponentInstanceContext.Provider
value={{ instanceId }}
>
<RecordFiltersComponentInstanceContext.Provider
value={{ instanceId }}
>
<AdvancedFilterCommandMenuContainer
onUpdate={() => {}}
objectMetadataItem={objectMetadataItem}
isWorkflowFindRecords={false}
/>
{/* TODO: create chart record filter effect to persist filters
<WorkflowFindRecordsFiltersEffect defaultValue={formData.filter} /> */}
</RecordFiltersComponentInstanceContext.Provider>
</RecordFilterGroupsComponentInstanceContext.Provider>
</StyledFiltersContainer>
</StyledChartFiltersPageContainer>
);
};
@@ -1,8 +1,10 @@
import { CommandGroup } from '@/command-menu/components/CommandGroup';
import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem';
import { CommandMenuItemDropdown } from '@/command-menu/components/CommandMenuItemDropdown';
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';
@@ -15,12 +17,14 @@ import {
CHART_CONFIGURATION_SETTING_IDS,
CHART_CONFIGURATION_SETTING_TO_CONFIG_KEY_MAP,
} from '@/command-menu/pages/page-layout/types/ChartConfigurationSettingIds';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { useOpenDropdown } from '@/ui/layout/dropdown/hooks/useOpenDropdown';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
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,
@@ -31,6 +35,8 @@ import {
export const ChartSettings = ({ widget }: { widget: PageLayoutWidget }) => {
const { updateCommandMenuPageInfo } = useUpdateCommandMenuPageInfo();
const { navigateCommandMenu } = useNavigateCommandMenu();
const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord();
const { updateCurrentWidgetConfig } =
@@ -141,6 +147,32 @@ export const ChartSettings = ({ widget }: { widget: PageLayoutWidget }) => {
});
};
const handleFilterSettingsClick = () => {
navigateCommandMenu({
page: CommandMenuPages.PageLayoutGraphFilter,
pageTitle: t`Filters`,
pageIcon: IconFilter,
});
};
if (item.id === CHART_CONFIGURATION_SETTING_IDS.FILTER) {
return (
<SelectableListItem
key={item.id}
itemId={item.id}
onEnter={handleFilterSettingsClick}
>
<CommandMenuItem
id={item.id}
label="Filter"
Icon={item.Icon}
hasSubMenu
onClick={handleFilterSettingsClick}
/>
</SelectableListItem>
);
}
return item.isBoolean ? (
<SelectableListItem
key={item.id}
@@ -0,0 +1,71 @@
import { SidePanelHeader } from '@/command-menu/components/SidePanelHeader';
import { ChartFilters } from '@/command-menu/pages/page-layout/components/ChartFilters';
import { GRAPH_TYPE_INFORMATION } from '@/command-menu/pages/page-layout/constants/GraphTypeInformation';
import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord';
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
import { pageLayoutEditingWidgetIdComponentState } from '@/page-layout/states/pageLayoutEditingWidgetIdComponentState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useTheme } from '@emotion/react';
import { t } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils';
export const CommandMenuPageLayoutGraphFilter = () => {
const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord();
const draftPageLayout = useRecoilComponentValue(
pageLayoutDraftComponentState,
pageLayoutId,
);
const pageLayoutEditingWidgetId = useRecoilComponentValue(
pageLayoutEditingWidgetIdComponentState,
pageLayoutId,
);
const widgetInEditMode = draftPageLayout.tabs
.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.configuration) ||
!('graphType' in widgetInEditMode.configuration)
) {
return null;
}
const currentGraphType = widgetInEditMode.configuration.graphType;
const graphTypeLabel = t(GRAPH_TYPE_INFORMATION[currentGraphType].label);
return (
<>
<SidePanelHeader
Icon={GRAPH_TYPE_INFORMATION[currentGraphType].icon}
iconColor={theme.font.color.tertiary}
initialTitle={widgetInEditMode.title}
headerType={t`${graphTypeLabel} Chart`}
disabled
/>
<ChartFilters
widget={widgetInEditMode}
objectMetadataItem={objectMetadataItem}
/>
</>
);
};
@@ -67,7 +67,6 @@ export const CommandMenuPageLayoutGraphTypeSelect = () => {
}
}}
/>
<ChartSettings widget={widgetInEditMode} />
</>
);
@@ -17,5 +17,6 @@ export enum CommandMenuPages {
ViewPreviousAIChats = 'view-previous-ai-chats',
PageLayoutWidgetTypeSelect = 'page-layout-widget-type-select',
PageLayoutGraphTypeSelect = 'page-layout-graph-type-select',
PageLayoutGraphFilter = 'page-layout-graph-filter',
PageLayoutIframeConfig = 'page-layout-iframe-config',
}