feat(ui) - dashboard - wip

This commit is contained in:
Shekar Siri 2022-04-06 14:46:28 +02:00
parent 3dd8b04039
commit caa58d1f98
25 changed files with 310 additions and 94 deletions

View file

@ -59,6 +59,7 @@ const METRICS_DETAILS = routes.metricDetails();
const DASHBOARD_PATH = routes.dashboard();
const DASHBOARD_SELECT_PATH = routes.dashboardSelected();
const DASHBOARD_METRIC_CREATE_PATH = routes.dashboardMetricCreate();
const DASHBOARD_METRIC_DETAILS_PATH = routes.dashboardMetricDetails();
// const WIDGET_PATAH = routes.dashboardMetric();
const SESSIONS_PATH = routes.sessions();
@ -206,6 +207,7 @@ class Router extends React.Component {
<Route exact strict path={ withSiteId(DASHBOARD_PATH, siteIdList) } component={ Dashboard } />
<Route exact strict path={ withSiteId(DASHBOARD_SELECT_PATH, siteIdList) } component={ Dashboard } />
<Route exact strict path={ withSiteId(DASHBOARD_METRIC_CREATE_PATH, siteIdList) } component={ Dashboard } />
<Route exact strict path={ withSiteId(DASHBOARD_METRIC_DETAILS_PATH, siteIdList) } component={ Dashboard } />

View file

@ -114,7 +114,7 @@ class AutoComplete extends React.PureComponent {
render() {
const { ddOpen, query, loading, values } = this.state;
const {
const {
optionMapping = defaultOptionMapping,
valueToText = defaultValueToText,
placeholder = 'Type to search...',

View file

@ -0,0 +1,86 @@
import { useObserver } from 'mobx-react-lite';
import React from 'react';
import { Button, Modal, Form, Icon, Checkbox } from 'UI';
import { useStore } from 'App/mstore'
interface Props {
show: boolean;
// dashboard: any;
closeHandler?: () => void;
}
function DashboardEditModal(props: Props) {
const { show, closeHandler } = props;
const { dashboardStore } = useStore();
const dashboard = useObserver(() => dashboardStore.dashboardInstance);
const onSave = () => {
dashboardStore.save(dashboard).then(closeHandler);
}
const write = ({ target: { value, name } }) => dashboard.update({ [ name ]: value })
const writeOption = (e, { checked, name }) => {
dashboard.update({ [name]: checked });
}
return useObserver(() => (
<Modal size="tiny" open={ show }>
<Modal.Header className="flex items-center justify-between">
<div>{ 'Edit Dashboard' }</div>
<Icon
role="button"
tabIndex="-1"
color="gray-dark"
size="14"
name="close"
onClick={ closeHandler }
/>
</Modal.Header>
<Modal.Content>
<Form onSubmit={onSave}>
<Form.Field>
<label>{'Title:'}</label>
<input
autoFocus={ true }
className=""
name="name"
value={ dashboard.name }
onChange={write}
placeholder="Title"
/>
</Form.Field>
<Form.Field>
<div className="flex items-center">
<Checkbox
name="isPublic"
className="font-medium mr-3"
type="checkbox"
checked={ dashboard.isPublic }
onClick={ writeOption }
/>
<div className="flex items-center cursor-pointer" onClick={ () => dashboard.update({ 'isPublic': !dashboard.isPublic }) }>
<Icon name="user-friends" size="16" />
<span className="ml-2"> Team can see and edit the dashboard.</span>
</div>
</div>
</Form.Field>
</Form>
</Modal.Content>
<Modal.Actions>
<div className="-mx-2 px-2">
<Button
primary
onClick={ onSave }
// loading={ loading }
>
Save
</Button>
<Button className="" marginRight onClick={ closeHandler }>{ 'Cancel' }</Button>
</div>
</Modal.Actions>
</Modal>
));
}
export default DashboardEditModal;

View file

@ -0,0 +1 @@
export { default } from './DashboardEditModal'

View file

@ -91,10 +91,10 @@ function DashboardMetricSelection(props) {
{activeCategory && activeCategory.widgets.map((widget: any) => (
<div
key={widget.metricId}
className={cn("border rounded cursor-pointer", { 'border-color-teal' : selectedWidgetIds.includes(widget.metricId) })}
className={cn("rounded cursor-pointer")}
onClick={() => dashboardStore.toggleWidgetSelection(widget)}
>
<WidgetWrapper widget={widget} />
<WidgetWrapper widget={widget} active={selectedWidgetIds.includes(widget.metricId)} isTemplate={true}/>
</div>
))}
</div>

View file

@ -34,7 +34,7 @@ function DashboardModal(props) {
return useObserver(() => (
<div
className="fixed border-r shadow p-4 h-screen"
style={{ backgroundColor: '#FAFAFA', zIndex: '9999', width: '80%'}}
style={{ backgroundColor: '#FAFAFA', zIndex: '9999', width: '85%'}}
>
<div className="mb-6 flex items-end justify-between">
<div>

View file

@ -7,6 +7,7 @@ import {
metricDetails,
dashboardSelected,
dashboardMetricCreate,
dashboardMetricDetails,
withSiteId,
dashboard,
} from 'App/routes';
@ -24,13 +25,17 @@ function DashboardRouter(props: Props) {
<div>
<Switch>
<Route exact strict path={withSiteId(metrics(), siteId)}>
<MetricsView />
<MetricsView siteId={siteId} />
</Route>
<Route exact strict path={withSiteId(metricDetails(), siteId)}>
<WidgetView siteId={siteId} {...props} />
</Route>
<Route exact strict path={withSiteId(dashboardMetricDetails(dashboardId), siteId)}>
<WidgetView siteId={siteId} {...props} />
</Route>
<Route exact strict path={withSiteId(dashboardMetricCreate(dashboardId), siteId)}>
<WidgetView siteId={siteId} {...props} />
</Route>

View file

@ -1,7 +1,7 @@
import React, { useEffect } from 'react';
import { observer, useObserver } from 'mobx-react-lite';
import { useStore } from 'App/mstore';
import { Button, PageTitle, Link, Loader, NoContent } from 'UI';
import { Button, PageTitle, Link, Loader, NoContent, ItemMenu } from 'UI';
import { withSiteId, dashboardMetricCreate, dashboardSelected, dashboard } from 'App/routes';
import withModal from 'App/components/Modal/withModal';
import DashboardWidgetGrid from '../DashboardWidgetGrid';
@ -9,6 +9,7 @@ import { confirm } from 'UI/Confirmation';
import { withRouter } from 'react-router-dom';
import { useModal } from 'App/components/Modal';
import DashboardModal from '../DashboardModal';
import DashboardEditModal from '../DashboardEditModal';
interface Props {
siteId: number;
@ -22,16 +23,22 @@ function DashboardView(props: Props) {
const { hideModal, showModal } = useModal();
const loading = useObserver(() => dashboardStore.fetchingDashboard);
const dashboard: any = dashboardStore.selectedDashboard
const [showEditModal, setShowEditModal] = React.useState(false);
useEffect(() => {
dashboardStore.fetch(dashboardId)
}, []);
const onEditHandler = () => {
const onAddWidgets = () => {
dashboardStore.initDashboard(dashboard)
showModal(<DashboardModal siteId={siteId} dashboardId={dashboardId} />, {})
}
const onEdit = () => {
dashboardStore.initDashboard(dashboard)
setShowEditModal(true)
}
const onDelete = async () => {
if (await confirm({
header: 'Confirm',
@ -54,17 +61,37 @@ function DashboardView(props: Props) {
size="small"
>
<div>
<DashboardEditModal
show={showEditModal}
// dashboard={dashboard}
closeHandler={() => setShowEditModal(false)}
/>
<div className="flex items-center mb-4 justify-between">
<div className="flex items-center">
<PageTitle title={dashboard?.name} className="mr-3" />
{/* <Link to={withSiteId(dashboardMetricCreate(dashboard?.dashboardId), siteId)}><Button primary size="small">Add Metric</Button></Link> */}
<Button primary size="small" onClick={onEditHandler}>Add Metric</Button>
<Button primary size="small" onClick={onAddWidgets}>Add Metric</Button>
</div>
<div>
<Button onClick={onDelete}>Remove</Button>
<ItemMenu
items={[
{
text: 'Edit',
onClick: onEdit
},
{
text: 'Delete Dashboard',
onClick: onDelete
},
]}
/>
</div>
</div>
<DashboardWidgetGrid dashboardId={dashboardId} onEditHandler={onEditHandler} />
<DashboardWidgetGrid
siteId={siteId}
dashboardId={dashboardId}
onEditHandler={onAddWidgets}
/>
</div>
</NoContent>
</Loader>

View file

@ -5,11 +5,12 @@ import { NoContent, Button, Loader } from 'UI';
import { useObserver } from 'mobx-react-lite';
interface Props {
siteId: string,
dashboardId: string;
onEditHandler: () => void;
}
function DashboardWidgetGrid(props) {
const { dashboardId } = props;
const { dashboardId, siteId } = props;
const { dashboardStore } = useStore();
const loading = useObserver(() => dashboardStore.isLoading);
const dashbaord: any = dashboardStore.selectedDashboard;
@ -36,6 +37,7 @@ function DashboardWidgetGrid(props) {
key={item.widgetId}
moveListItem={(dragIndex, hoverIndex) => dashbaord.swapWidgetPosition(dragIndex, hoverIndex)}
dashboardId={dashboardId}
siteId={siteId}
/>
))}
</div>

View file

@ -10,9 +10,9 @@ function DashboardLink({ dashboards}) {
return (
dashboards.map(dashboard => (
<Link to={`/dashboard/${dashboard.dashboardId}`} className="">
<div className="flex items-center">
<div className="flex items-center mb-1">
<div className="mr-2 text-4xl no-underline" style={{ textDecoration: 'none'}}>·</div>
<span className="link">{dashboard.name}</span>
<span className="link leading-4">{dashboard.name}</span>
</div>
</Link>
))
@ -22,24 +22,24 @@ function DashboardLink({ dashboards}) {
function MetricListItem(props: Props) {
const { metric } = props;
return (
<div className="grid grid-cols-7 p-3 border-t select-none">
<div className="col-span-2">
<div className="grid grid-cols-12 p-3 border-t select-none">
<div className="col-span-3">
<Link to={`/metrics/${metric.metricId}`} className="link">
{metric.name}
</Link>
</div>
<div><Label className="capitalize">{metric.metricType}</Label></div>
<div>
<div className="col-span-2">
<DashboardLink dashboards={metric.dashboards} />
</div>
<div>{metric.owner}</div>
<div className="col-span-3">{metric.owner}</div>
<div>
<div className="flex items-center">
<Icon name={metric.isPublic ? "user-friends" : "person-fill"} className="mr-2" />
<span>{metric.isPublic ? 'Team' : 'Private'}</span>
</div>
</div>
<div>{metric.lastModified && checkForRecent(metric.lastModified, 'LLL dd, yyyy, hh:mm a')}</div>
<div className="col-span-2">{metric.lastModified && checkForRecent(metric.lastModified, 'LLL dd, yyyy, hh:mm a')}</div>
</div>
);
}

View file

@ -19,13 +19,13 @@ function MetricsList(props: Props) {
return useObserver(() => (
<NoContent show={lenth === 0} icon="exclamation-circle">
<div className="mt-3 border rounded bg-white">
<div className="grid grid-cols-7 p-3 font-medium">
<div className="col-span-2">Title</div>
<div className="grid grid-cols-12 p-3 font-medium">
<div className="col-span-3">Title</div>
<div>Type</div>
<div>Dashboards</div>
<div>Owner</div>
<div>Visibility & Edit Access</div>
<div>Last Modified</div>
<div className="col-span-2">Dashboards</div>
<div className="col-span-3">Owner</div>
<div>Visibility</div>
<div className="col-span-2">Last Modified</div>
</div>
{list.map((metric: any) => (

View file

@ -1,12 +1,16 @@
import React from 'react';
import { Button, PageTitle, Icon, Link } from 'UI';
import { withSiteId, dashboardMetricCreate } from 'App/routes';
import { withSiteId, metricCreate } from 'App/routes';
import MetricsList from '../MetricsList';
import MetricsSearch from '../MetricsSearch';
import { useStore } from 'App/mstore';
import { useObserver } from 'mobx-react-lite';
function MetricsView(props) {
interface Props{
siteId: number;
}
function MetricsView(props: Props) {
const { siteId } = props;
const { metricStore } = useStore();
React.useEffect(() => {
@ -16,7 +20,7 @@ function MetricsView(props) {
<div>
<div className="flex items-center mb-4 justify-between">
<PageTitle title="Metrics" className="mr-3" />
{/* <Link to={withSiteId(dashboardMetricCreate(dashboard.dashboardId), store.siteId)}><Button primary size="small">Add Metric</Button></Link> */}
<Link to={'/metrics/create'}><Button primary size="small">Add Metric</Button></Link>
<div className="ml-auto w-1/3">
<MetricsSearch />
</div>

View file

@ -11,12 +11,12 @@ import { observer, useObserver } from 'mobx-react-lite';
import { Loader } from 'UI';
import { useStore } from 'App/mstore';
interface Props {
// metric: any;
metric: any;
}
function WidgetChart(props: Props) {
// const metric = useObserver(() => props.metric);
const metric = useObserver(() => props.metric);
const { metricStore } = useStore();
const metric: any = useObserver(() => metricStore.instance);
// const metric: any = useObserver(() => metricStore.instance);
const series = useObserver(() => metric.series);
const colors = Styles.customMetricColors;
const [loading, setLoading] = useState(false)

View file

@ -8,6 +8,7 @@ import { HelpText, Button, Icon } from 'UI'
import FilterSeries from '../FilterSeries';
import { withRouter } from 'react-router-dom';
import { confirm } from 'UI/Confirmation';
import { withSiteId, dashboardMetricDetails, metricDetails } from 'App/routes'
interface Props {
history: any;
@ -50,7 +51,17 @@ function WidgetForm(props: Props) {
};
const onSave = () => {
metricStore.save(metric, dashboardId);
const wasCreating = !metric.exists()
metricStore.save(metric, dashboardId).then(() => {
if (wasCreating) {
if (parseInt(dashboardId) > 0) {
history.push(withSiteId(dashboardMetricDetails(parseInt(dashboardId)), siteId));
} else {
history.push(withSiteId(metricDetails(metric.metricId), siteId));
}
}
});
}
const onDelete = async () => {

View file

@ -0,0 +1,57 @@
import React, { useState, useRef, useEffect } from 'react';
import { Icon } from 'UI';
interface Props {
name: string;
onUpdate: (name) => void;
seriesIndex?: number;
}
function WidgetName(props: Props) {
const { seriesIndex = 1 } = props;
const [editing, setEditing] = useState(false)
const [name, setName] = useState(props.name)
const ref = useRef<any>(null)
const write = ({ target: { value, name } }) => {
setName(value)
}
const onBlur = () => {
setEditing(false)
props.onUpdate(name.trim() === '' ? 'New Widget' : name)
}
useEffect(() => {
if (editing) {
ref.current.focus()
}
}, [editing])
useEffect(() => {
setName(props.name)
}, [props.name])
// const { name } = props;
return (
<div className="flex items-center">
{ editing ? (
<input
ref={ ref }
name="name"
className="rounded fluid border-0 -mx-2 px-2 h-8"
value={name}
// readOnly={!editing}
onChange={write}
onBlur={onBlur}
onFocus={() => setEditing(true)}
/>
) : (
<div className="text-2xl h-8 flex items-center border-transparent">{ name }</div>
)}
<div className="ml-3 cursor-pointer" onClick={() => setEditing(true)}><Icon name="pencil" size="14" /></div>
</div>
);
}
export default WidgetName;

View file

@ -0,0 +1 @@
export { default } from './WidgetName';

View file

@ -25,6 +25,8 @@ function WidgetPreview(props: Props) {
metric.update({ ...changedDates, rangeName: changedDates.rangeValue });
}
console.log('view', metric.viewType)
return useObserver(() => (
<div className={cn(className)}>
<div className="flex items-center justify-between">
@ -77,7 +79,7 @@ function WidgetPreview(props: Props) {
/>
</div>
</div>
<div className="bg-white rounded border p-4">
<div className="bg-white rounded p-4">
<WidgetWrapper widget={metric} isPreview={true} />
</div>
</div>

View file

@ -7,6 +7,7 @@ import WidgetSessions from '../WidgetSessions';
import { Icon, BackLink, Loader } from 'UI';
import { useObserver } from 'mobx-react-lite';
import { withSiteId } from 'App/routes';
import WidgetName from '../WidgetName';
interface Props {
history: any;
match: any
@ -32,7 +33,7 @@ function WidgetView(props: Props) {
const onBackHandler = () => {
if (dashboardId) {
props.history.push(withSiteId(`/dashboard/${dashboardId}`, siteId));
} {
} else {
props.history.push(withSiteId(`/metrics`, siteId));
}
}
@ -43,7 +44,9 @@ function WidgetView(props: Props) {
<BackLink onClick={onBackHandler} vertical className="absolute" style={{ left: '-50px', top: '0px' }} />
<div className="bg-white rounded border">
<div className="p-4 flex justify-between items-center">
<h1 className="mb-0 text-2xl">{widget.name}</h1>
<h1 className="mb-0 text-2xl">
<WidgetName name={widget.name} onUpdate={(name) => metricStore.merge({ name })} />
</h1>
<div className="text-gray-600">
<div
onClick={() => setExpanded(!expanded)}

View file

@ -6,6 +6,9 @@ import WidgetChart from '../WidgetChart';
import { useObserver } from 'mobx-react-lite';
import { confirm } from 'UI/Confirmation';
import { useStore } from 'App/mstore';
import LazyLoad from 'react-lazyload';
import { withRouter } from 'react-router-dom';
import { withSiteId, dashboardMetricDetails } from 'App/routes';
interface Props {
className?: string;
@ -13,11 +16,15 @@ interface Props {
index?: number;
moveListItem?: any;
isPreview?: boolean;
isTemplate?: boolean
dashboardId?: string;
siteId?: string,
active?: boolean;
history?: any
}
function WidgetWrapper(props: Props) {
const { dashboardStore } = useStore();
const { widget, index = 0, moveListItem = null, isPreview = false, dashboardId } = props;
const { active = false, widget, index = 0, moveListItem = null, isPreview = false, isTemplate = false, dashboardId, siteId } = props;
const [{ opacity, isDragging }, dragRef] = useDrag({
type: 'item',
@ -47,54 +54,62 @@ function WidgetWrapper(props: Props) {
confirmButton: 'Yes, Delete',
confirmation: `Are you sure you want to permanently delete this Dashboard?`
})) {
dashboardStore.deleteDashboardWidget(dashboardId!, widget.widgetId).then(() => {
})
dashboardStore.deleteDashboardWidget(dashboardId!, widget.widgetId);
}
}
const editHandler = () => {
console.log('clicked', widget.metricId);
}
const onChartClick = () => {
if (isPreview || isTemplate) return;
props.history.push(withSiteId(dashboardMetricDetails(dashboardId, widget.metricId),siteId));
}
const ref: any = useRef(null)
const dragDropRef: any = dragRef(dropRef(ref))
return useObserver(() => (
<div
className={cn("rounded bg-white", 'col-span-' + widget.colSpan, { 'border' : !isPreview })}
className={cn("rounded bg-white border", 'col-span-' + widget.config.col)}
style={{
// borderColor: 'transparent'
userSelect: 'none',
opacity: isDragging ? 0.5 : 1,
borderColor: canDrop && isOver ? '#394EFF' : '',
borderColor: (canDrop && isOver) || active ? '#394EFF' : (isPreview ? 'transparent' : '#EEEEEE'),
}}
ref={dragDropRef}
>
{/* <Link to={withSiteId(dashboardMetricDetails(dashboard.dashboardId, widget.widgetId), siteId)}> */}
<div
className="p-3 cursor-move border-b flex items-center justify-between"
>
{widget.name}
<div
className="p-3 cursor-move flex items-center justify-between"
>
<h3 className="capitalize">{widget.name}</h3>
{!isPreview && !isTemplate && (
<div>
<ItemMenu
items={[
{
text: 'Edit',
onClick: () => {
console.log('edit');
}
text: 'Edit', onClick: editHandler,
},
{
text: 'Hide from view' + dashboardId,
text: 'Hide from view',
onClick: onDelete
},
]}
/>
</div>
</div>
)}
</div>
<div className="">
<WidgetChart />
<LazyLoad height={300} offset={320} >
<div className="px-2" onClick={onChartClick}>
<WidgetChart metric={props.widget}/>
</div>
{/* </Link> */}
</LazyLoad>
</div>
));
}
export default WidgetWrapper;
export default withRouter(WidgetWrapper);

View file

@ -1,35 +1,18 @@
import { Icon } from 'UI';
import styles from './itemMenu.css';
import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv';
export default class ItemMenu extends React.PureComponent {
state = {
displayed: false,
};
componentDidMount() {
document.addEventListener('click', this.handleClickOutside);
}
componentWillUnmount() {
document.removeEventListener('click', this.handleClickOutside);
}
onClick = callback => (e) => {
e.stopPropagation();
callback(e);
}
handleClickOutside = (e) => {
if (!this.state.displayed) return;
if (e.target !== this.menuBtnRef) {
this.closeMenu();
}
}
toggleMenu = (e) => {
// e.preventDefault();
// e.stopPropagation();
console.log('toggleMenu', e);
this.setState({ displayed: !this.state.displayed });
}
@ -41,22 +24,18 @@ export default class ItemMenu extends React.PureComponent {
return (
<div className={ styles.wrapper }>
{/* <div
ref={ (ref) => { this.menuBtnRef = ref; } }
className={ styles.menuBtn }
onClick={ this.toggleMenu }
role="button"
tabIndex="-1"
/> */}
<div
ref={ (ref) => { this.menuBtnRef = ref; } }
className="w-10 h-10 cursor-pointer bg-white rounded-full flex items-center justify-center hover:bg-gray-lightest"
onClick={ this.toggleMenu }
role="button"
// tabIndex="-1"
<OutsideClickDetectingDiv
onClickOutside={ this.closeMenu }
>
<Icon name="ellipsis-v" size="16" />
</div>
<div
ref={ (ref) => { this.menuBtnRef = ref; } }
className="w-10 h-10 cursor-pointer bg-white rounded-full flex items-center justify-center hover:bg-gray-lightest"
onClick={ this.toggleMenu }
role="button"
>
<Icon name="ellipsis-v" size="16" />
</div>
</OutsideClickDetectingDiv>
<div
className={ styles.menu }
data-displayed={ displayed }

View file

@ -2,6 +2,7 @@ import { makeAutoObservable, runInAction, observable, action, reaction } from "m
import Dashboard, { IDashboard } from "./types/dashboard"
import Widget, { IWidget } from "./types/widget";
import { dashboardService, metricService } from "App/services";
import { toast } from 'react-toastify';
export interface IDashboardSotre {
dashboards: IDashboard[]
@ -37,7 +38,7 @@ export interface IDashboardSotre {
deleteDashboard(dashboard: IDashboard): Promise<any>
toJson(): void
fromJson(json: any): void
initDashboard(dashboard: IDashboard): void
// initDashboard(dashboard: IDashboard): void
addDashboard(dashboard: IDashboard): void
removeDashboard(dashboard: IDashboard): void
getDashboard(dashboardId: string): void
@ -78,6 +79,7 @@ export default class DashboardStore implements IDashboardSotre {
constructor() {
makeAutoObservable(this, {
widgetCategories: observable.ref,
// dashboardInstance: observable.ref,
resetCurrentWidget: action,
addDashboard: action,
@ -159,7 +161,7 @@ export default class DashboardStore implements IDashboardSotre {
}
initDashboard(dashboard: Dashboard) {
this.dashboardInstance = dashboard || new Dashboard()
this.dashboardInstance = dashboard ? new Dashboard().fromJson(dashboard) : new Dashboard()
this.selectedWidgets = []
}
@ -208,8 +210,10 @@ export default class DashboardStore implements IDashboardSotre {
return dashboardService.saveDashboard(dashboard).then(_dashboard => {
runInAction(() => {
if (isCreating) {
toast.success('Dashboard created successfully')
this.addDashboard(_dashboard)
} else {
toast.success('Dashboard updated successfully')
this.updateDashboard(_dashboard)
}
})
@ -247,10 +251,15 @@ export default class DashboardStore implements IDashboardSotre {
deleteDashboard(dashboard: Dashboard): Promise<any> {
this.isDeleting = true
return dashboardService.deleteDashboard(dashboard.dashboardId).then(() => {
toast.success('Dashboard deleted successfully')
runInAction(() => {
this.removeDashboard(dashboard)
})
}).finally(() => {
})
.catch(() => {
toast.error('Dashboard could not be deleted')
})
.finally(() => {
runInAction(() => {
this.isDeleting = false
})
@ -364,6 +373,7 @@ export default class DashboardStore implements IDashboardSotre {
deleteDashboardWidget(dashboardId: string, widgetId: string) {
this.isDeleting = true
return dashboardService.deleteWidget(dashboardId, widgetId).then(() => {
toast.success('Widget deleted successfully')
runInAction(() => {
this.selectedDashboard?.removeWidget(widgetId)
})

View file

@ -1,6 +1,7 @@
import { makeAutoObservable, runInAction, observable, action, reaction, computed } from "mobx"
import Widget, { IWidget } from "./types/widget";
import { metricService } from "App/services";
import { toast } from 'react-toastify';
export interface IMetricStore {
paginatedList: any;
@ -136,8 +137,10 @@ export default class MetricStore implements IMetricStore {
return metricService.saveMetric(metric, dashboardId)
.then(() => {
if (wasCreating) {
toast.success('Metric created successfully')
this.addToList(metric)
} else {
toast.success('Metric updated successfully')
this.updateInList(metric)
}
}).finally(() => {

View file

@ -10,6 +10,7 @@ export interface IDashboard {
isValid: boolean
isPinned: boolean
currentWidget: IWidget
config: any
update(data: any): void
toJson(): any
@ -31,12 +32,13 @@ export default class Dashboard implements IDashboard {
public static get ID_KEY():string { return "dashboardId" }
dashboardId: any = undefined
name: string = "New Dashboard"
isPublic: boolean = false
isPublic: boolean = true
widgets: IWidget[] = []
metrics: any[] = []
isValid: boolean = false
isPinned: boolean = false
currentWidget: IWidget = new Widget()
config: any = {}
constructor() {
makeAutoObservable(this, {
@ -75,7 +77,7 @@ export default class Dashboard implements IDashboard {
return {
dashboardId: this.dashboardId,
name: this.name,
isPrivate: this.isPublic,
isPublic: this.isPublic,
// widgets: this.widgets.map(w => w.toJson())
// widgets: this.widgets
metrics: this.metrics
@ -88,6 +90,7 @@ export default class Dashboard implements IDashboard {
this.name = json.name
this.isPublic = json.isPublic
this.isPinned = json.isPinned
this.config = json.config
this.widgets = json.widgets ? json.widgets.map(w => new Widget().fromJson(w)) : []
})
return this

View file

@ -17,6 +17,7 @@ export interface IWidget {
lastModified: Date
dashboards: any[]
dashboardIds: any[]
config: any
position: number
data: any
@ -50,6 +51,7 @@ export default class Widget implements IWidget {
lastModified: Date = new Date()
dashboards: any[] = []
dashboardIds: any[] = []
config: any = {}
position: number = 0
data: any = {}
@ -111,6 +113,8 @@ export default class Widget implements IWidget {
this.dashboards = json.dashboards
this.owner = json.ownerEmail
this.lastModified = DateTime.fromISO(json.editedAt || json.createdAt)
this.config = json.config
this.position = json.config.position
})
return this
}
@ -122,6 +126,7 @@ export default class Widget implements IWidget {
metricOf: this.metricOf,
metricValue: this.metricValue,
metricType: this.metricType,
viewType: this.viewType,
name: this.name,
series: this.series.map((series: any) => series.toJson()),
}

View file

@ -104,7 +104,7 @@ export const dashboard = () => '/dashboard';
export const dashboardMetrics = () => '/dashboard/metrics';
export const dashboardSelected = (id = ':dashboardId', hash) => hashed(`/dashboard/${ id }`, hash);
export const dashboardMetricDetails = (id = ':dashboardId', metricId = ':metricId', hash) => hashed(`/dashboard/${ id }/metric/${metricId}`, hash);
export const dashboardMetricDetails = (dashboardId = ':dashboardId', metricId = ':metricId', hash) => hashed(`/dashboard/${ dashboardId }/metric/${metricId}`, hash);
export const dashboardMetricCreate = (dashboardId = ':dashboardId', hash) => hashed(`/dashboard/${ dashboardId }/metric/create`, hash);
export const metrics = () => `/metrics`;
export const metricCreate = () => `/metrics/create`;