feat(ui) - dashboard - wip
This commit is contained in:
parent
3dd8b04039
commit
caa58d1f98
25 changed files with 310 additions and 94 deletions
|
|
@ -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 } />
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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...',
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from './DashboardEditModal'
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) => (
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 () => {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from './WidgetName';
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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)}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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(() => {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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`;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue