feat(ui) - dashboard - ui review and fixes
This commit is contained in:
parent
f9df0d2b91
commit
af39480e09
13 changed files with 109 additions and 48 deletions
|
|
@ -12,7 +12,7 @@ function CustomMetriPercentage(props: Props) {
|
|||
return (
|
||||
<div className="flex flex-col items-center justify-center" style={{ height: '240px'}}>
|
||||
<div className="text-6xl">{numberWithCommas(data.count)}</div>
|
||||
<div className="text-lg mt-6">{`${parseInt(data.previousCount)} ( ${parseInt(data.countProgress).toFixed(1)}% )`}</div>
|
||||
<div className="text-lg mt-6">{`${parseInt(data.previousCount || 0)} ( ${parseInt(data.countProgress || 0).toFixed(1)}% )`}</div>
|
||||
<div className="color-gray-medium">from previous period.</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ function SlowestDomains(props: Props) {
|
|||
<NoContent
|
||||
size="small"
|
||||
show={ metric.data.chart.length === 0 }
|
||||
style={{ height: '240px' }}
|
||||
>
|
||||
<div className="w-full" style={{ height: '240px' }}>
|
||||
{metric.data.chart.map((item, i) =>
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ function WidgetCategoryItem({ category, isSelected, onClick, selectedWidgetIds,
|
|||
<div className="mb-2">{category.description}</div>
|
||||
{selectedCategoryWidgetsCount > 0 && (
|
||||
<div className="flex items-center">
|
||||
<input type="checkbox" checked={true} onChange={() => unSelectCategory(category)} />
|
||||
<span className="color-gray-medium ml-2">{`Selected ${selectedCategoryWidgetsCount} of ${category.widgets.length}`}</span>
|
||||
{/* <input type="checkbox" checked={true} onChange={() => unSelectCategory(category)} /> */}
|
||||
<span className="color-gray-medium text-sm">{`Selected ${selectedCategoryWidgetsCount} of ${category.widgets.length}`}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -29,6 +29,7 @@ function DashboardMetricSelection(props) {
|
|||
const { dashboardStore } = useStore();
|
||||
let widgetCategories: any[] = useObserver(() => dashboardStore.widgetCategories);
|
||||
const [activeCategory, setActiveCategory] = React.useState<any>();
|
||||
const [selectAllCheck, setSelectAllCheck] = React.useState(false);
|
||||
const selectedWidgetIds = useObserver(() => dashboardStore.selectedWidgets.map((widget: any) => widget.metricId));
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -39,10 +40,17 @@ function DashboardMetricSelection(props) {
|
|||
|
||||
const handleWidgetCategoryClick = (category: any) => {
|
||||
setActiveCategory(category);
|
||||
setSelectAllCheck(false);
|
||||
};
|
||||
|
||||
const toggleAllWidgets = ({ target: { checked }}) => {
|
||||
dashboardStore.toggleAllSelectedWidgets(checked);
|
||||
// dashboardStore.toggleAllSelectedWidgets(checked);
|
||||
setSelectAllCheck(checked);
|
||||
if (checked) {
|
||||
dashboardStore.selectWidgetsByCategory(activeCategory.name);
|
||||
} else {
|
||||
dashboardStore.removeSelectedWidgetByCategory(activeCategory);
|
||||
}
|
||||
}
|
||||
|
||||
return useObserver(() => (
|
||||
|
|
@ -62,10 +70,10 @@ function DashboardMetricSelection(props) {
|
|||
|
||||
<div className="ml-auto flex items-center">
|
||||
<span className="color-gray-medium">Showing past 7 days data for visual clue</span>
|
||||
<div className="flex items-center ml-3">
|
||||
<input type="checkbox" onChange={toggleAllWidgets} />
|
||||
<span className="ml-2">Select All</span>
|
||||
</div>
|
||||
<label className="flex items-center ml-3 cursor-pointer select-none">
|
||||
<input type="checkbox" onChange={toggleAllWidgets} checked={selectAllCheck} />
|
||||
<div className="ml-2">Select All</div>
|
||||
</label>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ interface Props {
|
|||
function DashboardModal(props) {
|
||||
const { history, siteId, dashboardId } = props;
|
||||
const { dashboardStore } = useStore();
|
||||
const selectedWidgetsCount = useObserver(() => dashboardStore.selectedWidgets.length);
|
||||
const { hideModal } = useModal();
|
||||
const dashboard = useObserver(() => dashboardStore.dashboardInstance);
|
||||
const loading = useObserver(() => dashboardStore.isSaving);
|
||||
|
|
@ -33,7 +34,7 @@ function DashboardModal(props) {
|
|||
return useObserver(() => (
|
||||
<div
|
||||
className="fixed border-r shadow p-4 h-screen"
|
||||
style={{ backgroundColor: '#FAFAFA', zIndex: '9999', width: '85%'}}
|
||||
style={{ backgroundColor: '#FAFAFA', zIndex: '999', width: '85%', maxWidth: '1300px' }}
|
||||
>
|
||||
<div className="mb-6 flex items-end justify-between">
|
||||
<div>
|
||||
|
|
@ -53,15 +54,16 @@ function DashboardModal(props) {
|
|||
)}
|
||||
<DashboardMetricSelection />
|
||||
|
||||
<div className="flex absolute bottom-0 left-0 right-0 bg-white border-t p-3">
|
||||
<div className="flex items-center absolute bottom-0 left-0 right-0 bg-white border-t p-3">
|
||||
<Button
|
||||
primary
|
||||
className=""
|
||||
disabled={!dashboard.isValid || loading}
|
||||
onClick={onSave}
|
||||
>
|
||||
{ dashboard.exists() ? "Add Selected to Dashboard" : "Create and Add to Dashboard" }
|
||||
{ dashboard.exists() ? "Add Selected to Dashboard" : "Create" }
|
||||
</Button>
|
||||
<span className="ml-2 color-gray-medium">{selectedWidgetsCount} Widgets</span>
|
||||
</div>
|
||||
</div>
|
||||
));
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ function DashboardSelectionModal(props: Props) {
|
|||
return useObserver(() => (
|
||||
<Modal size="tiny" open={ show }>
|
||||
<Modal.Header className="flex items-center justify-between">
|
||||
<div>{ 'Add to selected Dashboard' }</div>
|
||||
<div>{ 'Add to selected dashboard' }</div>
|
||||
<Icon
|
||||
role="button"
|
||||
tabIndex="-1"
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ function DashboardSideMenu(props: Props) {
|
|||
const dashboardId = useObserver(() => dashboardStore.selectedDashboard?.dashboardId);
|
||||
const dashboardsPicked = useObserver(() => dashboardStore.dashboards.slice(0, SHOW_COUNT));
|
||||
const remainingDashboardsCount = dashboardStore.dashboards.length - SHOW_COUNT;
|
||||
const isMetric = history.location.pathname.includes('metrics');
|
||||
|
||||
const redirect = (path) => {
|
||||
history.push(path);
|
||||
|
|
@ -82,6 +83,7 @@ function DashboardSideMenu(props: Props) {
|
|||
<div className="border-t w-full my-2" />
|
||||
<div className="w-full">
|
||||
<SideMenuitem
|
||||
active={isMetric}
|
||||
id="menu-manage-alerts"
|
||||
title="Metrics"
|
||||
iconName="bar-chart-line"
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ function DashboardView(props: Props) {
|
|||
<Button primary size="small" onClick={onAddWidgets}>Create Dashboard</Button>
|
||||
}
|
||||
>
|
||||
<div>
|
||||
<div style={{ maxWidth: '1300px', margin: 'auto'}}>
|
||||
<DashboardEditModal
|
||||
show={showEditModal}
|
||||
closeHandler={() => setShowEditModal(false)}
|
||||
|
|
@ -98,15 +98,16 @@ function DashboardView(props: Props) {
|
|||
</div>
|
||||
<div className="mx-4" />
|
||||
<div className="flex items-center">
|
||||
<span className="mr-1 color-gray-medium">More</span>
|
||||
{/* <span className="mr-1 color-gray-medium">Options</span> */}
|
||||
<ItemMenu
|
||||
label="Options"
|
||||
items={[
|
||||
{
|
||||
text: 'Edit',
|
||||
text: 'Rename',
|
||||
onClick: onEdit
|
||||
},
|
||||
{
|
||||
text: 'Delete Dashboard',
|
||||
text: 'Delete',
|
||||
onClick: onDelete
|
||||
},
|
||||
]}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ function WidgetView(props: Props) {
|
|||
onClick={() => setExpanded(!expanded)}
|
||||
className="flex items-center cursor-pointer select-none"
|
||||
>
|
||||
<span className="mr-2 color-teal">{expanded ? 'Collapse' : 'Expand'}</span>
|
||||
<span className="mr-2 color-teal">{expanded ? 'Close' : 'Edit'}</span>
|
||||
<Icon name={expanded ? 'chevron-up' : 'chevron-down'} size="16" color="teal" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
import React from 'react';
|
||||
import { Tooltip } from 'react-tippy';
|
||||
|
||||
function TemplateOverlay() {
|
||||
return (
|
||||
<div>
|
||||
<Tooltip
|
||||
// arrow
|
||||
// sticky
|
||||
title="Click to select"
|
||||
trigger="mouseenter"
|
||||
hideOnClick={true}
|
||||
// followCursor={true}
|
||||
// position="left"
|
||||
delay={300}
|
||||
>
|
||||
<div className="absolute inset-0 cursor-pointer z-10" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default TemplateOverlay;
|
||||
|
|
@ -9,6 +9,7 @@ import { useStore } from 'App/mstore';
|
|||
import LazyLoad from 'react-lazyload';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { withSiteId, dashboardMetricDetails } from 'App/routes';
|
||||
import TemplateOverlay from './TemplateOverlay';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
|
|
@ -27,7 +28,7 @@ interface Props {
|
|||
function WidgetWrapper(props: Props) {
|
||||
const { dashboardStore } = useStore();
|
||||
const { isWidget = false, active = false, index = 0, moveListItem = null, isPreview = false, isTemplate = false, dashboardId, siteId } = props;
|
||||
const widget = useObserver(() => props.widget);
|
||||
const widget: any = useObserver(() => props.widget);
|
||||
|
||||
const [{ opacity, isDragging }, dragRef] = useDrag({
|
||||
type: 'item',
|
||||
|
|
@ -40,7 +41,6 @@ function WidgetWrapper(props: Props) {
|
|||
|
||||
const [{ isOver, canDrop }, dropRef] = useDrop({
|
||||
accept: 'item',
|
||||
|
||||
drop: (item: any) => {
|
||||
if (item.index === index) return;
|
||||
moveListItem(item.index, index);
|
||||
|
|
@ -52,13 +52,14 @@ function WidgetWrapper(props: Props) {
|
|||
})
|
||||
|
||||
const onDelete = async () => {
|
||||
if (await confirm({
|
||||
header: 'Confirm',
|
||||
confirmButton: 'Yes, delete',
|
||||
confirmation: `Are you sure you want to permanently delete the widget from this dashboard?`
|
||||
})) {
|
||||
dashboardStore.deleteDashboardWidget(dashboardId!, widget.widgetId);
|
||||
}
|
||||
dashboardStore.deleteDashboardWidget(dashboardId!, widget.widgetId);
|
||||
// if (await confirm({
|
||||
// header: 'Confirm',
|
||||
// confirmButton: 'Yes, delete',
|
||||
// confirmation: `Are you sure you want to permanently delete the widget from this dashboard?`
|
||||
// })) {
|
||||
// dashboardStore.deleteDashboardWidget(dashboardId!, widget.widgetId);
|
||||
// }
|
||||
}
|
||||
|
||||
const onChartClick = () => {
|
||||
|
|
@ -72,9 +73,8 @@ function WidgetWrapper(props: Props) {
|
|||
|
||||
return useObserver(() => (
|
||||
<div
|
||||
className={cn("rounded bg-white border", 'col-span-' + widget.config.col)}
|
||||
className={cn("relative rounded bg-white border", 'col-span-' + widget.config.col, { "cursor-pointer" : isTemplate })}
|
||||
style={{
|
||||
// borderColor: 'transparent'
|
||||
userSelect: 'none',
|
||||
opacity: isDragging ? 0.5 : 1,
|
||||
borderColor: (canDrop && isOver) || active ? '#394EFF' : (isPreview ? 'transparent' : '#EEEEEE'),
|
||||
|
|
@ -82,8 +82,9 @@ function WidgetWrapper(props: Props) {
|
|||
ref={dragDropRef}
|
||||
onClick={props.onClick ? props.onClick : () => {}}
|
||||
>
|
||||
{isTemplate && <TemplateOverlay />}
|
||||
<div
|
||||
className="p-3 cursor-move flex items-center justify-between"
|
||||
className={cn("p-3 flex items-center justify-between", { "cursor-move" : !isTemplate })}
|
||||
>
|
||||
<h3 className="capitalize">{widget.name}</h3>
|
||||
{isWidget && (
|
||||
|
|
@ -92,9 +93,11 @@ function WidgetWrapper(props: Props) {
|
|||
items={[
|
||||
{
|
||||
text: 'Edit', onClick: onChartClick,
|
||||
disabled: widget.metricType === 'predefined',
|
||||
disabledMessage: 'Cannot edit system generated metrics'
|
||||
},
|
||||
{
|
||||
text: 'Hide from view',
|
||||
text: 'Remove from view',
|
||||
onClick: onDelete
|
||||
},
|
||||
]}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ function ModalOverlay({ children }) {
|
|||
let modal = useModal();
|
||||
|
||||
return (
|
||||
<div className="fixed w-full h-screen" style={{ zIndex: '99999' }}>
|
||||
<div className="fixed w-full h-screen" style={{ zIndex: '999' }}>
|
||||
<div
|
||||
onClick={() => modal.hideModal()}
|
||||
className={stl.overlay}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { Icon } from 'UI';
|
|||
import styles from './itemMenu.css';
|
||||
import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv';
|
||||
import cn from 'classnames';
|
||||
|
||||
import { Tooltip } from 'react-tippy';
|
||||
export default class ItemMenu extends React.PureComponent {
|
||||
state = {
|
||||
displayed: false,
|
||||
|
|
@ -20,7 +20,7 @@ export default class ItemMenu extends React.PureComponent {
|
|||
closeMenu = () => this.setState({ displayed: false })
|
||||
|
||||
render() {
|
||||
const { items } = this.props;
|
||||
const { items, label = "" } = this.props;
|
||||
const { displayed } = this.state;
|
||||
|
||||
return (
|
||||
|
|
@ -28,33 +28,46 @@ export default class ItemMenu extends React.PureComponent {
|
|||
<OutsideClickDetectingDiv
|
||||
onClickOutside={ this.closeMenu }
|
||||
>
|
||||
<div
|
||||
ref={ (ref) => { this.menuBtnRef = ref; } }
|
||||
className={cn("w-10 h-10 cursor-pointer rounded-full flex items-center justify-center hover:bg-gray-light", { 'bg-gray-light' : displayed })}
|
||||
onClick={ this.toggleMenu }
|
||||
role="button"
|
||||
>
|
||||
<Icon name="ellipsis-v" size="16" />
|
||||
<div onClick={ this.toggleMenu } className="flex items-center cursor-pointer">
|
||||
{label && <span className="mr-1 color-gray-medium ">{label}</span>}
|
||||
<div
|
||||
ref={ (ref) => { this.menuBtnRef = ref; } }
|
||||
className={cn("w-10 h-10 rounded-full flex items-center justify-center hover:bg-gray-light", { 'bg-gray-light' : displayed })}
|
||||
role="button"
|
||||
>
|
||||
<Icon name="ellipsis-v" size="16" />
|
||||
</div>
|
||||
</div>
|
||||
</OutsideClickDetectingDiv>
|
||||
<div
|
||||
className={ styles.menu }
|
||||
data-displayed={ displayed }
|
||||
>
|
||||
{ items.filter(({ hidden }) => !hidden).map(({ onClick, text, icon }) => (
|
||||
{ items.filter(({ hidden }) => !hidden).map(({ onClick, text, icon, disabled = false, disabledMessage = '' }) => (
|
||||
<div
|
||||
key={ text }
|
||||
className={ styles.menuItem }
|
||||
onClick={ this.onClick(onClick) }
|
||||
// className={ cn('') }
|
||||
onClick={ !disabled ? this.onClick(onClick) : () => {} }
|
||||
role="menuitem"
|
||||
tabIndex="-1"
|
||||
>
|
||||
{ icon && (
|
||||
<div className={ styles.iconWrapper }>
|
||||
<Icon name={ icon } size="13" color="gray-dark" />
|
||||
<Tooltip
|
||||
delay={500}
|
||||
arrow
|
||||
title={ disabledMessage }
|
||||
trigger="mouseenter"
|
||||
position="left"
|
||||
// disabled={ !disabled }
|
||||
>
|
||||
<div className={cn(styles.menuItem, {'disabled' : disabled })}>
|
||||
{ icon && (
|
||||
<div className={ styles.iconWrapper }>
|
||||
<Icon name={ icon } size="13" color="gray-dark" />
|
||||
</div>
|
||||
)}
|
||||
<div>{ text }</div>
|
||||
</div>
|
||||
)}
|
||||
<div>{ text }</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ export interface IDashboardSotre {
|
|||
fetchingDashboard: boolean
|
||||
sessionsLoading: boolean
|
||||
|
||||
selectWidgetsByCategory: (category: string) => void
|
||||
toggleAllSelectedWidgets: (isSelected: boolean) => void
|
||||
removeSelectedWidgetByCategory(category: string): void
|
||||
toggleWidgetSelection(widget: IWidget): void
|
||||
|
|
@ -111,6 +112,7 @@ export default class DashboardStore implements IDashboardSotre {
|
|||
editWidget: action,
|
||||
updateKey: action,
|
||||
|
||||
selectWidgetsByCategory: action,
|
||||
toggleAllSelectedWidgets: action,
|
||||
removeSelectedWidgetByCategory: action,
|
||||
toggleWidgetSelection: action,
|
||||
|
|
@ -138,6 +140,12 @@ export default class DashboardStore implements IDashboardSotre {
|
|||
}
|
||||
}
|
||||
|
||||
selectWidgetsByCategory(category: string) {
|
||||
const selectedWidgetIds = this.selectedWidgets.map((widget: any) => widget.metricId);
|
||||
const widgets = this.widgetCategories.find(cat => cat.name === category)?.widgets.filter(widget => !selectedWidgetIds.includes(widget.metricId))
|
||||
this.selectedWidgets = this.selectedWidgets.concat(widgets) || []
|
||||
}
|
||||
|
||||
removeSelectedWidgetByCategory = (category: any) => {
|
||||
const categoryWidgetIds = category.widgets.map(w => w.metricId)
|
||||
this.selectedWidgets = this.selectedWidgets.filter((widget: any) => !categoryWidgetIds.includes(widget.metricId));
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue