From 94370f028f09ccab9f29c15b2ec5f9a0298c1c68 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Mon, 21 Mar 2022 12:28:27 +0100 Subject: [PATCH] feat(ui) - dashboards wip --- frontend/app/Router.js | 16 ++++- .../app/components/Dashboard/NewDashboard.tsx | 61 ++++++++++++------- .../Dashboard/SideMenu/SideMenuSection.js | 2 +- .../Dashboard/WidgetView/WidgetView.tsx | 13 +++- .../Dashboard/WidgetWrapper/WidgetWrapper.tsx | 30 +++++---- .../DashboardSideMenu/DashboardSideMenu.tsx | 36 +++++++---- .../DashboardView/DashboardView.tsx | 5 +- .../components/Dashboard/store/dashboard.ts | 3 +- .../Dashboard/store/dashboardStore.ts | 51 +++++++++++----- .../app/components/Dashboard/store/widget.ts | 2 +- .../app/components/ui/ItemMenu/ItemMenu.js | 21 +++++-- .../app/components/ui/ItemMenu/itemMenu.css | 2 +- .../ui/SideMenuitem/SideMenuitem.js | 32 +++++++--- .../ui/SideMenuitem/sideMenuItem.css | 10 ++- frontend/app/routes.js | 26 +++++++- frontend/app/svg/icons/pin-fill.svg | 3 + 16 files changed, 223 insertions(+), 90 deletions(-) create mode 100644 frontend/app/svg/icons/pin-fill.svg diff --git a/frontend/app/Router.js b/frontend/app/Router.js index 665ff4df6..a6a9ba090 100644 --- a/frontend/app/Router.js +++ b/frontend/app/Router.js @@ -48,8 +48,13 @@ const FunnelIssue = withSiteIdUpdater(FunnelIssueDetails); const withSiteId = routes.withSiteId; const withObTab = routes.withObTab; -const DASHBOARD_PATH = routes.dashboardSelected(); -const WIDGET_PATAH = routes.dashboardMetric(); +const DASHBOARD_PATH = routes.dashboard(); +// const DASHBOARD_WIDGET_CREATE_PATH = routes.dashboardMetricCreate(); +// const DASHBOARD_WIDGET_DETAILS_PATH = routes.dashboardMetricDetails(); +// const METRIC_CREATE_PATH = routes.metricCreate(); +// const METRIC_DETAILS_PATH = routes.metricDetails(); + +// const WIDGET_PATAH = routes.dashboardMetric(); const SESSIONS_PATH = routes.sessions(); const ASSIST_PATH = routes.assist(); const ERRORS_PATH = routes.errors(); @@ -182,8 +187,13 @@ class Router extends React.Component { { siteIdList.length === 0 && } - + + {/* + + + */} + diff --git a/frontend/app/components/Dashboard/NewDashboard.tsx b/frontend/app/components/Dashboard/NewDashboard.tsx index 11b2313e5..831e44bd8 100644 --- a/frontend/app/components/Dashboard/NewDashboard.tsx +++ b/frontend/app/components/Dashboard/NewDashboard.tsx @@ -1,46 +1,63 @@ import React, { useEffect } from 'react'; import { Switch, Route, Redirect } from 'react-router'; import withPageTitle from 'HOCs/withPageTitle'; -import { observer, useObserver } from "mobx-react-lite"; -import { withDashboardStore } from './store/store'; +import { observer } from "mobx-react-lite"; +import { useDashboardStore } from './store/store'; import { withRouter } from 'react-router-dom'; import DashboardView from './components/DashboardView'; -import { dashboardSelected, dashboardMetric, withSiteId } from 'App/routes'; +import { dashboardSelected, dashboardMetricDetails, dashboardMetricCreate, withSiteId } from 'App/routes'; import DashboardSideMenu from './components/DashboardSideMenu'; +import WidgetView from './WidgetView'; function NewDashboard(props) { - const { store, match: { params: { siteId, dashboardId, metricId } } } = props; + const { match: { params: { siteId, dashboardId, metricId } } } = props; + const store: any = useDashboardStore(); const dashboard = store.selectedDashboard; useEffect(() => { store.setSiteId(siteId); - if (dashboardId) { - store.selectDashboardById(dashboardId); - } else { - store.selectDefaultDashboard(); - } - }, [dashboardId]); + }, []); - return useObserver(() => ( + useEffect(() => { + console.log('dashboardId', dashboardId); + if (!dashboard || !dashboard.dashboardId) { + if (dashboardId) { + store.selectDashboardById(dashboardId); + } else { + store.selectDefaultDashboard(); + } + } + + }, [dashboard]); + + return (
- - - - - -

Metric

-
- -
+ { dashboard && dashboard.dashboardId && ( + + + + + + + + + + + {/* + + */} + + + )}
- )); + ); } export default withPageTitle('New Dashboard')( - withRouter(withDashboardStore(NewDashboard)) + withRouter(observer(NewDashboard)) ); \ No newline at end of file diff --git a/frontend/app/components/Dashboard/SideMenu/SideMenuSection.js b/frontend/app/components/Dashboard/SideMenu/SideMenuSection.js index e26e24da2..801c02415 100644 --- a/frontend/app/components/Dashboard/SideMenu/SideMenuSection.js +++ b/frontend/app/components/Dashboard/SideMenu/SideMenuSection.js @@ -22,7 +22,7 @@ function SideMenuSection({ title, items, onItemClick, setShowAlerts, siteId }) { )}
-
+
- Widget view +
+
+

{widget.name}

+
+
); } -export default WidgetView; \ No newline at end of file +export default withRouter(WidgetView); \ No newline at end of file diff --git a/frontend/app/components/Dashboard/WidgetWrapper/WidgetWrapper.tsx b/frontend/app/components/Dashboard/WidgetWrapper/WidgetWrapper.tsx index 35e088d52..daade0774 100644 --- a/frontend/app/components/Dashboard/WidgetWrapper/WidgetWrapper.tsx +++ b/frontend/app/components/Dashboard/WidgetWrapper/WidgetWrapper.tsx @@ -1,9 +1,7 @@ import React from 'react'; -import { observer } from "mobx-react-lite"; import { useDashboardStore } from '../store/store'; import cn from 'classnames'; -import { Link } from 'UI'; -import { dashboardMetric, withSiteId } from 'App/routes'; +import { ItemMenu } from 'UI'; function WidgetWrapper(props) { const { widget } = props; @@ -12,23 +10,33 @@ function WidgetWrapper(props) { const siteId = store.siteId; return ( -
- -
+
+ {/* */} +
{widget.name} - {widget.position}
- + */}
-
+
- + {/* */}
); } -export default observer(WidgetWrapper); \ No newline at end of file +export default WidgetWrapper; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx b/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx index 850423c66..4c984904e 100644 --- a/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx +++ b/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx @@ -1,39 +1,51 @@ -import { useObserver } from 'mobx-react-lite'; +import { useObserver, observer, useLocalObservable } from 'mobx-react-lite'; import React from 'react'; -import { SideMenuitem, SideMenuHeader } from 'UI'; +import { SideMenuitem, SideMenuHeader, Icon } from 'UI'; import { withDashboardStore } from '../../store/store'; +import { withRouter } from 'react-router-dom'; +import { withSiteId, dashboardSelected } from 'App/routes'; function DashboardSideMenu(props) { - const { store } = props; - const { selectedDashboard } = store; + const { store, history } = props; + const { dashboardId } = store.selectedDashboard; const onItemClick = (dashboard) => { store.selectDashboardById(dashboard.dashboardId); + const path = withSiteId(dashboardSelected(dashboard.dashboardId), parseInt(store.siteId)); + // console.log('path', path); + // history.push(path); }; - return useObserver(() => ( + return (
{store.dashboards.map(item => ( onItemClick(item)} + + leading = {( +
+
+ {item.isPinned &&
} +
+ )} /> ))}
-
+
setShowAlerts(true)} - /> + />
-
+
- )); + ); } -export default withDashboardStore(DashboardSideMenu); \ No newline at end of file +export default withDashboardStore(withRouter(observer(DashboardSideMenu))); \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx index ce56053d8..e0d45a531 100644 --- a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx +++ b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx @@ -2,7 +2,8 @@ import React from 'react'; import WidgetWrapper from '../../WidgetWrapper'; import { observer } from 'mobx-react-lite'; import { withDashboardStore } from '../../store/store'; -import { Button, PageTitle } from 'UI'; +import { Button, PageTitle, Link } from 'UI'; +import { withSiteId, dashboardMetricCreate } from 'App/routes'; function DashboardView(props) { const { store } = props; @@ -12,7 +13,7 @@ function DashboardView(props) {
- +
{list && list.map(item => )} diff --git a/frontend/app/components/Dashboard/store/dashboard.ts b/frontend/app/components/Dashboard/store/dashboard.ts index 9395b7c1b..b8113f3ba 100644 --- a/frontend/app/components/Dashboard/store/dashboard.ts +++ b/frontend/app/components/Dashboard/store/dashboard.ts @@ -1,4 +1,4 @@ -import { makeAutoObservable, makeObservable, observable, action, runInAction, computed, reaction } from "mobx" +import { makeAutoObservable, observable, action, runInAction } from "mobx" import Widget from "./widget" // import APIClient from 'App/api_client'; @@ -9,6 +9,7 @@ export default class Dashboard { widgets: Widget[] = [] isValid: boolean = false isPinned: boolean = false + currentWidget: Widget = new Widget() constructor() { makeAutoObservable(this, { diff --git a/frontend/app/components/Dashboard/store/dashboardStore.ts b/frontend/app/components/Dashboard/store/dashboardStore.ts index 0fc775e1e..832988ce9 100644 --- a/frontend/app/components/Dashboard/store/dashboardStore.ts +++ b/frontend/app/components/Dashboard/store/dashboardStore.ts @@ -8,6 +8,7 @@ export default class DashboardStore { selectedDashboard: Dashboard | null = new Dashboard() isLoading: boolean = false siteId: any = null + currentWidget: Widget = new Widget() private client = new APIClient() @@ -25,7 +26,8 @@ export default class DashboardStore { getDashboardByIndex: action, getDashboardCount: action, getDashboardIndexByDashboardId: action, - selectDashboardById: action, + selectDashboardById: action, + selectDefaultDashboard: action, toJson: action, fromJson: action, setSiteId: action, @@ -34,7 +36,7 @@ export default class DashboardStore { // TODO remove this sample data this.dashboards = sampleDashboards - this.selectedDashboard = sampleDashboards[0] + // this.selectedDashboard = sampleDashboards[0] // setInterval(() => { // this.selectedDashboard?.addWidget(getRandomWidget()) @@ -185,7 +187,7 @@ export default class DashboardStore { } selectDashboardById = (dashboardId: any) => { - this.selectedDashboard = this.dashboards.find(d => d.dashboardId == dashboardId) || null;; + this.selectedDashboard = this.dashboards.find(d => d.dashboardId == dashboardId) || new Dashboard(); } setSiteId = (siteId: any) => { @@ -193,15 +195,13 @@ export default class DashboardStore { } selectDefaultDashboard = () => { - const pinnedDashboard = this.dashboards.find(d => d.isPinned) if (this.dashboards.length > 0) { + const pinnedDashboard = this.dashboards.find(d => d.isPinned) if (pinnedDashboard) { this.selectedDashboard = pinnedDashboard } else { this.selectedDashboard = this.dashboards[0] } - } else { - this.selectedDashboard = new Dashboard() } } } @@ -209,17 +209,38 @@ export default class DashboardStore { function getRandomWidget() { const widget = new Widget(); widget.widgetId = Math.floor(Math.random() * 100); - widget.name = "Widget " + Math.floor(Math.random() * 100); + widget.name = randomMetricName(); widget.type = "random"; widget.colSpan = Math.floor(Math.random() * 2) + 1; return widget; } -function getRandomDashboard(id: any = null) { +function generateRandomPlaceName() { + const placeNames = [ + "New York", + "Los Angeles", + "Chicago", + "Houston", + "Philadelphia", + "Phoenix", + "San Antonio", + "San Diego", + ] + return placeNames[Math.floor(Math.random() * placeNames.length)] +} + + +function randomMetricName () { + const metrics = ["Revenue", "Profit", "Expenses", "Sales", "Orders", "Revenue", "Profit", "Expenses", "Sales", "Orders", "Revenue", "Profit", "Expenses", "Sales", "Orders", "Revenue", "Profit", "Expenses", "Sales", "Orders"]; + return metrics[Math.floor(Math.random() * metrics.length)]; +} + +function getRandomDashboard(id: any = null, isPinned = false) { const dashboard = new Dashboard(); - dashboard.name = "Random Dashboard"; - dashboard.dashboardId = id ? id : "random-dashboard-" + Math.floor(Math.random() * 10); - for (let i = 0; i < 10; i++) { + dashboard.name = generateRandomPlaceName(); + dashboard.dashboardId = id ? id : Math.floor(Math.random() * 10); + dashboard.isPinned = isPinned; + for (let i = 0; i < 8; i++) { const widget = getRandomWidget(); widget.position = i; dashboard.addWidget(widget); @@ -228,8 +249,8 @@ function getRandomDashboard(id: any = null) { } const sampleDashboards = [ - getRandomDashboard(12), - getRandomDashboard(), - getRandomDashboard(), - getRandomDashboard(), + getRandomDashboard(1, true), + getRandomDashboard(2), + getRandomDashboard(3), + getRandomDashboard(4), ] \ No newline at end of file diff --git a/frontend/app/components/Dashboard/store/widget.ts b/frontend/app/components/Dashboard/store/widget.ts index 6326dd6db..2cd1bee32 100644 --- a/frontend/app/components/Dashboard/store/widget.ts +++ b/frontend/app/components/Dashboard/store/widget.ts @@ -2,7 +2,7 @@ import { makeAutoObservable, runInAction, observable, action, reaction } from "m export default class Widget { widgetId: any = undefined - name: string = "" + name: string = "New Metric" type: string = "" position: number = 0 data: any = {} diff --git a/frontend/app/components/ui/ItemMenu/ItemMenu.js b/frontend/app/components/ui/ItemMenu/ItemMenu.js index c31130071..94274a405 100644 --- a/frontend/app/components/ui/ItemMenu/ItemMenu.js +++ b/frontend/app/components/ui/ItemMenu/ItemMenu.js @@ -39,13 +39,22 @@ export default class ItemMenu extends React.PureComponent { return (
-
{ this.menuBtnRef = ref; } } className={ styles.menuBtn } onClick={ this.toggleMenu } role="button" tabIndex="-1" - /> + /> */} +
{ 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" + > + +
-
- -
+ { icon && ( +
+ +
+ )}
{ text }
))} diff --git a/frontend/app/components/ui/ItemMenu/itemMenu.css b/frontend/app/components/ui/ItemMenu/itemMenu.css index bb9bd6426..eabfb050a 100644 --- a/frontend/app/components/ui/ItemMenu/itemMenu.css +++ b/frontend/app/components/ui/ItemMenu/itemMenu.css @@ -6,7 +6,7 @@ } .menuBtn { - @mixin icon-before ellipsis-v, $gray-darkest, 25px { + @mixin icon-before ellipsis-v, $gray-darkest, 18px { margin: 5px; } width: 36px; diff --git a/frontend/app/components/ui/SideMenuitem/SideMenuitem.js b/frontend/app/components/ui/SideMenuitem/SideMenuitem.js index 978e02fbd..dbeca551e 100644 --- a/frontend/app/components/ui/SideMenuitem/SideMenuitem.js +++ b/frontend/app/components/ui/SideMenuitem/SideMenuitem.js @@ -3,7 +3,20 @@ import { Icon, Popup } from 'UI'; import cn from 'classnames'; import stl from './sideMenuItem.css'; -function SideMenuitem({ iconBg = false, iconColor = "gray-dark", iconSize = 18, className, iconName = null, title, active = false, disabled = false, onClick, deleteHandler, ...props }) { +function SideMenuitem({ + iconBg = false, + iconColor = "gray-dark", + iconSize = 18, + className, + iconName = null, + title, + active = false, + disabled = false, + onClick, + deleteHandler, + leading = null, + ...props + }) { return ( -
+
+
{ iconName && ( -
-
- -
- )} - { title } +
+
+ +
+ )} + { title } +
+ { leading && leading }
{deleteHandler &&
diff --git a/frontend/app/components/ui/SideMenuitem/sideMenuItem.css b/frontend/app/components/ui/SideMenuitem/sideMenuItem.css index f666a1477..e0780c68e 100644 --- a/frontend/app/components/ui/SideMenuitem/sideMenuItem.css +++ b/frontend/app/components/ui/SideMenuitem/sideMenuItem.css @@ -5,10 +5,14 @@ cursor: pointer; &:hover { - color: $teal; - & svg { - fill: $teal; + & .iconLabel { + color: $teal; + + & svg { + fill: $teal; + } } + & .actions { opacity: 1; } diff --git a/frontend/app/routes.js b/frontend/app/routes.js index 3ee5b6a41..8693ae3e6 100644 --- a/frontend/app/routes.js +++ b/frontend/app/routes.js @@ -102,14 +102,34 @@ export const testBuilder = (testId = ':testId') => `/test-builder/${ testId }`; export const dashboard = () => '/dashboard'; export const dashboardSelected = (id = ':dashboardId', hash) => hashed(`/dashboard/${ id }`, hash); -export const dashboardMetric = (id = ':dashboardId', metricId = ':metricId', hash) => hashed(`/dashboard/${ id }/metric/${metricId}`, hash); + +export const dashboardMetricDetails = (id = ':dashboardId', metricId = ':metricId', hash) => hashed(`/dashboard/${ id }/metric/${metricId}`, hash); +export const dashboardMetricCreate = (id = ':dashboardId', hash) => hashed(`/dashboard/${ id }/metric/create`, hash); +export const metricCreate = () => `/metric/create`; +export const metricDetails = (id = ':metricId', hash) => hashed(`/metric/${ id }`, hash); + export const RESULTS_QUERY_KEY = 'results'; export const METRICS_QUERY_KEY = 'metrics'; export const SOURCE_QUERY_KEY = 'source'; export const WIDGET_QUERY_KEY = 'widget'; -const REQUIRED_SITE_ID_ROUTES = [ liveSession(''), session(''), sessions(), assist(), dashboard(''), error(''), errors(), onboarding(''), funnel(''), funnelIssue(''), ]; +const REQUIRED_SITE_ID_ROUTES = [ + liveSession(''), + session(''), + sessions(), + assist(), + dashboard(''), + dashboardSelected(''), + // dashboardMetricCreate(''), + dashboardMetricDetails(''), + metricCreate(''), + error(''), + errors(), + onboarding(''), + funnel(''), + funnelIssue(''), + ]; const routeNeedsSiteId = path => REQUIRED_SITE_ID_ROUTES.some(r => path.startsWith(r)); const siteIdToUrl = (siteId = ':siteId') => { if (Array.isArray(siteId)) { @@ -132,7 +152,7 @@ export function isRoute(route, path){ routeParts.every((p, i) => p.startsWith(':') || p === pathParts[ i ]); } -const SITE_CHANGE_AVALIABLE_ROUTES = [ sessions(), assist(), dashboard(), errors(), onboarding('')]; +const SITE_CHANGE_AVALIABLE_ROUTES = [ sessions(), assist(), dashboard(), dashboardSelected(''), errors(), onboarding('')]; export const siteChangeAvaliable = path => SITE_CHANGE_AVALIABLE_ROUTES.some(r => isRoute(r, path)); export const redirects = Object.entries({ diff --git a/frontend/app/svg/icons/pin-fill.svg b/frontend/app/svg/icons/pin-fill.svg new file mode 100644 index 000000000..b5ea87670 --- /dev/null +++ b/frontend/app/svg/icons/pin-fill.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file