feat(ui) - dashboard - ui review and fixes

This commit is contained in:
Shekar Siri 2022-04-14 17:00:02 +02:00
parent f9df0d2b91
commit af39480e09
13 changed files with 109 additions and 48 deletions

View file

@ -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>
)

View file

@ -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) =>

View file

@ -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>
</>
)}

View file

@ -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>
));

View file

@ -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"

View file

@ -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"

View file

@ -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
},
]}

View file

@ -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>

View file

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

View file

@ -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
},
]}

View file

@ -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}

View file

@ -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>

View file

@ -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));