mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-15 19:25:52 +08:00
feat: new dashboard widget's option selection (#982)
* feat: new dashboard widget's option selection * fix: overflowing legend * feat: delete menu item is of type danger * feat: added keyboard events onFocus and onBlur
This commit is contained in:
parent
9a6bcaadf8
commit
3c2173de9e
@ -1,39 +0,0 @@
|
|||||||
import {
|
|
||||||
DeleteOutlined,
|
|
||||||
EditFilled,
|
|
||||||
FullscreenOutlined,
|
|
||||||
} from '@ant-design/icons';
|
|
||||||
import history from 'lib/history';
|
|
||||||
import React from 'react';
|
|
||||||
import { Widgets } from 'types/api/dashboard/getAll';
|
|
||||||
|
|
||||||
import { Container } from './styles';
|
|
||||||
|
|
||||||
function Bar({
|
|
||||||
widget,
|
|
||||||
onViewFullScreenHandler,
|
|
||||||
onDeleteHandler,
|
|
||||||
}: BarProps): JSX.Element {
|
|
||||||
const onEditHandler = (): void => {
|
|
||||||
const widgetId = widget.id;
|
|
||||||
history.push(
|
|
||||||
`${window.location.pathname}/new?widgetId=${widgetId}&graphType=${widget.panelTypes}`,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Container>
|
|
||||||
<FullscreenOutlined onClick={onViewFullScreenHandler} />
|
|
||||||
<EditFilled onClick={onEditHandler} />
|
|
||||||
<DeleteOutlined onClick={onDeleteHandler} />
|
|
||||||
</Container>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface BarProps {
|
|
||||||
widget: Widgets;
|
|
||||||
onViewFullScreenHandler: () => void;
|
|
||||||
onDeleteHandler: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Bar;
|
|
@ -1,15 +0,0 @@
|
|||||||
import styled from 'styled-components';
|
|
||||||
|
|
||||||
export const Container = styled.div`
|
|
||||||
height: 15%;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-end;
|
|
||||||
display: flex;
|
|
||||||
gap: 1rem;
|
|
||||||
padding-right: 1rem;
|
|
||||||
padding-left: 1rem;
|
|
||||||
padding-top: 0.5rem;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
`;
|
|
@ -20,7 +20,7 @@ import AppActions from 'types/actions';
|
|||||||
import { GlobalTime } from 'types/actions/globalTime';
|
import { GlobalTime } from 'types/actions/globalTime';
|
||||||
import { Widgets } from 'types/api/dashboard/getAll';
|
import { Widgets } from 'types/api/dashboard/getAll';
|
||||||
|
|
||||||
import Bar from './Bar';
|
import WidgetHeader from '../WidgetHeader';
|
||||||
import FullView from './FullView';
|
import FullView from './FullView';
|
||||||
import { ErrorContainer, FullViewContainer, Modal } from './styles';
|
import { ErrorContainer, FullViewContainer, Modal } from './styles';
|
||||||
|
|
||||||
@ -37,6 +37,7 @@ function GridCardGraph({
|
|||||||
error: false,
|
error: false,
|
||||||
payload: undefined,
|
payload: undefined,
|
||||||
});
|
});
|
||||||
|
const [hovered, setHovered] = useState(false);
|
||||||
const [modal, setModal] = useState(false);
|
const [modal, setModal] = useState(false);
|
||||||
const { minTime, maxTime } = useSelector<AppState, GlobalTime>(
|
const { minTime, maxTime } = useSelector<AppState, GlobalTime>(
|
||||||
(state) => state.globalTime,
|
(state) => state.globalTime,
|
||||||
@ -171,10 +172,12 @@ function GridCardGraph({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{getModals()}
|
{getModals()}
|
||||||
<Bar
|
<WidgetHeader
|
||||||
onViewFullScreenHandler={(): void => onToggleModal(setModal)}
|
parentHover={hovered}
|
||||||
|
title={widget?.title}
|
||||||
widget={widget}
|
widget={widget}
|
||||||
onDeleteHandler={(): void => onToggleModal(setDeletModal)}
|
onView={(): void => onToggleModal(setModal)}
|
||||||
|
onDelete={(): void => onToggleModal(setDeletModal)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ErrorContainer>{state.errorMessage}</ErrorContainer>
|
<ErrorContainer>{state.errorMessage}</ErrorContainer>
|
||||||
@ -187,11 +190,26 @@ function GridCardGraph({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<span
|
||||||
<Bar
|
onMouseOver={(): void => {
|
||||||
onViewFullScreenHandler={(): void => onToggleModal(setModal)}
|
setHovered(true);
|
||||||
|
}}
|
||||||
|
onFocus={(): void => {
|
||||||
|
setHovered(true);
|
||||||
|
}}
|
||||||
|
onMouseOut={(): void => {
|
||||||
|
setHovered(false);
|
||||||
|
}}
|
||||||
|
onBlur={(): void => {
|
||||||
|
setHovered(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<WidgetHeader
|
||||||
|
parentHover={hovered}
|
||||||
|
title={widget.title}
|
||||||
widget={widget}
|
widget={widget}
|
||||||
onDeleteHandler={(): void => onToggleModal(setDeletModal)}
|
onView={(): void => onToggleModal(setModal)}
|
||||||
|
onDelete={(): void => onToggleModal(setDeletModal)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{getModals()}
|
{getModals()}
|
||||||
@ -202,12 +220,12 @@ function GridCardGraph({
|
|||||||
data: state.payload,
|
data: state.payload,
|
||||||
isStacked: widget.isStacked,
|
isStacked: widget.isStacked,
|
||||||
opacity: widget.opacity,
|
opacity: widget.opacity,
|
||||||
title: widget.title,
|
title: ' ', // empty title to accommodate absolutely positioned widget header
|
||||||
name,
|
name,
|
||||||
yAxisUnit,
|
yAxisUnit,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
109
frontend/src/container/GridGraphLayout/WidgetHeader/index.tsx
Normal file
109
frontend/src/container/GridGraphLayout/WidgetHeader/index.tsx
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import {
|
||||||
|
DeleteOutlined,
|
||||||
|
DownOutlined,
|
||||||
|
EditFilled,
|
||||||
|
FullscreenOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
import { Dropdown, Menu, Typography } from 'antd';
|
||||||
|
import history from 'lib/history';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Widgets } from 'types/api/dashboard/getAll';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ArrowContainer,
|
||||||
|
HeaderContainer,
|
||||||
|
HeaderContentContainer,
|
||||||
|
MenuItemContainer,
|
||||||
|
} from './styles';
|
||||||
|
|
||||||
|
type TWidgetOptions = 'view' | 'edit' | 'delete' | string;
|
||||||
|
interface IWidgetHeaderProps {
|
||||||
|
title: string;
|
||||||
|
widget: Widgets;
|
||||||
|
onView: VoidFunction;
|
||||||
|
onDelete: VoidFunction;
|
||||||
|
parentHover: boolean;
|
||||||
|
}
|
||||||
|
function WidgetHeader({
|
||||||
|
title,
|
||||||
|
widget,
|
||||||
|
onView,
|
||||||
|
onDelete,
|
||||||
|
parentHover,
|
||||||
|
}: IWidgetHeaderProps): JSX.Element {
|
||||||
|
const [localHover, setLocalHover] = useState(false);
|
||||||
|
|
||||||
|
const onEditHandler = (): void => {
|
||||||
|
const widgetId = widget.id;
|
||||||
|
history.push(
|
||||||
|
`${window.location.pathname}/new?widgetId=${widgetId}&graphType=${widget.panelTypes}`,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const keyMethodMapping: {
|
||||||
|
[K in TWidgetOptions]: { key: TWidgetOptions; method: VoidFunction };
|
||||||
|
} = {
|
||||||
|
view: {
|
||||||
|
key: 'view',
|
||||||
|
method: onView,
|
||||||
|
},
|
||||||
|
edit: {
|
||||||
|
key: 'edit',
|
||||||
|
method: onEditHandler,
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
key: 'delete',
|
||||||
|
method: onDelete,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const onMenuItemSelectHandler = ({ key }: { key: TWidgetOptions }): void => {
|
||||||
|
keyMethodMapping[key]?.method();
|
||||||
|
};
|
||||||
|
|
||||||
|
const menu = (
|
||||||
|
<Menu onClick={onMenuItemSelectHandler}>
|
||||||
|
<Menu.Item key={keyMethodMapping.view.key}>
|
||||||
|
<MenuItemContainer>
|
||||||
|
<span>View</span> <FullscreenOutlined />
|
||||||
|
</MenuItemContainer>
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item key={keyMethodMapping.edit.key}>
|
||||||
|
<MenuItemContainer>
|
||||||
|
<span>Edit</span> <EditFilled />
|
||||||
|
</MenuItemContainer>
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Divider />
|
||||||
|
<Menu.Item key={keyMethodMapping.delete.key} danger>
|
||||||
|
<MenuItemContainer>
|
||||||
|
<span>Delete</span> <DeleteOutlined />
|
||||||
|
</MenuItemContainer>
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dropdown
|
||||||
|
overlay={menu}
|
||||||
|
trigger={['click']}
|
||||||
|
overlayStyle={{ minWidth: 100 }}
|
||||||
|
placement="bottomCenter"
|
||||||
|
>
|
||||||
|
<HeaderContainer
|
||||||
|
onMouseOver={(): void => setLocalHover(true)}
|
||||||
|
onMouseOut={(): void => setLocalHover(false)}
|
||||||
|
hover={localHover}
|
||||||
|
>
|
||||||
|
<HeaderContentContainer onClick={(e): void => e.preventDefault()}>
|
||||||
|
<Typography.Text style={{ maxWidth: '80%' }} ellipsis>
|
||||||
|
{title}
|
||||||
|
</Typography.Text>
|
||||||
|
<ArrowContainer hover={parentHover}>
|
||||||
|
<DownOutlined />
|
||||||
|
</ArrowContainer>
|
||||||
|
</HeaderContentContainer>
|
||||||
|
</HeaderContainer>
|
||||||
|
</Dropdown>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WidgetHeader;
|
@ -0,0 +1,30 @@
|
|||||||
|
import { grey } from '@ant-design/colors';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
export const MenuItemContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const HeaderContainer = styled.div<{ hover: boolean }>`
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
background: ${({ hover }): string => (hover ? `${grey[0]}66` : 'inherit')};
|
||||||
|
padding: 0.25rem 0;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
cursor: all-scroll;
|
||||||
|
position: absolute;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const HeaderContentContainer = styled.span`
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
text-align: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const ArrowContainer = styled.span<{ hover: boolean }>`
|
||||||
|
visibility: ${({ hover }): string => (hover ? 'visible' : 'hidden')};
|
||||||
|
position: absolute;
|
||||||
|
right: -1rem;
|
||||||
|
`;
|
Loading…
x
Reference in New Issue
Block a user