Chart editor - Part 1 (#14820)
This PR is the first part of the creation of the Chart editor. https://github.com/user-attachments/assets/8b0af8ea-be41-4506-84cb-e40f5521cbf0 Done: - Bar chart settings (except filters) - Line chart settings (except filters) In progress: - Pie chart settings - Number chart settings - Gauge chart settings Left to do: - Loosen the backend validation to allow the user to save a partial configuration, validate the graph configuration in the frontend and display a error friendly message in the graph if the config is not completed yet - Implement the filter edition - Finish the other graph types settings
This commit is contained in:
@@ -16,17 +16,25 @@ export type CommandMenuItemProps = {
|
||||
Icon?: IconComponent;
|
||||
hotKeys?: string[];
|
||||
RightComponent?: ReactNode;
|
||||
contextualTextPosition?: 'left' | 'right';
|
||||
hasSubMenu?: boolean;
|
||||
isSubMenuOpened?: boolean;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export const CommandMenuItem = ({
|
||||
label,
|
||||
description,
|
||||
contextualTextPosition = 'left',
|
||||
to,
|
||||
id,
|
||||
onClick,
|
||||
Icon,
|
||||
hotKeys,
|
||||
RightComponent,
|
||||
hasSubMenu = false,
|
||||
isSubMenuOpened = false,
|
||||
disabled = false,
|
||||
}: CommandMenuItemProps) => {
|
||||
const { onItemClick } = useCommandMenuOnItemClick();
|
||||
|
||||
@@ -45,15 +53,22 @@ export const CommandMenuItem = ({
|
||||
LeftIcon={Icon}
|
||||
text={label}
|
||||
contextualText={description}
|
||||
contextualTextPosition={contextualTextPosition}
|
||||
hotKeys={hotKeys}
|
||||
onClick={() =>
|
||||
onItemClick({
|
||||
onClick,
|
||||
to,
|
||||
})
|
||||
onClick={
|
||||
onClick || to
|
||||
? () =>
|
||||
onItemClick({
|
||||
onClick,
|
||||
to,
|
||||
})
|
||||
: undefined
|
||||
}
|
||||
focused={isSelectedItemId}
|
||||
RightComponent={RightComponent}
|
||||
hasSubMenu={hasSubMenu}
|
||||
isSubMenuOpened={isSubMenuOpened}
|
||||
disabled={disabled}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import {
|
||||
CommandMenuItem,
|
||||
type CommandMenuItemProps,
|
||||
} from '@/command-menu/components/CommandMenuItem';
|
||||
import {
|
||||
Dropdown,
|
||||
type DropdownProps,
|
||||
} from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
|
||||
export type CommandMenuItemDropdownProps = CommandMenuItemProps &
|
||||
Pick<
|
||||
DropdownProps,
|
||||
'dropdownPlacement' | 'dropdownOffset' | 'dropdownId' | 'dropdownComponents'
|
||||
>;
|
||||
|
||||
export const CommandMenuItemDropdown = ({
|
||||
id,
|
||||
label,
|
||||
Icon,
|
||||
hotKeys,
|
||||
RightComponent,
|
||||
description,
|
||||
contextualTextPosition,
|
||||
dropdownComponents,
|
||||
dropdownPlacement,
|
||||
dropdownOffset,
|
||||
dropdownId,
|
||||
disabled = false,
|
||||
}: CommandMenuItemDropdownProps) => {
|
||||
const isDropdownOpen = useRecoilComponentValue(
|
||||
isDropdownOpenComponentState,
|
||||
dropdownId,
|
||||
);
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
clickableComponent={
|
||||
<CommandMenuItem
|
||||
id={id}
|
||||
label={label}
|
||||
description={description}
|
||||
contextualTextPosition={contextualTextPosition}
|
||||
Icon={Icon}
|
||||
hotKeys={hotKeys}
|
||||
RightComponent={RightComponent}
|
||||
hasSubMenu
|
||||
isSubMenuOpened={isDropdownOpen}
|
||||
disabled={disabled}
|
||||
/>
|
||||
}
|
||||
dropdownComponents={dropdownComponents}
|
||||
dropdownId={dropdownId}
|
||||
dropdownPlacement={dropdownPlacement}
|
||||
dropdownOffset={dropdownOffset}
|
||||
disableClickForClickableComponent={disabled}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
import { isSelectedItemIdComponentFamilySelector } from '@/ui/layout/selectable-list/states/selectors/isSelectedItemIdComponentFamilySelector';
|
||||
import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue';
|
||||
import { MenuItemToggle, type MenuItemToggleProps } from 'twenty-ui/navigation';
|
||||
|
||||
export type CommandMenuItemToggleProps = MenuItemToggleProps & {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export const CommandMenuItemToggle = (props: CommandMenuItemToggleProps) => {
|
||||
const isSelectedItemId = useRecoilComponentFamilyValue(
|
||||
isSelectedItemIdComponentFamilySelector,
|
||||
props.id,
|
||||
);
|
||||
|
||||
return (
|
||||
<MenuItemToggle
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
focused={isSelectedItemId}
|
||||
withIconContainer
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -30,7 +30,7 @@ const StyledInnerList = styled.div`
|
||||
);
|
||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
||||
padding-right: ${({ theme }) => theme.spacing(2)};
|
||||
padding-top: ${({ theme }) => theme.spacing(1)};
|
||||
padding-top: ${({ theme }) => theme.spacing(2)};
|
||||
width: calc(100% - ${({ theme }) => theme.spacing(4)});
|
||||
|
||||
@media (min-width: ${MOBILE_VIEWPORT}px) {
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
import { useUpdateCommandMenuPageInfo } from '@/command-menu/hooks/useUpdateCommandMenuPageInfo';
|
||||
import { TitleInput } from '@/ui/input/components/TitleInput';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useState } from 'react';
|
||||
import { type IconComponent } from 'twenty-ui/display';
|
||||
|
||||
const StyledHeader = styled.div`
|
||||
background-color: ${({ theme }) => theme.background.secondary};
|
||||
border-bottom: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: ${({ theme }) => theme.spacing(4)};
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledHeaderInfo = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledHeaderTitle = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
||||
font-size: ${({ theme }) => theme.font.size.xl};
|
||||
width: fit-content;
|
||||
max-width: 420px;
|
||||
& > input:disabled {
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledHeaderType = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
padding-left: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
const StyledHeaderIconContainer = styled.div`
|
||||
align-self: flex-start;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: ${({ theme }) => theme.background.transparent.light};
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
padding: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
type SidePanelHeaderProps = {
|
||||
Icon: IconComponent;
|
||||
iconColor: string;
|
||||
initialTitle: string;
|
||||
headerType: string;
|
||||
} & (
|
||||
| {
|
||||
disabled: true;
|
||||
onTitleChange?: never;
|
||||
}
|
||||
| {
|
||||
disabled?: boolean;
|
||||
onTitleChange: (newTitle: string) => void;
|
||||
}
|
||||
);
|
||||
|
||||
export const SidePanelHeader = ({
|
||||
Icon,
|
||||
iconColor,
|
||||
initialTitle,
|
||||
headerType,
|
||||
disabled,
|
||||
onTitleChange,
|
||||
}: SidePanelHeaderProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const [title, setTitle] = useState(initialTitle);
|
||||
|
||||
const { updateCommandMenuPageInfo } = useUpdateCommandMenuPageInfo();
|
||||
|
||||
const handleChange = (newTitle: string) => {
|
||||
setTitle(newTitle);
|
||||
};
|
||||
|
||||
const saveTitle = () => {
|
||||
onTitleChange?.(title);
|
||||
updateCommandMenuPageInfo({
|
||||
pageTitle: title,
|
||||
pageIcon: Icon,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledHeader data-testid="side-panel-header">
|
||||
<StyledHeaderIconContainer>
|
||||
<Icon
|
||||
color={iconColor}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
size={theme.icon.size.lg}
|
||||
/>
|
||||
</StyledHeaderIconContainer>
|
||||
<StyledHeaderInfo>
|
||||
<StyledHeaderTitle>
|
||||
<TitleInput
|
||||
instanceId="side-panel-title-input"
|
||||
disabled={disabled}
|
||||
sizeVariant="md"
|
||||
value={title}
|
||||
onChange={handleChange}
|
||||
placeholder={headerType}
|
||||
onEnter={saveTitle}
|
||||
onEscape={() => {
|
||||
setTitle(initialTitle);
|
||||
}}
|
||||
onClickOutside={saveTitle}
|
||||
onTab={saveTitle}
|
||||
onShiftTab={saveTitle}
|
||||
/>
|
||||
</StyledHeaderTitle>
|
||||
<StyledHeaderType>{headerType}</StyledHeaderType>
|
||||
</StyledHeaderInfo>
|
||||
</StyledHeader>
|
||||
);
|
||||
};
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
import { type Meta, type StoryObj } from '@storybook/react';
|
||||
import { expect, fn, userEvent, waitFor, within } from '@storybook/test';
|
||||
import { IconPlus } from 'twenty-ui/display';
|
||||
import { ComponentDecorator } from 'twenty-ui/testing';
|
||||
import { THEME_LIGHT } from 'twenty-ui/theme';
|
||||
import { SidePanelHeader } from '../SidePanelHeader';
|
||||
|
||||
const meta: Meta<typeof SidePanelHeader> = {
|
||||
title: 'Modules/CommandMenu/SidePanelHeader',
|
||||
component: SidePanelHeader,
|
||||
args: {
|
||||
onTitleChange: fn(),
|
||||
},
|
||||
argTypes: {},
|
||||
decorators: [ComponentDecorator],
|
||||
parameters: {
|
||||
disableHotkeyInitialization: true,
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof SidePanelHeader>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
headerType: 'Action',
|
||||
iconColor: THEME_LIGHT.font.color.tertiary,
|
||||
initialTitle: 'Create Record',
|
||||
Icon: IconPlus,
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
expect(await canvas.findByText('Create Record')).toBeVisible();
|
||||
expect(await canvas.findByText('Action')).toBeVisible();
|
||||
},
|
||||
};
|
||||
|
||||
export const EditableTitle: Story = {
|
||||
args: {
|
||||
headerType: 'Action',
|
||||
iconColor: THEME_LIGHT.font.color.tertiary,
|
||||
initialTitle: 'Create Record',
|
||||
Icon: IconPlus,
|
||||
onTitleChange: fn(),
|
||||
},
|
||||
play: async ({ canvasElement, args }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const titleText = await canvas.findByText('Create Record');
|
||||
await userEvent.click(titleText);
|
||||
|
||||
const titleInput = await canvas.findByDisplayValue('Create Record');
|
||||
|
||||
const NEW_TITLE = 'New Title';
|
||||
|
||||
await userEvent.clear(titleInput);
|
||||
await userEvent.type(titleInput, NEW_TITLE);
|
||||
|
||||
await userEvent.keyboard('{Enter}');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(args.onTitleChange).toHaveBeenCalledWith(NEW_TITLE);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export const Disabled: Story = {
|
||||
args: {
|
||||
headerType: 'Action',
|
||||
iconColor: THEME_LIGHT.font.color.tertiary,
|
||||
initialTitle: 'Create Record',
|
||||
Icon: IconPlus,
|
||||
disabled: true,
|
||||
onTitleChange: fn(),
|
||||
},
|
||||
play: async ({ canvasElement, args }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
const titleText = await canvas.findByText('Create Record');
|
||||
|
||||
expect(window.getComputedStyle(titleText).cursor).toBe('default');
|
||||
|
||||
await userEvent.click(titleText);
|
||||
|
||||
const titleInput = canvas.queryByDisplayValue('Create Record');
|
||||
expect(titleInput).not.toBeInTheDocument();
|
||||
|
||||
expect(args.onTitleChange).not.toHaveBeenCalled();
|
||||
},
|
||||
};
|
||||
+42
@@ -12,7 +12,10 @@ import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelect
|
||||
import { isCommandMenuClosingState } from '@/command-menu/states/isCommandMenuClosingState';
|
||||
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
|
||||
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { pageLayoutEditingWidgetIdComponentState } from '@/page-layout/states/pageLayoutEditingWidgetIdComponentState';
|
||||
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
||||
import { emitSidePanelCloseEvent } from '@/ui/layout/right-drawer/utils/emitSidePanelCloseEvent';
|
||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||
@@ -21,6 +24,7 @@ import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTab
|
||||
import { WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/workflow-actions/code-action/constants/WorkflowServerlessFunctionTabListComponentId';
|
||||
import { WorkflowServerlessFunctionTabId } from '@/workflow/workflow-steps/workflow-actions/code-action/types/WorkflowServerlessFunctionTabId';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const useCommandMenuCloseAnimationCompleteCleanup = () => {
|
||||
const { resetSelectedItem } = useSelectableList('command-menu-list');
|
||||
@@ -37,6 +41,44 @@ export const useCommandMenuCloseAnimationCompleteCleanup = () => {
|
||||
resetContextStoreStates(COMMAND_MENU_COMPONENT_INSTANCE_ID);
|
||||
resetContextStoreStates(COMMAND_MENU_PREVIOUS_COMPONENT_INSTANCE_ID);
|
||||
|
||||
const currentPage = snapshot
|
||||
.getLoadable(commandMenuPageState)
|
||||
.getValue();
|
||||
|
||||
const isPageLayoutEditingPage =
|
||||
currentPage === CommandMenuPages.PageLayoutWidgetTypeSelect ||
|
||||
currentPage === CommandMenuPages.PageLayoutGraphTypeSelect ||
|
||||
currentPage === CommandMenuPages.PageLayoutIframeConfig;
|
||||
|
||||
if (isPageLayoutEditingPage) {
|
||||
const targetedRecordsRule = snapshot
|
||||
.getLoadable(
|
||||
contextStoreTargetedRecordsRuleComponentState.atomFamily({
|
||||
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
|
||||
}),
|
||||
)
|
||||
.getValue();
|
||||
|
||||
if (
|
||||
targetedRecordsRule.mode === 'selection' &&
|
||||
targetedRecordsRule.selectedRecordIds.length === 1
|
||||
) {
|
||||
const recordId = targetedRecordsRule.selectedRecordIds[0];
|
||||
const record = snapshot
|
||||
.getLoadable(recordStoreFamilyState(recordId))
|
||||
.getValue();
|
||||
|
||||
if (isDefined(record) && isDefined(record.pageLayoutId)) {
|
||||
set(
|
||||
pageLayoutEditingWidgetIdComponentState.atomFamily({
|
||||
instanceId: record.pageLayoutId,
|
||||
}),
|
||||
null,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set(viewableRecordIdState, null);
|
||||
set(commandMenuPageState, CommandMenuPages.Root);
|
||||
set(commandMenuPageInfoState, {
|
||||
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
import { CommandGroup } from '@/command-menu/components/CommandGroup';
|
||||
import { CommandMenuItemDropdown } from '@/command-menu/components/CommandMenuItemDropdown';
|
||||
import { CommandMenuItemToggle } from '@/command-menu/components/CommandMenuItemToggle';
|
||||
import { CommandMenuList } from '@/command-menu/components/CommandMenuList';
|
||||
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 { 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';
|
||||
import {
|
||||
CHART_CONFIGURATION_SETTING_IDS,
|
||||
CHART_CONFIGURATION_SETTING_TO_CONFIG_KEY_MAP,
|
||||
} from '@/command-menu/pages/page-layout/types/ChartConfigurationSettingIds';
|
||||
import { getChartSettingsDropdownContent } from '@/command-menu/pages/page-layout/utils/getChartSettingsDropdownContent';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useOpenDropdown } from '@/ui/layout/dropdown/hooks/useOpenDropdown';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
import { type GraphType, type PageLayoutWidget } from '~/generated/graphql';
|
||||
|
||||
export const ChartSettings = ({ widget }: { widget: PageLayoutWidget }) => {
|
||||
const { updateCommandMenuPageInfo } = useUpdateCommandMenuPageInfo();
|
||||
|
||||
const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord();
|
||||
|
||||
const { updateCurrentWidgetConfig } =
|
||||
useUpdateCurrentWidgetConfig(pageLayoutId);
|
||||
|
||||
const { openDropdown } = useOpenDropdown();
|
||||
|
||||
if (widget.configuration?.__typename === 'IframeConfiguration') {
|
||||
throw new Error(t`IframeConfiguration is not supported`);
|
||||
}
|
||||
|
||||
const configuration = widget.configuration as ChartConfiguration;
|
||||
|
||||
const { getChartSettingsValues } = useChartSettingsValues({
|
||||
objectMetadataId: widget.objectMetadataId,
|
||||
configuration,
|
||||
});
|
||||
|
||||
const currentGraphType = configuration?.graphType;
|
||||
|
||||
const handleGraphTypeChange = (graphType: GraphType) => {
|
||||
updateCurrentWidgetConfig({
|
||||
configToUpdate: {
|
||||
__typename: GRAPH_TYPE_TO_CONFIG_TYPENAME[graphType],
|
||||
graphType,
|
||||
},
|
||||
});
|
||||
|
||||
updateCommandMenuPageInfo({
|
||||
pageIcon: GRAPH_TYPE_INFORMATION[graphType].icon,
|
||||
});
|
||||
};
|
||||
|
||||
const chartSettings = GRAPH_TYPE_INFORMATION[currentGraphType].settings;
|
||||
|
||||
return (
|
||||
<CommandMenuList
|
||||
commandGroups={[]}
|
||||
selectableItemIds={[
|
||||
...chartSettings.flatMap((group) => group.items.map((item) => item.id)),
|
||||
]}
|
||||
>
|
||||
<ChartTypeSelectionSection
|
||||
currentGraphType={currentGraphType}
|
||||
setCurrentGraphType={handleGraphTypeChange}
|
||||
/>
|
||||
{chartSettings.map((group) => (
|
||||
<CommandGroup key={group.heading} heading={group.heading}>
|
||||
{group.items.map((item) => {
|
||||
const isDisabled =
|
||||
!isNonEmptyString(widget.objectMetadataId) &&
|
||||
(item?.dependsOn?.includes(
|
||||
CHART_CONFIGURATION_SETTING_IDS.SOURCE,
|
||||
) ??
|
||||
false);
|
||||
|
||||
const handleToggleChange = () => {
|
||||
const configKey =
|
||||
item.id === CHART_CONFIGURATION_SETTING_IDS.DATA_LABELS
|
||||
? CHART_CONFIGURATION_SETTING_TO_CONFIG_KEY_MAP[
|
||||
CHART_CONFIGURATION_SETTING_IDS.DATA_LABELS
|
||||
]
|
||||
: item.id;
|
||||
|
||||
updateCurrentWidgetConfig({
|
||||
configToUpdate: {
|
||||
[configKey]: !getChartSettingsValues(item.id),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleDropdownOpen = () => {
|
||||
openDropdown({
|
||||
dropdownComponentInstanceIdFromProps: item.id,
|
||||
});
|
||||
};
|
||||
|
||||
return item.isBoolean ? (
|
||||
<SelectableListItem
|
||||
key={item.id}
|
||||
itemId={item.id}
|
||||
onEnter={isDisabled ? undefined : handleToggleChange}
|
||||
>
|
||||
<CommandMenuItemToggle
|
||||
LeftIcon={item.Icon}
|
||||
text={t(item.label)}
|
||||
id={item.id}
|
||||
toggled={getChartSettingsValues(item.id) as boolean}
|
||||
onToggleChange={handleToggleChange}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
) : (
|
||||
<SelectableListItem
|
||||
key={item.id}
|
||||
itemId={item.id}
|
||||
onEnter={isDisabled ? undefined : handleDropdownOpen}
|
||||
>
|
||||
<CommandMenuItemDropdown
|
||||
Icon={item.Icon}
|
||||
label={t(item.label)}
|
||||
id={item.id}
|
||||
dropdownId={item.id}
|
||||
dropdownComponents={
|
||||
<DropdownContent>
|
||||
<DropdownMenuItemsContainer>
|
||||
{getChartSettingsDropdownContent(item.id)}
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
dropdownPlacement="bottom-end"
|
||||
description={getChartSettingsValues(item.id) as string}
|
||||
contextualTextPosition={'right'}
|
||||
hasSubMenu
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
);
|
||||
})}
|
||||
</CommandGroup>
|
||||
))}
|
||||
</CommandMenuList>
|
||||
);
|
||||
};
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
import { GRAPH_TYPE_INFORMATION } from '@/command-menu/pages/page-layout/constants/GraphTypeInformation';
|
||||
import styled from '@emotion/styled';
|
||||
import { GraphType } from '~/generated-metadata/graphql';
|
||||
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { MenuPicker } from 'twenty-ui/navigation';
|
||||
|
||||
const graphTypeOptions = [
|
||||
GraphType.BAR,
|
||||
GraphType.PIE,
|
||||
GraphType.LINE,
|
||||
GraphType.NUMBER,
|
||||
GraphType.GAUGE,
|
||||
];
|
||||
|
||||
const StyledChartTypeSelectionContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
type ChartTypeSelectionSectionProps = {
|
||||
currentGraphType: GraphType;
|
||||
setCurrentGraphType: (graphType: GraphType) => void;
|
||||
};
|
||||
|
||||
export const ChartTypeSelectionSection = ({
|
||||
currentGraphType,
|
||||
setCurrentGraphType,
|
||||
}: ChartTypeSelectionSectionProps) => {
|
||||
return (
|
||||
<StyledChartTypeSelectionContainer>
|
||||
{graphTypeOptions.map((graphType) => (
|
||||
<MenuPicker
|
||||
selected={currentGraphType === graphType}
|
||||
key={graphType}
|
||||
icon={GRAPH_TYPE_INFORMATION[graphType].icon}
|
||||
onClick={() => {
|
||||
setCurrentGraphType(graphType);
|
||||
}}
|
||||
label={t(GRAPH_TYPE_INFORMATION[graphType].label)}
|
||||
showLabel={false}
|
||||
/>
|
||||
))}
|
||||
</StyledChartTypeSelectionContainer>
|
||||
);
|
||||
};
|
||||
+42
-76
@@ -1,87 +1,53 @@
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { SidePanelHeader } from '@/command-menu/components/SidePanelHeader';
|
||||
import { ChartSettings } from '@/command-menu/pages/page-layout/components/ChartSettings';
|
||||
import { GRAPH_TYPE_INFORMATION } from '@/command-menu/pages/page-layout/constants/GraphTypeInformation';
|
||||
import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord';
|
||||
import { useCreatePageLayoutGraphWidget } from '@/page-layout/hooks/useCreatePageLayoutGraphWidget';
|
||||
import { GraphType, WidgetType } from '~/generated-metadata/graphql';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import {
|
||||
IconChartBar,
|
||||
IconChartLine,
|
||||
IconChartPie,
|
||||
IconGauge,
|
||||
IconNumber,
|
||||
} from 'twenty-ui/display';
|
||||
import { MenuItem } from 'twenty-ui/navigation';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: ${({ theme }) => theme.spacing(1)} ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledSectionTitle = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
font-size: ${({ theme }) => theme.font.size.sm};
|
||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||
padding-top: ${({ theme }) => theme.spacing(2)};
|
||||
padding-bottom: ${({ theme }) => theme.spacing(1)};
|
||||
padding-left: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
const graphTypeOptions = [
|
||||
{
|
||||
type: GraphType.BAR,
|
||||
icon: IconChartBar,
|
||||
title: 'Bar Chart',
|
||||
},
|
||||
{
|
||||
type: GraphType.PIE,
|
||||
icon: IconChartPie,
|
||||
title: 'Pie Chart',
|
||||
},
|
||||
{
|
||||
type: GraphType.GAUGE,
|
||||
icon: IconGauge,
|
||||
title: 'Gauge',
|
||||
},
|
||||
{
|
||||
type: GraphType.NUMBER,
|
||||
icon: IconNumber,
|
||||
title: 'Number',
|
||||
},
|
||||
{
|
||||
type: GraphType.LINE,
|
||||
icon: IconChartLine,
|
||||
title: 'Line Chart',
|
||||
},
|
||||
];
|
||||
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 CommandMenuPageLayoutGraphTypeSelect = () => {
|
||||
const { closeCommandMenu } = useCommandMenu();
|
||||
|
||||
const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord();
|
||||
|
||||
const { createPageLayoutWidget } =
|
||||
useCreatePageLayoutGraphWidget(pageLayoutId);
|
||||
const draftPageLayout = useRecoilComponentValue(
|
||||
pageLayoutDraftComponentState,
|
||||
pageLayoutId,
|
||||
);
|
||||
|
||||
const handleSelectGraphType = (graphType: GraphType) => {
|
||||
createPageLayoutWidget(WidgetType.GRAPH, graphType);
|
||||
closeCommandMenu();
|
||||
};
|
||||
const pageLayoutEditingWidgetId = useRecoilComponentValue(
|
||||
pageLayoutEditingWidgetIdComponentState,
|
||||
pageLayoutId,
|
||||
);
|
||||
|
||||
const widgetInEditMode = draftPageLayout.tabs
|
||||
.flatMap((tab) => tab.widgets)
|
||||
.find((widget) => widget.id === pageLayoutEditingWidgetId);
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
if (
|
||||
!isDefined(widgetInEditMode?.configuration) ||
|
||||
!('graphType' in widgetInEditMode.configuration)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const currentGraphType = widgetInEditMode.configuration.graphType;
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledSectionTitle>Graph type</StyledSectionTitle>
|
||||
<>
|
||||
<SidePanelHeader
|
||||
Icon={GRAPH_TYPE_INFORMATION[currentGraphType].icon}
|
||||
iconColor={theme.font.color.tertiary}
|
||||
initialTitle={t`Chart`}
|
||||
headerType={t(GRAPH_TYPE_INFORMATION[currentGraphType].label)}
|
||||
onTitleChange={() => {}}
|
||||
/>
|
||||
|
||||
{graphTypeOptions.map((option) => (
|
||||
<MenuItem
|
||||
withIconContainer={true}
|
||||
key={option.type}
|
||||
LeftIcon={option.icon}
|
||||
text={option.title}
|
||||
onClick={() => handleSelectGraphType(option.type)}
|
||||
/>
|
||||
))}
|
||||
</StyledContainer>
|
||||
<ChartSettings widget={widgetInEditMode} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+4
-3
@@ -8,6 +8,7 @@ import { pageLayoutEditingWidgetIdComponentState } from '@/page-layout/states/pa
|
||||
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import styled from '@emotion/styled';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isString } from '@sniptt/guards';
|
||||
import { useState } from 'react';
|
||||
import { isValidUrl } from 'twenty-shared/utils';
|
||||
@@ -119,12 +120,12 @@ export const CommandMenuPageLayoutIframeConfig = () => {
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledSectionTitle>
|
||||
{isEditMode ? 'Edit iFrame Widget' : 'Configure iFrame Widget'}
|
||||
{isEditMode ? t`Edit iFrame Widget` : t`Configure iFrame Widget`}
|
||||
</StyledSectionTitle>
|
||||
|
||||
<FormTextFieldInput
|
||||
label="Widget Title"
|
||||
placeholder="e.g., Analytics Dashboard"
|
||||
label={t`Widget Title`}
|
||||
placeholder={t`e.g., Analytics Dashboard`}
|
||||
defaultValue={title}
|
||||
onChange={setTitle}
|
||||
/>
|
||||
|
||||
+60
-91
@@ -1,109 +1,78 @@
|
||||
import { CommandGroup } from '@/command-menu/components/CommandGroup';
|
||||
import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem';
|
||||
import { CommandMenuList } from '@/command-menu/components/CommandMenuList';
|
||||
import { useNavigatePageLayoutCommandMenu } from '@/command-menu/pages/page-layout/hooks/useNavigatePageLayoutCommandMenu';
|
||||
import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord';
|
||||
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
|
||||
import { pageLayoutDraggedAreaComponentState } from '@/page-layout/states/pageLayoutDraggedAreaComponentState';
|
||||
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
|
||||
import styled from '@emotion/styled';
|
||||
import { IconChartPie, IconFrame, IconList } from 'twenty-ui/display';
|
||||
import { MenuItem } from 'twenty-ui/navigation';
|
||||
import { WidgetType } from '~/generated-metadata/graphql';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: ${({ theme }) => theme.spacing(1)} ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledSectionTitle = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
font-size: ${({ theme }) => theme.font.size.sm};
|
||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||
padding-top: ${({ theme }) => theme.spacing(2)};
|
||||
padding-bottom: ${({ theme }) => theme.spacing(1)};
|
||||
padding-left: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
const StyledDisabledMenuItem = styled.div`
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
pointer-events: none;
|
||||
`;
|
||||
|
||||
const widgetTypeOptions = [
|
||||
{
|
||||
type: WidgetType.GRAPH,
|
||||
icon: IconChartPie,
|
||||
title: 'Add a graph',
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
type: WidgetType.VIEW,
|
||||
icon: IconList,
|
||||
title: 'Add a view',
|
||||
disabled: true,
|
||||
},
|
||||
{
|
||||
type: WidgetType.IFRAME,
|
||||
icon: IconFrame,
|
||||
title: 'Add an iframe',
|
||||
disabled: false,
|
||||
},
|
||||
];
|
||||
import { useCreatePageLayoutGraphWidget } from '@/page-layout/hooks/useCreatePageLayoutGraphWidget';
|
||||
import { pageLayoutEditingWidgetIdComponentState } from '@/page-layout/states/pageLayoutEditingWidgetIdComponentState';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IconChartPie, IconFrame } from 'twenty-ui/display';
|
||||
import { GraphType } from '~/generated-metadata/graphql';
|
||||
|
||||
export const CommandMenuPageLayoutWidgetTypeSelect = () => {
|
||||
const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord();
|
||||
|
||||
const setPageLayoutDraggedArea = useSetRecoilComponentState(
|
||||
pageLayoutDraggedAreaComponentState,
|
||||
pageLayoutId,
|
||||
);
|
||||
|
||||
const { navigatePageLayoutCommandMenu } = useNavigatePageLayoutCommandMenu();
|
||||
|
||||
const handleSelectWidget = (widgetType: WidgetType) => {
|
||||
switch (widgetType) {
|
||||
case WidgetType.GRAPH: {
|
||||
navigatePageLayoutCommandMenu({
|
||||
commandMenuPage: CommandMenuPages.PageLayoutGraphTypeSelect,
|
||||
});
|
||||
const { createPageLayoutGraphWidget } =
|
||||
useCreatePageLayoutGraphWidget(pageLayoutId);
|
||||
|
||||
break;
|
||||
}
|
||||
case WidgetType.IFRAME: {
|
||||
navigatePageLayoutCommandMenu({
|
||||
commandMenuPage: CommandMenuPages.PageLayoutIframeConfig,
|
||||
});
|
||||
const [pageLayoutEditingWidgetId, setPageLayoutEditingWidgetId] =
|
||||
useRecoilComponentState(
|
||||
pageLayoutEditingWidgetIdComponentState,
|
||||
pageLayoutId,
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
setPageLayoutDraggedArea(null);
|
||||
break;
|
||||
const handleNavigateToGraphTypeSelect = () => {
|
||||
if (!isDefined(pageLayoutEditingWidgetId)) {
|
||||
const newWidget = createPageLayoutGraphWidget(GraphType.BAR);
|
||||
|
||||
setPageLayoutEditingWidgetId(newWidget.id);
|
||||
}
|
||||
|
||||
navigatePageLayoutCommandMenu({
|
||||
commandMenuPage: CommandMenuPages.PageLayoutGraphTypeSelect,
|
||||
});
|
||||
};
|
||||
|
||||
const handleNavigateToIframeConfig = () => {
|
||||
navigatePageLayoutCommandMenu({
|
||||
commandMenuPage: CommandMenuPages.PageLayoutIframeConfig,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledSectionTitle>Widget type</StyledSectionTitle>
|
||||
{widgetTypeOptions.map((option) => {
|
||||
const MenuItemComponent = (
|
||||
<MenuItem
|
||||
withIconContainer={true}
|
||||
key={option.type}
|
||||
LeftIcon={option.icon}
|
||||
text={option.title}
|
||||
onClick={() => handleSelectWidget(option.type)}
|
||||
<CommandMenuList commandGroups={[]} selectableItemIds={['chart', 'iframe']}>
|
||||
<CommandGroup heading={t`Widget type`}>
|
||||
<SelectableListItem
|
||||
itemId="chart"
|
||||
onEnter={handleNavigateToGraphTypeSelect}
|
||||
>
|
||||
<CommandMenuItem
|
||||
Icon={IconChartPie}
|
||||
label={t`Chart`}
|
||||
id="chart"
|
||||
onClick={handleNavigateToGraphTypeSelect}
|
||||
/>
|
||||
);
|
||||
|
||||
return option.disabled ? (
|
||||
<StyledDisabledMenuItem key={option.type}>
|
||||
{MenuItemComponent}
|
||||
</StyledDisabledMenuItem>
|
||||
) : (
|
||||
MenuItemComponent
|
||||
);
|
||||
})}
|
||||
</StyledContainer>
|
||||
</SelectableListItem>
|
||||
<SelectableListItem
|
||||
itemId="iframe"
|
||||
onEnter={() => {
|
||||
handleNavigateToIframeConfig();
|
||||
}}
|
||||
>
|
||||
<CommandMenuItem
|
||||
Icon={IconFrame}
|
||||
label="iFrame"
|
||||
id="iframe"
|
||||
onClick={handleNavigateToIframeConfig}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
</CommandGroup>
|
||||
</CommandMenuList>
|
||||
);
|
||||
};
|
||||
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord';
|
||||
import { useUpdateCurrentWidgetConfig } from '@/command-menu/pages/page-layout/hooks/useUpdateCurrentWidgetConfig';
|
||||
import { useWidgetInEditMode } from '@/command-menu/pages/page-layout/hooks/useWidgetInEditMode';
|
||||
import { mapToGraphQLExtendedAggregateOperation } from '@/command-menu/pages/page-layout/utils/mapToGraphQLExtendedAggregateOperation';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { getAggregateOperationLabel } from '@/object-record/record-board/record-board-column/utils/getAggregateOperationLabel';
|
||||
import { getAvailableAggregateOperationsForFieldMetadataType } from '@/object-record/record-table/record-table-footer/utils/getAvailableAggregateOperationsForFieldMetadataType';
|
||||
import { type ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations';
|
||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
|
||||
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponentInstanceContext';
|
||||
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
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 { t } from '@lingui/core/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { useState } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IconChevronLeft } from 'twenty-ui/display';
|
||||
import { MenuItemSelect } from 'twenty-ui/navigation';
|
||||
import { filterBySearchQuery } from '~/utils/filterBySearchQuery';
|
||||
|
||||
export const ChartAggregateOperationSelectionDropdownContent = ({
|
||||
currentFieldMetadataId,
|
||||
setIsSubMenuOpen,
|
||||
}: {
|
||||
currentFieldMetadataId: string;
|
||||
setIsSubMenuOpen: (isSubMenuOpen: boolean) => void;
|
||||
}) => {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord();
|
||||
const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId);
|
||||
|
||||
if (
|
||||
widgetInEditMode?.configuration?.__typename !== 'BarChartConfiguration' &&
|
||||
widgetInEditMode?.configuration?.__typename !== 'LineChartConfiguration'
|
||||
) {
|
||||
throw new Error('Invalid configuration type');
|
||||
}
|
||||
|
||||
const currentAggregateOperation =
|
||||
widgetInEditMode.configuration.aggregateOperation;
|
||||
|
||||
const sourceObjectMetadataItem = objectMetadataItems.find(
|
||||
(item) => item.id === widgetInEditMode.objectMetadataId,
|
||||
);
|
||||
|
||||
const selectedField = sourceObjectMetadataItem?.fields.find(
|
||||
(field) => field.id === currentFieldMetadataId,
|
||||
);
|
||||
|
||||
const dropdownId = useAvailableComponentInstanceIdOrThrow(
|
||||
DropdownComponentInstanceContext,
|
||||
);
|
||||
|
||||
const selectedItemId = useRecoilComponentValue(
|
||||
selectedItemIdComponentState,
|
||||
dropdownId,
|
||||
);
|
||||
|
||||
const availableAggregateOperations = selectedField
|
||||
? getAvailableAggregateOperationsForFieldMetadataType({
|
||||
fieldMetadataType: selectedField.type,
|
||||
})
|
||||
: [];
|
||||
|
||||
const aggregateOperationsWithLabels = availableAggregateOperations.map(
|
||||
(operation) => ({
|
||||
operation,
|
||||
label: getAggregateOperationLabel(operation),
|
||||
}),
|
||||
);
|
||||
|
||||
const filteredAggregateOperationsWithLabels = filterBySearchQuery({
|
||||
items: aggregateOperationsWithLabels,
|
||||
searchQuery,
|
||||
getSearchableValues: (item) => [item.label],
|
||||
});
|
||||
|
||||
const { updateCurrentWidgetConfig } =
|
||||
useUpdateCurrentWidgetConfig(pageLayoutId);
|
||||
|
||||
const { closeDropdown } = useCloseDropdown();
|
||||
|
||||
if (!isDefined(sourceObjectMetadataItem) || !isDefined(selectedField)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleSelectAggregateOperation = (
|
||||
aggregateOperation: ExtendedAggregateOperations,
|
||||
) => {
|
||||
updateCurrentWidgetConfig({
|
||||
configToUpdate: {
|
||||
aggregateFieldMetadataId: currentFieldMetadataId,
|
||||
aggregateOperation:
|
||||
mapToGraphQLExtendedAggregateOperation(aggregateOperation),
|
||||
},
|
||||
});
|
||||
closeDropdown();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuHeader
|
||||
StartComponent={
|
||||
<DropdownMenuHeaderLeftComponent
|
||||
onClick={() => setIsSubMenuOpen(false)}
|
||||
Icon={IconChevronLeft}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Trans>Y-Axis Aggregate Operation</Trans>
|
||||
</DropdownMenuHeader>
|
||||
<DropdownMenuSearchInput
|
||||
autoFocus
|
||||
type="text"
|
||||
placeholder={t`Search operations`}
|
||||
onChange={(event) => setSearchQuery(event.target.value)}
|
||||
value={searchQuery}
|
||||
/>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer>
|
||||
<SelectableList
|
||||
selectableListInstanceId={dropdownId}
|
||||
focusId={dropdownId}
|
||||
selectableItemIdArray={filteredAggregateOperationsWithLabels.map(
|
||||
(item) => item.operation,
|
||||
)}
|
||||
>
|
||||
{filteredAggregateOperationsWithLabels.map((item) => (
|
||||
<SelectableListItem
|
||||
key={item.operation}
|
||||
itemId={item.operation}
|
||||
onEnter={() => {
|
||||
handleSelectAggregateOperation(item.operation);
|
||||
}}
|
||||
>
|
||||
<MenuItemSelect
|
||||
text={item.label}
|
||||
selected={
|
||||
currentAggregateOperation ===
|
||||
mapToGraphQLExtendedAggregateOperation(item.operation)
|
||||
}
|
||||
focused={selectedItemId === item.operation}
|
||||
onClick={() => {
|
||||
handleSelectAggregateOperation(item.operation);
|
||||
}}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
))}
|
||||
</SelectableList>
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord';
|
||||
import { useUpdateCurrentWidgetConfig } from '@/command-menu/pages/page-layout/hooks/useUpdateCurrentWidgetConfig';
|
||||
import { useWidgetInEditMode } from '@/command-menu/pages/page-layout/hooks/useWidgetInEditMode';
|
||||
import { getChartAxisNameDisplayOptions } from '@/command-menu/pages/page-layout/utils/getChartAxisNameDisplayOptions';
|
||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponentInstanceContext';
|
||||
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
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 { Trans } from '@lingui/react/macro';
|
||||
import { MenuItemSelect } from 'twenty-ui/navigation';
|
||||
import { AxisNameDisplay } from '~/generated/graphql';
|
||||
|
||||
export const ChartAxisNameSelectionDropdownContent = () => {
|
||||
const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord();
|
||||
const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId);
|
||||
|
||||
if (
|
||||
widgetInEditMode?.configuration?.__typename !== 'BarChartConfiguration' &&
|
||||
widgetInEditMode?.configuration?.__typename !== 'LineChartConfiguration'
|
||||
) {
|
||||
throw new Error('Invalid configuration type');
|
||||
}
|
||||
|
||||
const currentAxisNameDisplay = widgetInEditMode.configuration.axisNameDisplay;
|
||||
|
||||
const dropdownId = useAvailableComponentInstanceIdOrThrow(
|
||||
DropdownComponentInstanceContext,
|
||||
);
|
||||
|
||||
const selectedItemId = useRecoilComponentValue(
|
||||
selectedItemIdComponentState,
|
||||
dropdownId,
|
||||
);
|
||||
|
||||
const axisOptions: AxisNameDisplay[] = [
|
||||
AxisNameDisplay.NONE,
|
||||
AxisNameDisplay.X,
|
||||
AxisNameDisplay.Y,
|
||||
AxisNameDisplay.BOTH,
|
||||
];
|
||||
|
||||
const { updateCurrentWidgetConfig } =
|
||||
useUpdateCurrentWidgetConfig(pageLayoutId);
|
||||
|
||||
const { closeDropdown } = useCloseDropdown();
|
||||
|
||||
const handleSelectAxisNameOption = (axisNameOption: AxisNameDisplay) => {
|
||||
updateCurrentWidgetConfig({
|
||||
configToUpdate: {
|
||||
axisNameDisplay: axisNameOption,
|
||||
},
|
||||
});
|
||||
closeDropdown();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuHeader>
|
||||
<Trans>Axis Name</Trans>
|
||||
</DropdownMenuHeader>
|
||||
<DropdownMenuItemsContainer>
|
||||
<SelectableList
|
||||
selectableListInstanceId={dropdownId}
|
||||
focusId={dropdownId}
|
||||
selectableItemIdArray={axisOptions}
|
||||
>
|
||||
{axisOptions.map((option) => (
|
||||
<SelectableListItem
|
||||
key={option}
|
||||
itemId={option}
|
||||
onEnter={() => {
|
||||
handleSelectAxisNameOption(option);
|
||||
}}
|
||||
>
|
||||
<MenuItemSelect
|
||||
text={getChartAxisNameDisplayOptions(option)}
|
||||
selected={currentAxisNameDisplay?.toUpperCase() === option}
|
||||
focused={selectedItemId === option}
|
||||
onClick={() => {
|
||||
handleSelectAxisNameOption(option);
|
||||
}}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
))}
|
||||
</SelectableList>
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord';
|
||||
import { useUpdateCurrentWidgetConfig } from '@/command-menu/pages/page-layout/hooks/useUpdateCurrentWidgetConfig';
|
||||
import { useWidgetInEditMode } from '@/command-menu/pages/page-layout/hooks/useWidgetInEditMode';
|
||||
import { type ChartConfiguration } from '@/command-menu/pages/page-layout/types/ChartConfiguration';
|
||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
||||
import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponentInstanceContext';
|
||||
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
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 { t } from '@lingui/core/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { useState } from 'react';
|
||||
import { capitalize, isDefined } from 'twenty-shared/utils';
|
||||
import { ColorSample } from 'twenty-ui/display';
|
||||
import { MenuItemSelect } from 'twenty-ui/navigation';
|
||||
import { MAIN_COLOR_NAMES, type ThemeColor } from 'twenty-ui/theme';
|
||||
import { filterBySearchQuery } from '~/utils/filterBySearchQuery';
|
||||
|
||||
export const ChartColorSelectionDropdownContent = () => {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord();
|
||||
const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId);
|
||||
|
||||
const dropdownId = useAvailableComponentInstanceIdOrThrow(
|
||||
DropdownComponentInstanceContext,
|
||||
);
|
||||
|
||||
const selectedItemId = useRecoilComponentValue(
|
||||
selectedItemIdComponentState,
|
||||
dropdownId,
|
||||
);
|
||||
|
||||
const { updateCurrentWidgetConfig } =
|
||||
useUpdateCurrentWidgetConfig(pageLayoutId);
|
||||
|
||||
const { closeDropdown } = useCloseDropdown();
|
||||
|
||||
if (!isDefined(widgetInEditMode)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (widgetInEditMode.configuration?.__typename === 'IframeConfiguration') {
|
||||
throw new Error('Invalid configuration type');
|
||||
}
|
||||
|
||||
const configuration = widgetInEditMode.configuration as ChartConfiguration;
|
||||
|
||||
const currentColor = configuration.color;
|
||||
|
||||
const colorOptions = MAIN_COLOR_NAMES.map((colorName) => ({
|
||||
id: colorName,
|
||||
name: capitalize(colorName),
|
||||
colorName: colorName,
|
||||
}));
|
||||
|
||||
const filteredColorOptions = filterBySearchQuery({
|
||||
items: colorOptions,
|
||||
searchQuery,
|
||||
getSearchableValues: (item) => [item.name],
|
||||
});
|
||||
|
||||
const handleSelectColor = (colorName: ThemeColor) => {
|
||||
updateCurrentWidgetConfig({
|
||||
configToUpdate: {
|
||||
color: colorName,
|
||||
},
|
||||
});
|
||||
closeDropdown();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuHeader>
|
||||
<Trans>Color</Trans>
|
||||
</DropdownMenuHeader>
|
||||
<DropdownMenuSearchInput
|
||||
autoFocus
|
||||
type="text"
|
||||
placeholder={t`Search colors`}
|
||||
onChange={(event) => setSearchQuery(event.target.value)}
|
||||
value={searchQuery}
|
||||
/>
|
||||
<DropdownMenuItemsContainer>
|
||||
<SelectableList
|
||||
selectableListInstanceId={dropdownId}
|
||||
focusId={dropdownId}
|
||||
selectableItemIdArray={filteredColorOptions.map(
|
||||
(colorOption) => colorOption.id,
|
||||
)}
|
||||
>
|
||||
{filteredColorOptions.map((colorOption) => (
|
||||
<SelectableListItem
|
||||
key={colorOption.id}
|
||||
itemId={colorOption.id}
|
||||
onEnter={() => {
|
||||
handleSelectColor(colorOption.colorName);
|
||||
}}
|
||||
>
|
||||
<MenuItemSelect
|
||||
text={colorOption.name}
|
||||
selected={currentColor === colorOption.id}
|
||||
focused={selectedItemId === colorOption.id}
|
||||
LeftIcon={() => (
|
||||
<ColorSample colorName={colorOption.colorName} />
|
||||
)}
|
||||
onClick={() => {
|
||||
handleSelectColor(colorOption.colorName);
|
||||
}}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
))}
|
||||
</SelectableList>
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord';
|
||||
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';
|
||||
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponentInstanceContext';
|
||||
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
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 { t } from '@lingui/core/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { useState } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useIcons } from 'twenty-ui/display';
|
||||
import { MenuItemSelect } from 'twenty-ui/navigation';
|
||||
import { filterBySearchQuery } from '~/utils/filterBySearchQuery';
|
||||
|
||||
export const ChartDataSourceDropdownContent = () => {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord();
|
||||
|
||||
const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId);
|
||||
|
||||
const currentSource = widgetInEditMode?.objectMetadataId;
|
||||
|
||||
const dropdownId = useAvailableComponentInstanceIdOrThrow(
|
||||
DropdownComponentInstanceContext,
|
||||
);
|
||||
|
||||
const selectedItemId = useRecoilComponentValue(
|
||||
selectedItemIdComponentState,
|
||||
dropdownId,
|
||||
);
|
||||
|
||||
const objectsWithReadAccess = objectMetadataItems.filter(
|
||||
(objectMetadataItem) => {
|
||||
const objectPermissions =
|
||||
objectPermissionsByObjectMetadataId[objectMetadataItem.id];
|
||||
|
||||
return (
|
||||
isDefined(objectPermissions) && objectPermissions.canReadObjectRecords
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
const availableObjectMetadataItems = filterBySearchQuery({
|
||||
items: objectsWithReadAccess,
|
||||
searchQuery,
|
||||
getSearchableValues: (item) => [item.labelPlural, item.namePlural],
|
||||
});
|
||||
|
||||
const { updateCurrentWidgetConfig } =
|
||||
useUpdateCurrentWidgetConfig(pageLayoutId);
|
||||
|
||||
const { closeDropdown } = useCloseDropdown();
|
||||
|
||||
const { getIcon } = useIcons();
|
||||
|
||||
const handleSelectSource = (objectMetadataId: string) => {
|
||||
updateCurrentWidgetConfig({
|
||||
objectMetadataId,
|
||||
});
|
||||
closeDropdown();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuHeader>
|
||||
<Trans>Source</Trans>
|
||||
</DropdownMenuHeader>
|
||||
<DropdownMenuSearchInput
|
||||
autoFocus
|
||||
type="text"
|
||||
placeholder={t`Search objects`}
|
||||
onChange={(event) => setSearchQuery(event.target.value)}
|
||||
value={searchQuery}
|
||||
/>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer>
|
||||
<SelectableList
|
||||
selectableListInstanceId={dropdownId}
|
||||
focusId={dropdownId}
|
||||
selectableItemIdArray={availableObjectMetadataItems.map(
|
||||
(objectMetadataItem) => objectMetadataItem.id,
|
||||
)}
|
||||
>
|
||||
{availableObjectMetadataItems.map((objectMetadataItem) => (
|
||||
<SelectableListItem
|
||||
key={objectMetadataItem.id}
|
||||
itemId={objectMetadataItem.id}
|
||||
onEnter={() => {
|
||||
handleSelectSource(objectMetadataItem.id);
|
||||
}}
|
||||
>
|
||||
<MenuItemSelect
|
||||
text={objectMetadataItem.labelPlural}
|
||||
selected={currentSource === objectMetadataItem.id}
|
||||
focused={selectedItemId === objectMetadataItem.id}
|
||||
LeftIcon={getIcon(objectMetadataItem.icon)}
|
||||
onClick={() => {
|
||||
handleSelectSource(objectMetadataItem.id);
|
||||
}}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
))}
|
||||
</SelectableList>
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord';
|
||||
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';
|
||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponentInstanceContext';
|
||||
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
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 { t } from '@lingui/core/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { useState } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useIcons } from 'twenty-ui/display';
|
||||
import { MenuItemSelect } from 'twenty-ui/navigation';
|
||||
import { filterBySearchQuery } from '~/utils/filterBySearchQuery';
|
||||
|
||||
export const ChartFieldSelectionDropdownContent = () => {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord();
|
||||
const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId);
|
||||
|
||||
if (
|
||||
widgetInEditMode?.configuration?.__typename !== 'BarChartConfiguration' &&
|
||||
widgetInEditMode?.configuration?.__typename !== 'LineChartConfiguration'
|
||||
) {
|
||||
throw new Error('Invalid configuration type');
|
||||
}
|
||||
|
||||
const currentXAxisFieldMetadataId =
|
||||
widgetInEditMode.configuration.groupByFieldMetadataIdX;
|
||||
|
||||
const sourceObjectMetadataItem = objectMetadataItems.find(
|
||||
(item) => item.id === widgetInEditMode.objectMetadataId,
|
||||
);
|
||||
|
||||
const dropdownId = useAvailableComponentInstanceIdOrThrow(
|
||||
DropdownComponentInstanceContext,
|
||||
);
|
||||
|
||||
const selectedItemId = useRecoilComponentValue(
|
||||
selectedItemIdComponentState,
|
||||
dropdownId,
|
||||
);
|
||||
|
||||
const availableFieldMetadataItems = filterBySearchQuery({
|
||||
items: sourceObjectMetadataItem?.fields || [],
|
||||
searchQuery,
|
||||
getSearchableValues: (item) => [item.label, item.name],
|
||||
});
|
||||
|
||||
const { updateCurrentWidgetConfig } =
|
||||
useUpdateCurrentWidgetConfig(pageLayoutId);
|
||||
|
||||
const { closeDropdown } = useCloseDropdown();
|
||||
|
||||
const { getIcon } = useIcons();
|
||||
|
||||
if (!isDefined(sourceObjectMetadataItem)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleSelectField = (fieldMetadataId: string) => {
|
||||
updateCurrentWidgetConfig({
|
||||
configToUpdate: {
|
||||
groupByFieldMetadataIdX: fieldMetadataId,
|
||||
},
|
||||
});
|
||||
closeDropdown();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuHeader>
|
||||
<Trans>X-Axis Field</Trans>
|
||||
</DropdownMenuHeader>
|
||||
<DropdownMenuSearchInput
|
||||
autoFocus
|
||||
type="text"
|
||||
placeholder={t`Search fields`}
|
||||
onChange={(event) => setSearchQuery(event.target.value)}
|
||||
value={searchQuery}
|
||||
/>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer>
|
||||
<SelectableList
|
||||
selectableListInstanceId={dropdownId}
|
||||
focusId={dropdownId}
|
||||
selectableItemIdArray={availableFieldMetadataItems.map(
|
||||
(item) => item.id,
|
||||
)}
|
||||
>
|
||||
{availableFieldMetadataItems.map((fieldMetadataItem) => (
|
||||
<SelectableListItem
|
||||
key={fieldMetadataItem.id}
|
||||
itemId={fieldMetadataItem.id}
|
||||
onEnter={() => {
|
||||
handleSelectField(fieldMetadataItem.id);
|
||||
}}
|
||||
>
|
||||
<MenuItemSelect
|
||||
text={fieldMetadataItem.label}
|
||||
selected={currentXAxisFieldMetadataId === fieldMetadataItem.id}
|
||||
focused={selectedItemId === fieldMetadataItem.id}
|
||||
LeftIcon={getIcon(fieldMetadataItem.icon)}
|
||||
onClick={() => {
|
||||
handleSelectField(fieldMetadataItem.id);
|
||||
}}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
))}
|
||||
</SelectableList>
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
import { ChartAggregateOperationSelectionDropdownContent } from '@/command-menu/pages/page-layout/components/dropdown-content/ChartAggregateOperationSelectionDropdownContent';
|
||||
import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord';
|
||||
import { useWidgetInEditMode } from '@/command-menu/pages/page-layout/hooks/useWidgetInEditMode';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponentInstanceContext';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
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 { t } from '@lingui/core/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { useState } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useIcons } from 'twenty-ui/display';
|
||||
import { MenuItemSelect } from 'twenty-ui/navigation';
|
||||
import { filterBySearchQuery } from '~/utils/filterBySearchQuery';
|
||||
|
||||
export const ChartFieldSelectionForAggregateOperationDropdownContent = () => {
|
||||
const [isSubMenuOpen, setIsSubMenuOpen] = useState(false);
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord();
|
||||
const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId);
|
||||
|
||||
if (
|
||||
widgetInEditMode?.configuration?.__typename !== 'BarChartConfiguration' &&
|
||||
widgetInEditMode?.configuration?.__typename !== 'LineChartConfiguration'
|
||||
) {
|
||||
throw new Error('Invalid configuration type');
|
||||
}
|
||||
|
||||
const currentFieldMetadataId =
|
||||
widgetInEditMode.configuration.groupByFieldMetadataIdY;
|
||||
|
||||
const [selectedFieldMetadataId, setSelectedFieldMetadataId] = useState(
|
||||
currentFieldMetadataId,
|
||||
);
|
||||
|
||||
const sourceObjectMetadataItem = objectMetadataItems.find(
|
||||
(item) => item.id === widgetInEditMode.objectMetadataId,
|
||||
);
|
||||
|
||||
const dropdownId = useAvailableComponentInstanceIdOrThrow(
|
||||
DropdownComponentInstanceContext,
|
||||
);
|
||||
|
||||
const selectedItemId = useRecoilComponentValue(
|
||||
selectedItemIdComponentState,
|
||||
dropdownId,
|
||||
);
|
||||
|
||||
const availableFieldMetadataItems = filterBySearchQuery({
|
||||
items: sourceObjectMetadataItem?.fields || [],
|
||||
searchQuery,
|
||||
getSearchableValues: (item) => [item.label, item.name],
|
||||
});
|
||||
|
||||
const { getIcon } = useIcons();
|
||||
|
||||
if (!isDefined(sourceObjectMetadataItem)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isSubMenuOpen) {
|
||||
return (
|
||||
<ChartAggregateOperationSelectionDropdownContent
|
||||
currentFieldMetadataId={selectedFieldMetadataId}
|
||||
setIsSubMenuOpen={setIsSubMenuOpen}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuHeader>
|
||||
<Trans>Y-Axis Field</Trans>
|
||||
</DropdownMenuHeader>
|
||||
<DropdownMenuSearchInput
|
||||
autoFocus
|
||||
type="text"
|
||||
placeholder={t`Search fields`}
|
||||
onChange={(event) => setSearchQuery(event.target.value)}
|
||||
value={searchQuery}
|
||||
/>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer>
|
||||
<SelectableList
|
||||
selectableListInstanceId={dropdownId}
|
||||
focusId={dropdownId}
|
||||
selectableItemIdArray={availableFieldMetadataItems.map(
|
||||
(item) => item.id,
|
||||
)}
|
||||
>
|
||||
{availableFieldMetadataItems.map((fieldMetadataItem) => (
|
||||
<SelectableListItem
|
||||
key={fieldMetadataItem.id}
|
||||
itemId={fieldMetadataItem.id}
|
||||
onEnter={() => {
|
||||
setIsSubMenuOpen(true);
|
||||
setSelectedFieldMetadataId(fieldMetadataItem.id);
|
||||
}}
|
||||
>
|
||||
<MenuItemSelect
|
||||
text={fieldMetadataItem.label}
|
||||
selected={selectedFieldMetadataId === fieldMetadataItem.id}
|
||||
focused={selectedItemId === fieldMetadataItem.id}
|
||||
LeftIcon={getIcon(fieldMetadataItem.icon)}
|
||||
hasSubMenu={true}
|
||||
onClick={() => {
|
||||
setIsSubMenuOpen(true);
|
||||
setSelectedFieldMetadataId(fieldMetadataItem.id);
|
||||
}}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
))}
|
||||
</SelectableList>
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord';
|
||||
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';
|
||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponentInstanceContext';
|
||||
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
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 { t } from '@lingui/core/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { useState } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useIcons } from 'twenty-ui/display';
|
||||
import { MenuItemSelect } from 'twenty-ui/navigation';
|
||||
import { filterBySearchQuery } from '~/utils/filterBySearchQuery';
|
||||
|
||||
export const ChartGroupByFieldSelectionDropdownContent = () => {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord();
|
||||
const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId);
|
||||
|
||||
if (
|
||||
widgetInEditMode?.configuration?.__typename !== 'BarChartConfiguration' &&
|
||||
widgetInEditMode?.configuration?.__typename !== 'LineChartConfiguration'
|
||||
) {
|
||||
throw new Error('Invalid configuration type');
|
||||
}
|
||||
|
||||
const currentGroupByFieldMetadataId =
|
||||
widgetInEditMode.configuration.groupByFieldMetadataIdY;
|
||||
|
||||
const sourceObjectMetadataItem = objectMetadataItems.find(
|
||||
(item) => item.id === widgetInEditMode.objectMetadataId,
|
||||
);
|
||||
|
||||
const dropdownId = useAvailableComponentInstanceIdOrThrow(
|
||||
DropdownComponentInstanceContext,
|
||||
);
|
||||
|
||||
const selectedItemId = useRecoilComponentValue(
|
||||
selectedItemIdComponentState,
|
||||
dropdownId,
|
||||
);
|
||||
|
||||
const availableFieldMetadataItems = filterBySearchQuery({
|
||||
items: sourceObjectMetadataItem?.fields || [],
|
||||
searchQuery,
|
||||
getSearchableValues: (item) => [item.label, item.name],
|
||||
});
|
||||
|
||||
const { updateCurrentWidgetConfig } =
|
||||
useUpdateCurrentWidgetConfig(pageLayoutId);
|
||||
|
||||
const { closeDropdown } = useCloseDropdown();
|
||||
|
||||
const { getIcon } = useIcons();
|
||||
|
||||
if (!isDefined(sourceObjectMetadataItem)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleSelectField = (fieldMetadataId: string) => {
|
||||
updateCurrentWidgetConfig({
|
||||
configToUpdate: {
|
||||
groupByFieldMetadataIdY: fieldMetadataId,
|
||||
},
|
||||
});
|
||||
closeDropdown();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuHeader>
|
||||
<Trans>Y-Axis Group By Field</Trans>
|
||||
</DropdownMenuHeader>
|
||||
<DropdownMenuSearchInput
|
||||
autoFocus
|
||||
type="text"
|
||||
placeholder={t`Search fields`}
|
||||
onChange={(event) => setSearchQuery(event.target.value)}
|
||||
value={searchQuery}
|
||||
/>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer>
|
||||
<SelectableList
|
||||
selectableListInstanceId={dropdownId}
|
||||
focusId={dropdownId}
|
||||
selectableItemIdArray={availableFieldMetadataItems.map(
|
||||
(item) => item.id,
|
||||
)}
|
||||
>
|
||||
{availableFieldMetadataItems.map((fieldMetadataItem) => (
|
||||
<SelectableListItem
|
||||
key={fieldMetadataItem.id}
|
||||
itemId={fieldMetadataItem.id}
|
||||
onEnter={() => {
|
||||
handleSelectField(fieldMetadataItem.id);
|
||||
}}
|
||||
>
|
||||
<MenuItemSelect
|
||||
text={fieldMetadataItem.label}
|
||||
selected={
|
||||
currentGroupByFieldMetadataId === fieldMetadataItem.id
|
||||
}
|
||||
focused={selectedItemId === fieldMetadataItem.id}
|
||||
LeftIcon={getIcon(fieldMetadataItem.icon)}
|
||||
onClick={() => {
|
||||
handleSelectField(fieldMetadataItem.id);
|
||||
}}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
))}
|
||||
</SelectableList>
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
import { AGGREGATE_SORT_BY_OPTIONS } from '@/command-menu/pages/page-layout/constants/AggregateSortByOptions';
|
||||
import { useGraphGroupBySortOptionLabels } from '@/command-menu/pages/page-layout/hooks/useGraphGroupBySortOptionLabels';
|
||||
import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord';
|
||||
import { useUpdateCurrentWidgetConfig } from '@/command-menu/pages/page-layout/hooks/useUpdateCurrentWidgetConfig';
|
||||
import { useWidgetInEditMode } from '@/command-menu/pages/page-layout/hooks/useWidgetInEditMode';
|
||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponentInstanceContext';
|
||||
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
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 { MenuItemSelect } from 'twenty-ui/navigation';
|
||||
import {
|
||||
type BarChartConfiguration,
|
||||
type GraphOrderBy,
|
||||
type LineChartConfiguration,
|
||||
type NumberChartConfiguration,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
type ChartSortByGroupByFieldDropdownContentProps = {
|
||||
title: string;
|
||||
};
|
||||
|
||||
export const ChartSortByGroupByFieldDropdownContent = ({
|
||||
title,
|
||||
}: ChartSortByGroupByFieldDropdownContentProps) => {
|
||||
const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord();
|
||||
const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId);
|
||||
|
||||
const configuration = widgetInEditMode?.configuration as
|
||||
| BarChartConfiguration
|
||||
| LineChartConfiguration
|
||||
| NumberChartConfiguration;
|
||||
|
||||
const currentOrderBy =
|
||||
'orderByY' in configuration
|
||||
? configuration.orderByY
|
||||
: 'orderBy' in configuration
|
||||
? configuration.orderBy
|
||||
: undefined;
|
||||
|
||||
const dropdownId = useAvailableComponentInstanceIdOrThrow(
|
||||
DropdownComponentInstanceContext,
|
||||
);
|
||||
|
||||
const selectedItemId = useRecoilComponentValue(
|
||||
selectedItemIdComponentState,
|
||||
dropdownId,
|
||||
);
|
||||
|
||||
const { updateCurrentWidgetConfig } =
|
||||
useUpdateCurrentWidgetConfig(pageLayoutId);
|
||||
|
||||
const { closeDropdown } = useCloseDropdown();
|
||||
|
||||
const orderByKey = 'orderByY' in configuration ? 'orderByY' : 'orderBy';
|
||||
|
||||
const handleSelectSortOption = (orderBy: GraphOrderBy) => {
|
||||
updateCurrentWidgetConfig({
|
||||
configToUpdate: {
|
||||
[orderByKey]: orderBy,
|
||||
},
|
||||
});
|
||||
closeDropdown();
|
||||
};
|
||||
|
||||
const { getGroupBySortOptionLabel } = useGraphGroupBySortOptionLabels({
|
||||
objectMetadataId: widgetInEditMode?.objectMetadataId,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuHeader>{title}</DropdownMenuHeader>
|
||||
<DropdownMenuItemsContainer>
|
||||
<SelectableList
|
||||
selectableListInstanceId={dropdownId}
|
||||
focusId={dropdownId}
|
||||
selectableItemIdArray={AGGREGATE_SORT_BY_OPTIONS.map(
|
||||
(option) => option.value,
|
||||
)}
|
||||
>
|
||||
{AGGREGATE_SORT_BY_OPTIONS.map((sortOption) => (
|
||||
<SelectableListItem
|
||||
key={sortOption.value}
|
||||
itemId={sortOption.value}
|
||||
onEnter={() => {
|
||||
handleSelectSortOption(sortOption.value);
|
||||
}}
|
||||
>
|
||||
<MenuItemSelect
|
||||
text={getGroupBySortOptionLabel({
|
||||
graphOrderBy: sortOption.value,
|
||||
groupByFieldMetadataId:
|
||||
'groupByFieldMetadataIdY' in configuration
|
||||
? configuration.groupByFieldMetadataIdY
|
||||
: 'groupByFieldMetadataId' in configuration
|
||||
? configuration.groupByFieldMetadataId
|
||||
: undefined,
|
||||
})}
|
||||
selected={currentOrderBy === sortOption.value}
|
||||
focused={selectedItemId === sortOption.value}
|
||||
LeftIcon={sortOption.icon}
|
||||
onClick={() => {
|
||||
handleSelectSortOption(sortOption.value);
|
||||
}}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
))}
|
||||
</SelectableList>
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
import { X_SORT_BY_OPTIONS } from '@/command-menu/pages/page-layout/constants/XSortByOptions';
|
||||
import { useGraphXSortOptionLabels } from '@/command-menu/pages/page-layout/hooks/useGraphXSortOptionLabels';
|
||||
import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord';
|
||||
import { useUpdateCurrentWidgetConfig } from '@/command-menu/pages/page-layout/hooks/useUpdateCurrentWidgetConfig';
|
||||
import { useWidgetInEditMode } from '@/command-menu/pages/page-layout/hooks/useWidgetInEditMode';
|
||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponentInstanceContext';
|
||||
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
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 { Trans } from '@lingui/react/macro';
|
||||
|
||||
import { MenuItemSelect } from 'twenty-ui/navigation';
|
||||
import { type GraphOrderBy } from '~/generated/graphql';
|
||||
|
||||
export const ChartXAxisSortBySelectionDropdownContent = () => {
|
||||
const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord();
|
||||
const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId);
|
||||
|
||||
if (
|
||||
widgetInEditMode?.configuration?.__typename !== 'BarChartConfiguration' &&
|
||||
widgetInEditMode?.configuration?.__typename !== 'LineChartConfiguration'
|
||||
) {
|
||||
throw new Error('Invalid configuration type');
|
||||
}
|
||||
|
||||
const configuration = widgetInEditMode?.configuration;
|
||||
const currentOrderByX = configuration.orderByX;
|
||||
|
||||
const dropdownId = useAvailableComponentInstanceIdOrThrow(
|
||||
DropdownComponentInstanceContext,
|
||||
);
|
||||
|
||||
const selectedItemId = useRecoilComponentValue(
|
||||
selectedItemIdComponentState,
|
||||
dropdownId,
|
||||
);
|
||||
|
||||
const { updateCurrentWidgetConfig } =
|
||||
useUpdateCurrentWidgetConfig(pageLayoutId);
|
||||
|
||||
const { closeDropdown } = useCloseDropdown();
|
||||
|
||||
const handleSelectSortOption = (orderByX: GraphOrderBy) => {
|
||||
updateCurrentWidgetConfig({
|
||||
configToUpdate: {
|
||||
orderByX,
|
||||
},
|
||||
});
|
||||
closeDropdown();
|
||||
};
|
||||
|
||||
const { getXSortOptionLabel } = useGraphXSortOptionLabels({
|
||||
objectMetadataId: widgetInEditMode?.objectMetadataId,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuHeader>
|
||||
<Trans>X-Axis Sort By</Trans>
|
||||
</DropdownMenuHeader>
|
||||
<DropdownMenuItemsContainer>
|
||||
<SelectableList
|
||||
selectableListInstanceId={dropdownId}
|
||||
focusId={dropdownId}
|
||||
selectableItemIdArray={X_SORT_BY_OPTIONS.map(
|
||||
(option) => option.value,
|
||||
)}
|
||||
>
|
||||
{X_SORT_BY_OPTIONS.map((sortOption) => (
|
||||
<SelectableListItem
|
||||
key={sortOption.value}
|
||||
itemId={sortOption.value}
|
||||
onEnter={() => {
|
||||
handleSelectSortOption(sortOption.value);
|
||||
}}
|
||||
>
|
||||
<MenuItemSelect
|
||||
text={getXSortOptionLabel({
|
||||
graphOrderBy: sortOption.value,
|
||||
groupByFieldMetadataIdX:
|
||||
configuration.groupByFieldMetadataIdX,
|
||||
aggregateFieldMetadataId:
|
||||
configuration.aggregateFieldMetadataId,
|
||||
aggregateOperation: configuration.aggregateOperation,
|
||||
})}
|
||||
selected={currentOrderByX === sortOption.value}
|
||||
focused={selectedItemId === sortOption.value}
|
||||
LeftIcon={sortOption.icon}
|
||||
onClick={() => {
|
||||
handleSelectSortOption(sortOption.value);
|
||||
}}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
))}
|
||||
</SelectableList>
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import { IconArrowDown, IconArrowUp } from 'twenty-ui/display';
|
||||
import { GraphOrderBy } from '~/generated/graphql';
|
||||
|
||||
export const AGGREGATE_SORT_BY_OPTIONS = [
|
||||
{
|
||||
value: GraphOrderBy.FIELD_ASC,
|
||||
icon: IconArrowUp,
|
||||
},
|
||||
{
|
||||
value: GraphOrderBy.FIELD_DESC,
|
||||
icon: IconArrowDown,
|
||||
},
|
||||
];
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
import { AXIS_NAME_SETTING } from '@/command-menu/pages/page-layout/constants/settings/AxisNameSetting';
|
||||
import { CHART_DATA_SOURCE_SETTING } from '@/command-menu/pages/page-layout/constants/settings/ChartDataSourceSetting';
|
||||
import { COLORS_SETTING } from '@/command-menu/pages/page-layout/constants/settings/ColorsSetting';
|
||||
import { DATA_DISPLAY_X_SETTING } from '@/command-menu/pages/page-layout/constants/settings/DataDisplayXSetting';
|
||||
import { DATA_DISPLAY_Y_SETTING } from '@/command-menu/pages/page-layout/constants/settings/DataDisplayYSetting';
|
||||
import { DATA_LABELS_SETTING } from '@/command-menu/pages/page-layout/constants/settings/DataLabelsSetting';
|
||||
import { FILTER_SETTING } from '@/command-menu/pages/page-layout/constants/settings/FilterSetting';
|
||||
import { GROUP_BY_SETTING } from '@/command-menu/pages/page-layout/constants/settings/GroupBySetting';
|
||||
import { SORT_BY_GROUP_BY_FIELD_SETTING } from '@/command-menu/pages/page-layout/constants/settings/SortByGroupByFieldSetting';
|
||||
import { SORT_BY_X_SETTING } from '@/command-menu/pages/page-layout/constants/settings/SortByXSetting';
|
||||
import { type ChartSettingsGroup } from '@/command-menu/pages/page-layout/types/ChartSettingsGroup';
|
||||
|
||||
export const BAR_CHART_SETTINGS: ChartSettingsGroup[] = [
|
||||
{
|
||||
heading: 'Data',
|
||||
items: [CHART_DATA_SOURCE_SETTING, FILTER_SETTING],
|
||||
},
|
||||
{
|
||||
heading: 'X axis',
|
||||
items: [DATA_DISPLAY_X_SETTING, SORT_BY_X_SETTING],
|
||||
},
|
||||
{
|
||||
heading: 'Y axis',
|
||||
items: [
|
||||
DATA_DISPLAY_Y_SETTING,
|
||||
GROUP_BY_SETTING,
|
||||
SORT_BY_GROUP_BY_FIELD_SETTING,
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: 'Style',
|
||||
items: [COLORS_SETTING, AXIS_NAME_SETTING, DATA_LABELS_SETTING],
|
||||
},
|
||||
];
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import { CHART_DATA_SOURCE_SETTING } from '@/command-menu/pages/page-layout/constants/settings/ChartDataSourceSetting';
|
||||
import { DATA_LABELS_SETTING } from '@/command-menu/pages/page-layout/constants/settings/DataLabelsSetting';
|
||||
import { FILTER_SETTING } from '@/command-menu/pages/page-layout/constants/settings/FilterSetting';
|
||||
import { type ChartSettingsGroup } from '@/command-menu/pages/page-layout/types/ChartSettingsGroup';
|
||||
|
||||
export const GAUGE_CHART_SETTINGS: ChartSettingsGroup[] = [
|
||||
{
|
||||
heading: 'Data',
|
||||
items: [CHART_DATA_SOURCE_SETTING, FILTER_SETTING],
|
||||
},
|
||||
{
|
||||
heading: 'Style',
|
||||
items: [DATA_LABELS_SETTING],
|
||||
},
|
||||
];
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
import { BAR_CHART_SETTINGS } from '@/command-menu/pages/page-layout/constants/BarChartSettings';
|
||||
import { GAUGE_CHART_SETTINGS } from '@/command-menu/pages/page-layout/constants/GaugeChartSettings';
|
||||
import { LINE_CHART_SETTINGS } from '@/command-menu/pages/page-layout/constants/LineChartSettings';
|
||||
import { NUMBER_CHART_SETTINGS } from '@/command-menu/pages/page-layout/constants/NumberChartSettings';
|
||||
import { PIE_CHART_SETTINGS } from '@/command-menu/pages/page-layout/constants/PieChartSettings';
|
||||
import { type ChartSettingsGroup } from '@/command-menu/pages/page-layout/types/ChartSettingsGroup';
|
||||
import { type MessageDescriptor } from '@lingui/core';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import {
|
||||
IconChartBar,
|
||||
IconChartLine,
|
||||
IconChartPie,
|
||||
type IconComponent,
|
||||
IconGauge,
|
||||
IconNumber,
|
||||
} from 'twenty-ui/display';
|
||||
import { GraphType } from '~/generated-metadata/graphql';
|
||||
|
||||
export const GRAPH_TYPE_INFORMATION: Record<
|
||||
GraphType,
|
||||
{
|
||||
label: MessageDescriptor;
|
||||
icon: IconComponent;
|
||||
settings: ChartSettingsGroup[];
|
||||
}
|
||||
> = {
|
||||
[GraphType.BAR]: {
|
||||
label: msg`Bar Chart`,
|
||||
icon: IconChartBar,
|
||||
settings: BAR_CHART_SETTINGS,
|
||||
},
|
||||
[GraphType.PIE]: {
|
||||
label: msg`Pie Chart`,
|
||||
icon: IconChartPie,
|
||||
settings: PIE_CHART_SETTINGS,
|
||||
},
|
||||
[GraphType.LINE]: {
|
||||
label: msg`Line Chart`,
|
||||
icon: IconChartLine,
|
||||
settings: LINE_CHART_SETTINGS,
|
||||
},
|
||||
[GraphType.NUMBER]: {
|
||||
label: msg`Number Chart`,
|
||||
icon: IconNumber,
|
||||
settings: NUMBER_CHART_SETTINGS,
|
||||
},
|
||||
[GraphType.GAUGE]: {
|
||||
label: msg`Gauge Chart`,
|
||||
icon: IconGauge,
|
||||
settings: GAUGE_CHART_SETTINGS,
|
||||
},
|
||||
};
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import { GraphType } from '~/generated-metadata/graphql';
|
||||
|
||||
export const GRAPH_TYPE_TO_CONFIG_TYPENAME = {
|
||||
[GraphType.BAR]: 'BarChartConfiguration',
|
||||
[GraphType.LINE]: 'LineChartConfiguration',
|
||||
[GraphType.PIE]: 'PieChartConfiguration',
|
||||
[GraphType.NUMBER]: 'NumberChartConfiguration',
|
||||
[GraphType.GAUGE]: 'GaugeChartConfiguration',
|
||||
} as const;
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
import { AXIS_NAME_SETTING } from '@/command-menu/pages/page-layout/constants/settings/AxisNameSetting';
|
||||
import { CHART_DATA_SOURCE_SETTING } from '@/command-menu/pages/page-layout/constants/settings/ChartDataSourceSetting';
|
||||
import { COLORS_SETTING } from '@/command-menu/pages/page-layout/constants/settings/ColorsSetting';
|
||||
import { DATA_DISPLAY_X_SETTING } from '@/command-menu/pages/page-layout/constants/settings/DataDisplayXSetting';
|
||||
import { DATA_DISPLAY_Y_SETTING } from '@/command-menu/pages/page-layout/constants/settings/DataDisplayYSetting';
|
||||
import { DATA_LABELS_SETTING } from '@/command-menu/pages/page-layout/constants/settings/DataLabelsSetting';
|
||||
import { FILTER_SETTING } from '@/command-menu/pages/page-layout/constants/settings/FilterSetting';
|
||||
import { GROUP_BY_SETTING } from '@/command-menu/pages/page-layout/constants/settings/GroupBySetting';
|
||||
import { SORT_BY_GROUP_BY_FIELD_SETTING } from '@/command-menu/pages/page-layout/constants/settings/SortByGroupByFieldSetting';
|
||||
import { SORT_BY_X_SETTING } from '@/command-menu/pages/page-layout/constants/settings/SortByXSetting';
|
||||
import { type ChartSettingsGroup } from '@/command-menu/pages/page-layout/types/ChartSettingsGroup';
|
||||
|
||||
export const LINE_CHART_SETTINGS: ChartSettingsGroup[] = [
|
||||
{
|
||||
heading: 'Data',
|
||||
items: [CHART_DATA_SOURCE_SETTING, FILTER_SETTING],
|
||||
},
|
||||
{
|
||||
heading: 'X axis',
|
||||
items: [DATA_DISPLAY_X_SETTING, SORT_BY_X_SETTING],
|
||||
},
|
||||
{
|
||||
heading: 'Y axis',
|
||||
items: [
|
||||
DATA_DISPLAY_Y_SETTING,
|
||||
GROUP_BY_SETTING,
|
||||
SORT_BY_GROUP_BY_FIELD_SETTING,
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: 'Style',
|
||||
items: [COLORS_SETTING, AXIS_NAME_SETTING, DATA_LABELS_SETTING],
|
||||
},
|
||||
];
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import { CHART_DATA_SOURCE_SETTING } from '@/command-menu/pages/page-layout/constants/settings/ChartDataSourceSetting';
|
||||
import { DATA_LABELS_SETTING } from '@/command-menu/pages/page-layout/constants/settings/DataLabelsSetting';
|
||||
import { FILTER_SETTING } from '@/command-menu/pages/page-layout/constants/settings/FilterSetting';
|
||||
import { type ChartSettingsGroup } from '@/command-menu/pages/page-layout/types/ChartSettingsGroup';
|
||||
|
||||
export const NUMBER_CHART_SETTINGS: ChartSettingsGroup[] = [
|
||||
{
|
||||
heading: 'Data',
|
||||
items: [CHART_DATA_SOURCE_SETTING, FILTER_SETTING],
|
||||
},
|
||||
{
|
||||
heading: 'Style',
|
||||
items: [DATA_LABELS_SETTING],
|
||||
},
|
||||
];
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import { CHART_DATA_SOURCE_SETTING } from '@/command-menu/pages/page-layout/constants/settings/ChartDataSourceSetting';
|
||||
import { COLORS_SETTING } from '@/command-menu/pages/page-layout/constants/settings/ColorsSetting';
|
||||
import { DATA_DISPLAY_PIE_CHART_SETTING } from '@/command-menu/pages/page-layout/constants/settings/DataDisplayPieChartSetting';
|
||||
import { DATA_LABELS_SETTING } from '@/command-menu/pages/page-layout/constants/settings/DataLabelsSetting';
|
||||
import { EACH_SLICE_REPRESENTS_SETTING } from '@/command-menu/pages/page-layout/constants/settings/EachSliceRepresentsSetting';
|
||||
import { FILTER_SETTING } from '@/command-menu/pages/page-layout/constants/settings/FilterSetting';
|
||||
import { SORT_BY_X_SETTING } from '@/command-menu/pages/page-layout/constants/settings/SortByXSetting';
|
||||
import { type ChartSettingsGroup } from '@/command-menu/pages/page-layout/types/ChartSettingsGroup';
|
||||
|
||||
export const PIE_CHART_SETTINGS: ChartSettingsGroup[] = [
|
||||
{
|
||||
heading: 'Data',
|
||||
items: [
|
||||
CHART_DATA_SOURCE_SETTING,
|
||||
FILTER_SETTING,
|
||||
DATA_DISPLAY_PIE_CHART_SETTING,
|
||||
EACH_SLICE_REPRESENTS_SETTING,
|
||||
SORT_BY_X_SETTING,
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: 'Style',
|
||||
items: [COLORS_SETTING, DATA_LABELS_SETTING],
|
||||
},
|
||||
];
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
import {
|
||||
IconArrowDown,
|
||||
IconArrowUp,
|
||||
IconTrendingDown,
|
||||
IconTrendingUp,
|
||||
} from 'twenty-ui/display';
|
||||
import { GraphOrderBy } from '~/generated/graphql';
|
||||
|
||||
export const X_SORT_BY_OPTIONS = [
|
||||
{
|
||||
value: GraphOrderBy.FIELD_ASC,
|
||||
icon: IconArrowUp,
|
||||
},
|
||||
{
|
||||
value: GraphOrderBy.FIELD_DESC,
|
||||
icon: IconArrowDown,
|
||||
},
|
||||
{
|
||||
value: GraphOrderBy.VALUE_ASC,
|
||||
icon: IconTrendingUp,
|
||||
},
|
||||
{
|
||||
value: GraphOrderBy.VALUE_DESC,
|
||||
icon: IconTrendingDown,
|
||||
},
|
||||
];
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { CHART_CONFIGURATION_SETTING_LABELS } from '@/command-menu/pages/page-layout/constants/settings/ChartConfigurationSettingLabels';
|
||||
import { CHART_CONFIGURATION_SETTING_IDS } from '@/command-menu/pages/page-layout/types/ChartConfigurationSettingIds';
|
||||
import { type ChartSettingsItem } from '@/command-menu/pages/page-layout/types/ChartSettingsGroup';
|
||||
import { IconGizmo } from 'twenty-ui/display';
|
||||
|
||||
export const AXIS_NAME_SETTING: ChartSettingsItem = {
|
||||
isBoolean: false,
|
||||
Icon: IconGizmo,
|
||||
label: CHART_CONFIGURATION_SETTING_LABELS.AXIS_NAME,
|
||||
id: CHART_CONFIGURATION_SETTING_IDS.AXIS_NAME,
|
||||
};
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
|
||||
export const CHART_CONFIGURATION_SETTING_LABELS = {
|
||||
SOURCE: msg`Source`,
|
||||
DATA_LABELS: msg`Data labels`,
|
||||
COLORS: msg`Colors`,
|
||||
FILTER: msg`Filter`,
|
||||
GROUP_BY: msg`Group by`,
|
||||
SORT_BY_X: msg`Sort by`,
|
||||
SORT_BY_GROUP_BY_FIELD: msg`Sort by`,
|
||||
DATA_ON_DISPLAY_X: msg`Data on display`,
|
||||
DATA_ON_DISPLAY_Y: msg`Data on display`,
|
||||
DATA_ON_DISPLAY_PIE_CHART: msg`Data on display`,
|
||||
EACH_SLICE_REPRESENTS: msg`Each slice represents`,
|
||||
AXIS_NAME: msg`Axis name`,
|
||||
};
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { CHART_CONFIGURATION_SETTING_LABELS } from '@/command-menu/pages/page-layout/constants/settings/ChartConfigurationSettingLabels';
|
||||
import { CHART_CONFIGURATION_SETTING_IDS } from '@/command-menu/pages/page-layout/types/ChartConfigurationSettingIds';
|
||||
import { type ChartSettingsItem } from '@/command-menu/pages/page-layout/types/ChartSettingsGroup';
|
||||
import { IconDatabase } from 'twenty-ui/display';
|
||||
|
||||
export const CHART_DATA_SOURCE_SETTING: ChartSettingsItem = {
|
||||
isBoolean: false,
|
||||
Icon: IconDatabase,
|
||||
label: CHART_CONFIGURATION_SETTING_LABELS.SOURCE,
|
||||
id: CHART_CONFIGURATION_SETTING_IDS.SOURCE,
|
||||
};
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { CHART_CONFIGURATION_SETTING_LABELS } from '@/command-menu/pages/page-layout/constants/settings/ChartConfigurationSettingLabels';
|
||||
import { CHART_CONFIGURATION_SETTING_IDS } from '@/command-menu/pages/page-layout/types/ChartConfigurationSettingIds';
|
||||
import { type ChartSettingsItem } from '@/command-menu/pages/page-layout/types/ChartSettingsGroup';
|
||||
import { IconColorSwatch } from 'twenty-ui/display';
|
||||
|
||||
export const COLORS_SETTING: ChartSettingsItem = {
|
||||
isBoolean: false,
|
||||
Icon: IconColorSwatch,
|
||||
label: CHART_CONFIGURATION_SETTING_LABELS.COLORS,
|
||||
id: CHART_CONFIGURATION_SETTING_IDS.COLORS,
|
||||
};
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import { CHART_CONFIGURATION_SETTING_LABELS } from '@/command-menu/pages/page-layout/constants/settings/ChartConfigurationSettingLabels';
|
||||
import { CHART_CONFIGURATION_SETTING_IDS } from '@/command-menu/pages/page-layout/types/ChartConfigurationSettingIds';
|
||||
import { type ChartSettingsItem } from '@/command-menu/pages/page-layout/types/ChartSettingsGroup';
|
||||
import { IconChartPie } from 'twenty-ui/display';
|
||||
|
||||
export const DATA_DISPLAY_PIE_CHART_SETTING: ChartSettingsItem = {
|
||||
isBoolean: false,
|
||||
Icon: IconChartPie,
|
||||
label: CHART_CONFIGURATION_SETTING_LABELS.DATA_ON_DISPLAY_PIE_CHART,
|
||||
id: CHART_CONFIGURATION_SETTING_IDS.DATA_ON_DISPLAY_PIE_CHART,
|
||||
dependsOn: [CHART_CONFIGURATION_SETTING_IDS.SOURCE],
|
||||
};
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import { CHART_CONFIGURATION_SETTING_LABELS } from '@/command-menu/pages/page-layout/constants/settings/ChartConfigurationSettingLabels';
|
||||
import { CHART_CONFIGURATION_SETTING_IDS } from '@/command-menu/pages/page-layout/types/ChartConfigurationSettingIds';
|
||||
import { type ChartSettingsItem } from '@/command-menu/pages/page-layout/types/ChartSettingsGroup';
|
||||
import { IconAxisX } from 'twenty-ui/display';
|
||||
|
||||
export const DATA_DISPLAY_X_SETTING: ChartSettingsItem = {
|
||||
isBoolean: false,
|
||||
Icon: IconAxisX,
|
||||
label: CHART_CONFIGURATION_SETTING_LABELS.DATA_ON_DISPLAY_X,
|
||||
id: CHART_CONFIGURATION_SETTING_IDS.DATA_ON_DISPLAY_X,
|
||||
dependsOn: [CHART_CONFIGURATION_SETTING_IDS.SOURCE],
|
||||
};
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import { CHART_CONFIGURATION_SETTING_LABELS } from '@/command-menu/pages/page-layout/constants/settings/ChartConfigurationSettingLabels';
|
||||
import { CHART_CONFIGURATION_SETTING_IDS } from '@/command-menu/pages/page-layout/types/ChartConfigurationSettingIds';
|
||||
import { type ChartSettingsItem } from '@/command-menu/pages/page-layout/types/ChartSettingsGroup';
|
||||
import { IconAxisY } from 'twenty-ui/display';
|
||||
|
||||
export const DATA_DISPLAY_Y_SETTING: ChartSettingsItem = {
|
||||
isBoolean: false,
|
||||
Icon: IconAxisY,
|
||||
label: CHART_CONFIGURATION_SETTING_LABELS.DATA_ON_DISPLAY_Y,
|
||||
id: CHART_CONFIGURATION_SETTING_IDS.DATA_ON_DISPLAY_Y,
|
||||
dependsOn: [CHART_CONFIGURATION_SETTING_IDS.SOURCE],
|
||||
};
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { CHART_CONFIGURATION_SETTING_LABELS } from '@/command-menu/pages/page-layout/constants/settings/ChartConfigurationSettingLabels';
|
||||
import { CHART_CONFIGURATION_SETTING_IDS } from '@/command-menu/pages/page-layout/types/ChartConfigurationSettingIds';
|
||||
import { type ChartSettingsItem } from '@/command-menu/pages/page-layout/types/ChartSettingsGroup';
|
||||
import { IconTag } from 'twenty-ui/display';
|
||||
|
||||
export const DATA_LABELS_SETTING: ChartSettingsItem = {
|
||||
isBoolean: true,
|
||||
Icon: IconTag,
|
||||
label: CHART_CONFIGURATION_SETTING_LABELS.DATA_LABELS,
|
||||
id: CHART_CONFIGURATION_SETTING_IDS.DATA_LABELS,
|
||||
};
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { CHART_CONFIGURATION_SETTING_LABELS } from '@/command-menu/pages/page-layout/constants/settings/ChartConfigurationSettingLabels';
|
||||
import { CHART_CONFIGURATION_SETTING_IDS } from '@/command-menu/pages/page-layout/types/ChartConfigurationSettingIds';
|
||||
import { type ChartSettingsItem } from '@/command-menu/pages/page-layout/types/ChartSettingsGroup';
|
||||
import { IconChartPie } from 'twenty-ui/display';
|
||||
|
||||
export const EACH_SLICE_REPRESENTS_SETTING: ChartSettingsItem = {
|
||||
isBoolean: false,
|
||||
Icon: IconChartPie,
|
||||
label: CHART_CONFIGURATION_SETTING_LABELS.EACH_SLICE_REPRESENTS,
|
||||
id: CHART_CONFIGURATION_SETTING_IDS.EACH_SLICE_REPRESENTS,
|
||||
};
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import { CHART_CONFIGURATION_SETTING_LABELS } from '@/command-menu/pages/page-layout/constants/settings/ChartConfigurationSettingLabels';
|
||||
import { CHART_CONFIGURATION_SETTING_IDS } from '@/command-menu/pages/page-layout/types/ChartConfigurationSettingIds';
|
||||
import { type ChartSettingsItem } from '@/command-menu/pages/page-layout/types/ChartSettingsGroup';
|
||||
import { IconFilter } from 'twenty-ui/display';
|
||||
|
||||
export const FILTER_SETTING: ChartSettingsItem = {
|
||||
isBoolean: false,
|
||||
Icon: IconFilter,
|
||||
label: CHART_CONFIGURATION_SETTING_LABELS.FILTER,
|
||||
id: CHART_CONFIGURATION_SETTING_IDS.FILTER,
|
||||
dependsOn: [CHART_CONFIGURATION_SETTING_IDS.SOURCE],
|
||||
};
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import { CHART_CONFIGURATION_SETTING_LABELS } from '@/command-menu/pages/page-layout/constants/settings/ChartConfigurationSettingLabels';
|
||||
import { CHART_CONFIGURATION_SETTING_IDS } from '@/command-menu/pages/page-layout/types/ChartConfigurationSettingIds';
|
||||
import { type ChartSettingsItem } from '@/command-menu/pages/page-layout/types/ChartSettingsGroup';
|
||||
import { IconFilters } from 'twenty-ui/display';
|
||||
|
||||
export const GROUP_BY_SETTING: ChartSettingsItem = {
|
||||
isBoolean: false,
|
||||
Icon: IconFilters,
|
||||
label: CHART_CONFIGURATION_SETTING_LABELS.GROUP_BY,
|
||||
id: CHART_CONFIGURATION_SETTING_IDS.GROUP_BY,
|
||||
dependsOn: [CHART_CONFIGURATION_SETTING_IDS.SOURCE],
|
||||
};
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { CHART_CONFIGURATION_SETTING_LABELS } from '@/command-menu/pages/page-layout/constants/settings/ChartConfigurationSettingLabels';
|
||||
import { CHART_CONFIGURATION_SETTING_IDS } from '@/command-menu/pages/page-layout/types/ChartConfigurationSettingIds';
|
||||
import { type ChartSettingsItem } from '@/command-menu/pages/page-layout/types/ChartSettingsGroup';
|
||||
import { IconArrowsSort } from 'twenty-ui/display';
|
||||
|
||||
export const SORT_BY_GROUP_BY_FIELD_SETTING: ChartSettingsItem = {
|
||||
isBoolean: false,
|
||||
Icon: IconArrowsSort,
|
||||
label: CHART_CONFIGURATION_SETTING_LABELS.SORT_BY_GROUP_BY_FIELD,
|
||||
id: CHART_CONFIGURATION_SETTING_IDS.SORT_BY_GROUP_BY_FIELD,
|
||||
};
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { CHART_CONFIGURATION_SETTING_LABELS } from '@/command-menu/pages/page-layout/constants/settings/ChartConfigurationSettingLabels';
|
||||
import { CHART_CONFIGURATION_SETTING_IDS } from '@/command-menu/pages/page-layout/types/ChartConfigurationSettingIds';
|
||||
import { type ChartSettingsItem } from '@/command-menu/pages/page-layout/types/ChartSettingsGroup';
|
||||
import { IconArrowsSort } from 'twenty-ui/display';
|
||||
|
||||
export const SORT_BY_X_SETTING: ChartSettingsItem = {
|
||||
isBoolean: false,
|
||||
Icon: IconArrowsSort,
|
||||
label: CHART_CONFIGURATION_SETTING_LABELS.SORT_BY_X,
|
||||
id: CHART_CONFIGURATION_SETTING_IDS.SORT_BY_X,
|
||||
};
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
import { useGraphGroupBySortOptionLabels } from '@/command-menu/pages/page-layout/hooks/useGraphGroupBySortOptionLabels';
|
||||
import { useGraphXSortOptionLabels } from '@/command-menu/pages/page-layout/hooks/useGraphXSortOptionLabels';
|
||||
import { type ChartConfiguration } from '@/command-menu/pages/page-layout/types/ChartConfiguration';
|
||||
import { CHART_CONFIGURATION_SETTING_IDS } from '@/command-menu/pages/page-layout/types/ChartConfigurationSettingIds';
|
||||
import { getChartAxisNameDisplayOptions } from '@/command-menu/pages/page-layout/utils/getChartAxisNameDisplayOptions';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { getAggregateOperationLabel } from '@/object-record/record-board/record-board-column/utils/getAggregateOperationLabel';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { capitalize, isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const useChartSettingsValues = ({
|
||||
objectMetadataId,
|
||||
configuration,
|
||||
}: {
|
||||
objectMetadataId: string;
|
||||
configuration?: ChartConfiguration;
|
||||
}) => {
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
|
||||
const objectMetadataItem = objectMetadataItems.find(
|
||||
(objectMetadataItem) => objectMetadataItem.id === objectMetadataId,
|
||||
);
|
||||
|
||||
const { getXSortOptionLabel } = useGraphXSortOptionLabels({
|
||||
objectMetadataId,
|
||||
});
|
||||
|
||||
const { getGroupBySortOptionLabel } = useGraphGroupBySortOptionLabels({
|
||||
objectMetadataId,
|
||||
});
|
||||
|
||||
if (!configuration) {
|
||||
return {
|
||||
getChartSettingsValues: () => undefined,
|
||||
};
|
||||
}
|
||||
|
||||
const groupByFieldX =
|
||||
'groupByFieldMetadataIdX' in configuration
|
||||
? objectMetadataItem?.fields.find(
|
||||
(fieldMetadataItem) =>
|
||||
fieldMetadataItem.id === configuration.groupByFieldMetadataIdX,
|
||||
)
|
||||
: undefined;
|
||||
|
||||
const aggregateField = objectMetadataItem?.fields.find(
|
||||
(fieldMetadataItem) =>
|
||||
fieldMetadataItem.id === configuration.aggregateFieldMetadataId,
|
||||
);
|
||||
|
||||
const yAxisAggregateOperation = configuration.aggregateOperation;
|
||||
|
||||
const groupByFieldY =
|
||||
'groupByFieldMetadataIdY' in configuration
|
||||
? objectMetadataItem?.fields.find(
|
||||
(fieldMetadataItem) =>
|
||||
fieldMetadataItem.id === configuration.groupByFieldMetadataIdY,
|
||||
)
|
||||
: undefined;
|
||||
|
||||
const xAxisOrderBy =
|
||||
'orderByX' in configuration ? configuration.orderByX : undefined;
|
||||
|
||||
const xAxisOrderByLabel =
|
||||
isDefined(xAxisOrderBy) && 'groupByFieldMetadataIdX' in configuration
|
||||
? getXSortOptionLabel({
|
||||
graphOrderBy: xAxisOrderBy,
|
||||
groupByFieldMetadataIdX: configuration.groupByFieldMetadataIdX,
|
||||
aggregateFieldMetadataId: configuration.aggregateFieldMetadataId,
|
||||
aggregateOperation: configuration.aggregateOperation,
|
||||
})
|
||||
: undefined;
|
||||
|
||||
const groupByOrderBy =
|
||||
'orderByY' in configuration
|
||||
? configuration.orderByY
|
||||
: 'orderBy' in configuration
|
||||
? configuration.orderBy
|
||||
: undefined;
|
||||
|
||||
const groupByOrderByLabel =
|
||||
isDefined(groupByOrderBy) &&
|
||||
getGroupBySortOptionLabel({
|
||||
graphOrderBy: groupByOrderBy,
|
||||
groupByFieldMetadataId:
|
||||
'groupByFieldMetadataIdY' in configuration
|
||||
? configuration.groupByFieldMetadataIdY
|
||||
: 'groupByFieldMetadataId' in configuration
|
||||
? configuration.groupByFieldMetadataId
|
||||
: undefined,
|
||||
});
|
||||
|
||||
const getChartSettingsValues = (
|
||||
itemId: CHART_CONFIGURATION_SETTING_IDS,
|
||||
): boolean | string | undefined => {
|
||||
switch (itemId) {
|
||||
case CHART_CONFIGURATION_SETTING_IDS.SOURCE:
|
||||
return objectMetadataItem?.labelPlural;
|
||||
case CHART_CONFIGURATION_SETTING_IDS.DATA_ON_DISPLAY_X:
|
||||
return groupByFieldX?.label;
|
||||
case CHART_CONFIGURATION_SETTING_IDS.COLORS:
|
||||
return isDefined(configuration.color) && 'color' in configuration
|
||||
? capitalize(configuration.color)
|
||||
: undefined;
|
||||
case CHART_CONFIGURATION_SETTING_IDS.DATA_ON_DISPLAY_Y:
|
||||
return `${aggregateField?.label ?? ''}${aggregateField?.label ? ` (${getAggregateOperationLabel(yAxisAggregateOperation)})` : ''}`;
|
||||
case CHART_CONFIGURATION_SETTING_IDS.GROUP_BY:
|
||||
return groupByFieldY?.label;
|
||||
case CHART_CONFIGURATION_SETTING_IDS.AXIS_NAME:
|
||||
return 'axisNameDisplay' in configuration
|
||||
? getChartAxisNameDisplayOptions(configuration.axisNameDisplay)
|
||||
: undefined;
|
||||
case CHART_CONFIGURATION_SETTING_IDS.SORT_BY_X:
|
||||
return xAxisOrderByLabel;
|
||||
case CHART_CONFIGURATION_SETTING_IDS.SORT_BY_GROUP_BY_FIELD:
|
||||
return groupByOrderByLabel;
|
||||
case CHART_CONFIGURATION_SETTING_IDS.DATA_LABELS:
|
||||
return configuration.displayDataLabel;
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
getChartSettingsValues,
|
||||
};
|
||||
};
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { GraphOrderBy } from '~/generated/graphql';
|
||||
|
||||
export const useGraphGroupBySortOptionLabels = ({
|
||||
objectMetadataId,
|
||||
}: {
|
||||
objectMetadataId: string;
|
||||
}) => {
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
|
||||
const objectMetadataItem = objectMetadataItems.find(
|
||||
(objectMetadataItem) => objectMetadataItem.id === objectMetadataId,
|
||||
);
|
||||
|
||||
const getGroupBySortOptionLabel = ({
|
||||
graphOrderBy,
|
||||
groupByFieldMetadataId,
|
||||
}: {
|
||||
graphOrderBy: GraphOrderBy;
|
||||
groupByFieldMetadataId: string;
|
||||
}): string => {
|
||||
const field = objectMetadataItem?.fields.find(
|
||||
(fieldMetadataItem) => fieldMetadataItem.id === groupByFieldMetadataId,
|
||||
);
|
||||
|
||||
const fieldLabel = field?.label || t`Field`;
|
||||
|
||||
switch (graphOrderBy) {
|
||||
case GraphOrderBy.FIELD_ASC:
|
||||
return `${fieldLabel} ${t`Ascending`}`;
|
||||
case GraphOrderBy.FIELD_DESC:
|
||||
return `${fieldLabel} ${t`Descending`}`;
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
getGroupBySortOptionLabel,
|
||||
};
|
||||
};
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { getAggregateOperationLabel } from '@/object-record/record-board/record-board-column/utils/getAggregateOperationLabel';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { assertUnreachable, isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
type ExtendedAggregateOperations,
|
||||
GraphOrderBy,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
export const useGraphXSortOptionLabels = ({
|
||||
objectMetadataId,
|
||||
}: {
|
||||
objectMetadataId: string;
|
||||
}) => {
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
|
||||
const objectMetadataItem = objectMetadataItems.find(
|
||||
(objectMetadataItem) => objectMetadataItem.id === objectMetadataId,
|
||||
);
|
||||
|
||||
const getXSortOptionLabel = ({
|
||||
graphOrderBy,
|
||||
groupByFieldMetadataIdX,
|
||||
aggregateFieldMetadataId,
|
||||
aggregateOperation,
|
||||
}: {
|
||||
graphOrderBy: GraphOrderBy;
|
||||
groupByFieldMetadataIdX: string;
|
||||
aggregateFieldMetadataId?: string;
|
||||
aggregateOperation?: ExtendedAggregateOperations;
|
||||
}): string => {
|
||||
const groupByField = objectMetadataItem?.fields.find(
|
||||
(fieldMetadataItem) => fieldMetadataItem.id === groupByFieldMetadataIdX,
|
||||
);
|
||||
|
||||
const fieldLabel = groupByField?.label || t`Field`;
|
||||
|
||||
const aggregateField = objectMetadataItem?.fields.find(
|
||||
(fieldMetadataItem) =>
|
||||
isDefined(aggregateFieldMetadataId) &&
|
||||
fieldMetadataItem.id === aggregateFieldMetadataId,
|
||||
);
|
||||
|
||||
const valueLabel =
|
||||
aggregateField?.label && isDefined(aggregateOperation)
|
||||
? getAggregateOperationLabel(aggregateOperation)
|
||||
: t`Value`;
|
||||
|
||||
switch (graphOrderBy) {
|
||||
case GraphOrderBy.FIELD_ASC:
|
||||
return `${fieldLabel} ${t`Ascending`}`;
|
||||
case GraphOrderBy.FIELD_DESC:
|
||||
return `${fieldLabel} ${t`Descending`}`;
|
||||
case GraphOrderBy.VALUE_ASC:
|
||||
return `${valueLabel} ${t`Ascending`}`;
|
||||
case GraphOrderBy.VALUE_DESC:
|
||||
return `${valueLabel} ${t`Descending`}`;
|
||||
default:
|
||||
assertUnreachable(graphOrderBy);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
getXSortOptionLabel,
|
||||
};
|
||||
};
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
|
||||
import { pageLayoutEditingWidgetIdComponentState } from '@/page-layout/states/pageLayoutEditingWidgetIdComponentState';
|
||||
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import type { PageLayoutWidget } from '~/generated/graphql';
|
||||
|
||||
export const useUpdateCurrentWidgetConfig = (pageLayoutIdFromProps: string) => {
|
||||
const pageLayoutDraftCallbackState = useRecoilComponentCallbackState(
|
||||
pageLayoutDraftComponentState,
|
||||
pageLayoutIdFromProps,
|
||||
);
|
||||
|
||||
const currentlyEditingWidgetId = useRecoilComponentValue(
|
||||
pageLayoutEditingWidgetIdComponentState,
|
||||
pageLayoutIdFromProps,
|
||||
);
|
||||
|
||||
const updateCurrentWidgetConfig = useRecoilCallback(
|
||||
({ set }) =>
|
||||
({
|
||||
objectMetadataId,
|
||||
configToUpdate,
|
||||
}: {
|
||||
objectMetadataId?: string | null;
|
||||
configToUpdate?: Partial<PageLayoutWidget['configuration']>;
|
||||
}) => {
|
||||
set(pageLayoutDraftCallbackState, (prev) => ({
|
||||
...prev,
|
||||
tabs: prev.tabs.map((tab) => ({
|
||||
...tab,
|
||||
widgets: tab.widgets.map((widget) =>
|
||||
widget.id === currentlyEditingWidgetId
|
||||
? {
|
||||
...widget,
|
||||
objectMetadataId:
|
||||
objectMetadataId ?? widget.objectMetadataId,
|
||||
configuration: {
|
||||
...(widget.configuration ?? {}),
|
||||
...configToUpdate,
|
||||
} as PageLayoutWidget['configuration'],
|
||||
}
|
||||
: widget,
|
||||
),
|
||||
})),
|
||||
}));
|
||||
},
|
||||
[pageLayoutDraftCallbackState, currentlyEditingWidgetId],
|
||||
);
|
||||
|
||||
return { updateCurrentWidgetConfig };
|
||||
};
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
|
||||
import { pageLayoutEditingWidgetIdComponentState } from '@/page-layout/states/pageLayoutEditingWidgetIdComponentState';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
|
||||
export const useWidgetInEditMode = (pageLayoutId: string) => {
|
||||
const draftPageLayout = useRecoilComponentValue(
|
||||
pageLayoutDraftComponentState,
|
||||
pageLayoutId,
|
||||
);
|
||||
|
||||
const pageLayoutEditingWidgetId = useRecoilComponentValue(
|
||||
pageLayoutEditingWidgetIdComponentState,
|
||||
pageLayoutId,
|
||||
);
|
||||
|
||||
const widgetInEditMode = draftPageLayout.tabs
|
||||
.flatMap((tab) => tab.widgets)
|
||||
.find((widget) => widget.id === pageLayoutEditingWidgetId);
|
||||
|
||||
return { widgetInEditMode };
|
||||
};
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import {
|
||||
type BarChartConfiguration,
|
||||
type GaugeChartConfiguration,
|
||||
type LineChartConfiguration,
|
||||
type NumberChartConfiguration,
|
||||
type PieChartConfiguration,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
export type ChartConfiguration =
|
||||
| BarChartConfiguration
|
||||
| LineChartConfiguration
|
||||
| PieChartConfiguration
|
||||
| NumberChartConfiguration
|
||||
| GaugeChartConfiguration;
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
export enum CHART_CONFIGURATION_SETTING_IDS {
|
||||
SOURCE = 'SOURCE',
|
||||
DATA_LABELS = 'DATA_LABELS',
|
||||
COLORS = 'COLORS',
|
||||
FILTER = 'FILTER',
|
||||
GROUP_BY = 'GROUP_BY',
|
||||
SORT_BY_X = 'SORT_BY_X',
|
||||
SORT_BY_GROUP_BY_FIELD = 'SORT_BY_GROUP_BY_FIELD',
|
||||
SORT_BY = 'SORT_BY',
|
||||
DATA_ON_DISPLAY_X = 'DATA_ON_DISPLAY_X',
|
||||
DATA_ON_DISPLAY_Y = 'DATA_ON_DISPLAY_Y',
|
||||
DATA_ON_DISPLAY_PIE_CHART = 'DATA_ON_DISPLAY_PIE_CHART',
|
||||
EACH_SLICE_REPRESENTS = 'EACH_SLICE_REPRESENTS',
|
||||
AXIS_NAME = 'AXIS_NAME',
|
||||
}
|
||||
|
||||
export const CHART_CONFIGURATION_SETTING_TO_CONFIG_KEY_MAP = {
|
||||
[CHART_CONFIGURATION_SETTING_IDS.DATA_LABELS]: 'displayDataLabel',
|
||||
} as const;
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import { type CHART_CONFIGURATION_SETTING_IDS } from '@/command-menu/pages/page-layout/types/ChartConfigurationSettingIds';
|
||||
import { type MessageDescriptor } from '@lingui/core';
|
||||
import { type IconComponent } from 'twenty-ui/display';
|
||||
|
||||
export type ChartSettingsGroup = {
|
||||
heading: string;
|
||||
items: ChartSettingsItem[];
|
||||
};
|
||||
|
||||
export type ChartSettingsItem = {
|
||||
Icon: IconComponent;
|
||||
label: MessageDescriptor;
|
||||
id: CHART_CONFIGURATION_SETTING_IDS;
|
||||
description?: string;
|
||||
isBoolean: boolean;
|
||||
dependsOn?: CHART_CONFIGURATION_SETTING_IDS[];
|
||||
};
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { assertUnreachable } from 'twenty-shared/utils';
|
||||
import { AxisNameDisplay } from '~/generated/graphql';
|
||||
|
||||
export const getChartAxisNameDisplayOptions = (option: AxisNameDisplay) => {
|
||||
switch (option) {
|
||||
case AxisNameDisplay.NONE:
|
||||
return t`None`;
|
||||
case AxisNameDisplay.X:
|
||||
return t`X axis`;
|
||||
case AxisNameDisplay.Y:
|
||||
return t`Y axis`;
|
||||
case AxisNameDisplay.BOTH:
|
||||
return t`Both`;
|
||||
default:
|
||||
assertUnreachable(option);
|
||||
}
|
||||
};
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import { ChartAxisNameSelectionDropdownContent } from '@/command-menu/pages/page-layout/components/dropdown-content/ChartAxisNameSelectionDropdownContent';
|
||||
import { ChartColorSelectionDropdownContent } from '@/command-menu/pages/page-layout/components/dropdown-content/ChartColorSelectionDropdownContent';
|
||||
import { ChartDataSourceDropdownContent } from '@/command-menu/pages/page-layout/components/dropdown-content/ChartDataSourceDropdownContent';
|
||||
import { ChartFieldSelectionDropdownContent } from '@/command-menu/pages/page-layout/components/dropdown-content/ChartFieldSelectionDropdownContent';
|
||||
import { ChartFieldSelectionForAggregateOperationDropdownContent } from '@/command-menu/pages/page-layout/components/dropdown-content/ChartFieldSelectionForAggregateOperationDropdownContent';
|
||||
import { ChartGroupByFieldSelectionDropdownContent } from '@/command-menu/pages/page-layout/components/dropdown-content/ChartGroupByFieldSelectionDropdownContent';
|
||||
import { ChartSortByGroupByFieldDropdownContent } from '@/command-menu/pages/page-layout/components/dropdown-content/ChartSortByGroupByFieldDropdownContent';
|
||||
import { ChartXAxisSortBySelectionDropdownContent } from '@/command-menu/pages/page-layout/components/dropdown-content/ChartXAxisSortBySelectionDropdownContent';
|
||||
import { CHART_CONFIGURATION_SETTING_IDS } from '@/command-menu/pages/page-layout/types/ChartConfigurationSettingIds';
|
||||
import { t } from '@lingui/core/macro';
|
||||
|
||||
export const getChartSettingsDropdownContent = (itemId: string) => {
|
||||
switch (itemId) {
|
||||
case CHART_CONFIGURATION_SETTING_IDS.SOURCE:
|
||||
return <ChartDataSourceDropdownContent />;
|
||||
case CHART_CONFIGURATION_SETTING_IDS.DATA_ON_DISPLAY_X:
|
||||
return <ChartFieldSelectionDropdownContent />;
|
||||
case CHART_CONFIGURATION_SETTING_IDS.SORT_BY_X:
|
||||
return <ChartXAxisSortBySelectionDropdownContent />;
|
||||
case CHART_CONFIGURATION_SETTING_IDS.DATA_ON_DISPLAY_Y:
|
||||
return <ChartFieldSelectionForAggregateOperationDropdownContent />;
|
||||
case CHART_CONFIGURATION_SETTING_IDS.SORT_BY_GROUP_BY_FIELD:
|
||||
return (
|
||||
<ChartSortByGroupByFieldDropdownContent title={t`Y-Axis Sort By`} />
|
||||
);
|
||||
case CHART_CONFIGURATION_SETTING_IDS.GROUP_BY:
|
||||
return <ChartGroupByFieldSelectionDropdownContent />;
|
||||
case CHART_CONFIGURATION_SETTING_IDS.COLORS:
|
||||
return <ChartColorSelectionDropdownContent />;
|
||||
case CHART_CONFIGURATION_SETTING_IDS.AXIS_NAME:
|
||||
return <ChartAxisNameSelectionDropdownContent />;
|
||||
default:
|
||||
return <div>Configuration options will be implemented here</div>;
|
||||
}
|
||||
};
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
|
||||
import { DateAggregateOperations } from '@/object-record/record-table/constants/DateAggregateOperations';
|
||||
import { type ExtendedAggregateOperations as FrontendExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations';
|
||||
import { ExtendedAggregateOperations as GraphQLExtendedAggregateOperations } from '~/generated-metadata/graphql';
|
||||
|
||||
const AGGREGATE_OPERATION_MAPPING: Record<
|
||||
FrontendExtendedAggregateOperations,
|
||||
GraphQLExtendedAggregateOperations
|
||||
> = {
|
||||
[AggregateOperations.MIN]: GraphQLExtendedAggregateOperations.MIN,
|
||||
[AggregateOperations.MAX]: GraphQLExtendedAggregateOperations.MAX,
|
||||
[AggregateOperations.AVG]: GraphQLExtendedAggregateOperations.AVG,
|
||||
[AggregateOperations.SUM]: GraphQLExtendedAggregateOperations.SUM,
|
||||
[AggregateOperations.COUNT]: GraphQLExtendedAggregateOperations.COUNT,
|
||||
[AggregateOperations.COUNT_EMPTY]:
|
||||
GraphQLExtendedAggregateOperations.COUNT_EMPTY,
|
||||
[AggregateOperations.COUNT_NOT_EMPTY]:
|
||||
GraphQLExtendedAggregateOperations.COUNT_NOT_EMPTY,
|
||||
[AggregateOperations.COUNT_UNIQUE_VALUES]:
|
||||
GraphQLExtendedAggregateOperations.COUNT_UNIQUE_VALUES,
|
||||
[AggregateOperations.PERCENTAGE_EMPTY]:
|
||||
GraphQLExtendedAggregateOperations.PERCENTAGE_EMPTY,
|
||||
[AggregateOperations.PERCENTAGE_NOT_EMPTY]:
|
||||
GraphQLExtendedAggregateOperations.PERCENTAGE_NOT_EMPTY,
|
||||
[AggregateOperations.COUNT_TRUE]:
|
||||
GraphQLExtendedAggregateOperations.COUNT_TRUE,
|
||||
[AggregateOperations.COUNT_FALSE]:
|
||||
GraphQLExtendedAggregateOperations.COUNT_FALSE,
|
||||
[DateAggregateOperations.EARLIEST]:
|
||||
GraphQLExtendedAggregateOperations.EARLIEST,
|
||||
[DateAggregateOperations.LATEST]: GraphQLExtendedAggregateOperations.LATEST,
|
||||
};
|
||||
|
||||
export const mapToGraphQLExtendedAggregateOperation = (
|
||||
operation: FrontendExtendedAggregateOperations,
|
||||
): GraphQLExtendedAggregateOperations => {
|
||||
return AGGREGATE_OPERATION_MAPPING[operation];
|
||||
};
|
||||
Reference in New Issue
Block a user