feat(ui) - dashboards wip
This commit is contained in:
parent
af39d9c32f
commit
b551fd5084
13 changed files with 283 additions and 69 deletions
|
|
@ -5,9 +5,9 @@ import { ItemMenu } from 'UI';
|
|||
|
||||
function WidgetWrapper(props) {
|
||||
const { widget } = props;
|
||||
const store: any = useDashboardStore();
|
||||
const dashboard = store.selectedDashboard;
|
||||
const siteId = store.siteId;
|
||||
// const store: any = useDashboardStore();
|
||||
// const dashboard = store.selectedDashboard;
|
||||
// const siteId = store.siteId;
|
||||
|
||||
return (
|
||||
<div className={cn("border rounded bg-white", 'col-span-' + widget.colSpan)} style={{ userSelect: 'none'}}>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,129 @@
|
|||
import React from 'react';
|
||||
import WidgetWrapper from '../../WidgetWrapper';
|
||||
import { useDashboardStore } from '../../store/store';
|
||||
import { useObserver } from 'mobx-react-lite';
|
||||
import cn from 'classnames';
|
||||
import { Button } from 'UI';
|
||||
|
||||
function WidgetCategoryItem({ category, isSelected, onClick, selectedWidgetIds, unSelectCategory }) {
|
||||
const selectedCategoryWidgetsCount = useObserver(() => {
|
||||
return category.widgets.filter(widget => selectedWidgetIds.includes(widget.widgetId)).length;
|
||||
});
|
||||
return (
|
||||
<div
|
||||
className={cn("rounded p-4 shadow border cursor-pointer", { 'bg-active-blue border-color-teal':isSelected, 'bg-white': !isSelected })}
|
||||
onClick={() => onClick(category)}
|
||||
>
|
||||
<div className="font-medium text-lg mb-2">{category.name}</div>
|
||||
<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>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function DashboardMetricSelection(props) {
|
||||
const store: any = useDashboardStore();
|
||||
const widgetCategories = store?.widgetCategories;
|
||||
const widgetTemplates = store?.widgetTemplates;
|
||||
const [activeCategory, setActiveCategory] = React.useState<any>(widgetCategories[0]);
|
||||
const [selectedWidgets, setSelectedWidgets] = React.useState<any>([]);
|
||||
const selectedWidgetIds = selectedWidgets.map((widget: any) => widget.widgetId);
|
||||
|
||||
const removeSelectedWidgetByCategory = (category: any) => {
|
||||
const categoryWidgetIds = category.widgets.map((widget: any) => widget.widgetId);
|
||||
const newSelectedWidgets = selectedWidgets.filter((widget: any) => !categoryWidgetIds.includes(widget.widgetId));
|
||||
setSelectedWidgets(newSelectedWidgets);
|
||||
};
|
||||
|
||||
const toggleWidgetSelection = (widget: any) => {
|
||||
console.log('toggleWidgetSelection', widget.widgetId);
|
||||
if (selectedWidgetIds.includes(widget.widgetId)) {
|
||||
setSelectedWidgets(selectedWidgets.filter((w: any) => w.widgetId !== widget.widgetId));
|
||||
} else {
|
||||
setSelectedWidgets(selectedWidgets.concat(widget));
|
||||
}
|
||||
};
|
||||
|
||||
const handleWidgetCategoryClick = (category: any) => {
|
||||
setActiveCategory(category);
|
||||
};
|
||||
|
||||
const toggleAllWidgets = ({ target: { checked }}) => {
|
||||
if (checked == true) {
|
||||
const allWidgets = widgetCategories.reduce((acc, category) => {
|
||||
return acc.concat(category.widgets);
|
||||
}, []);
|
||||
|
||||
setSelectedWidgets(allWidgets);
|
||||
} else {
|
||||
setSelectedWidgets([]);
|
||||
}
|
||||
}
|
||||
|
||||
return useObserver(() => (
|
||||
<div >
|
||||
<div className="grid grid-cols-12 gap-4 my-3 items-end">
|
||||
<div className="col-span-3">
|
||||
<div className="uppercase color-gray-medium text-lg">Categories</div>
|
||||
</div>
|
||||
|
||||
<div className="col-span-9 flex items-center">
|
||||
<div className="flex items-center">
|
||||
<h2 className="text-2xl">Errors Tracking</h2>
|
||||
<span className="text-2xl color-gray-medium ml-2">12</span>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-12 gap-4">
|
||||
<div className="col-span-3">
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
{widgetCategories.map((category, index) =>
|
||||
<WidgetCategoryItem
|
||||
key={category.categoryId}
|
||||
onClick={handleWidgetCategoryClick}
|
||||
category={category}
|
||||
isSelected={activeCategory.categoryId === category.categoryId}
|
||||
selectedWidgetIds={selectedWidgetIds}
|
||||
unSelectCategory={removeSelectedWidgetByCategory}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-9">
|
||||
<div className="grid grid-cols-2 gap-4 -mx-4 px-4">
|
||||
{activeCategory.widgets.map((widget: any) => (
|
||||
<div
|
||||
key={widget.widgetId}
|
||||
className={cn("border rounded cursor-pointer", { 'border-color-teal' : selectedWidgetIds.includes(widget.widgetId) })}
|
||||
onClick={() => toggleWidgetSelection(widget)}
|
||||
>
|
||||
<WidgetWrapper widget={widget} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex absolute bottom-0 left-0 right-0 bg-white border-t p-3">
|
||||
<Button primary className="">
|
||||
Add Selected to Dashboard
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
));
|
||||
}
|
||||
|
||||
export default DashboardMetricSelection;
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from './DashboardMetricSelection';
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import React from 'react';
|
||||
import WidgetWrapper from '../../WidgetWrapper';
|
||||
import { useDashboardStore } from '../../store/store';
|
||||
import { observer, useObserver } from 'mobx-react-lite';
|
||||
import cn from 'classnames';
|
||||
import { Button } from 'UI';
|
||||
import DashboardMetricSelection from '../DashboardMetricSelection';
|
||||
|
||||
|
||||
|
||||
function DashboardModal(props) {
|
||||
const store: any = useDashboardStore();
|
||||
|
||||
|
||||
return useObserver(() => (
|
||||
<div className="fixed border-r shadow p-4 h-screen" style={{ backgroundColor: '#FAFAFA', zIndex: '9999', width: '80%'}}>
|
||||
<div className="mb-6">
|
||||
<h1 className="text-2xl">Add Metric to Dashboard</h1>
|
||||
</div>
|
||||
<DashboardMetricSelection />
|
||||
</div>
|
||||
));
|
||||
}
|
||||
|
||||
export default observer(DashboardModal);
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from './DashboardModal'
|
||||
|
|
@ -4,24 +4,17 @@ import { observer } from 'mobx-react-lite';
|
|||
import { withDashboardStore } from '../../store/store';
|
||||
import { Button, PageTitle, Link } from 'UI';
|
||||
import { withSiteId, dashboardMetricCreate } from 'App/routes';
|
||||
import { ModalContext } from "App/components/Modal/modalContext";
|
||||
|
||||
const ModalContent = () => {
|
||||
let { handleModal } = React.useContext(ModalContext);
|
||||
return (
|
||||
<div className="h-screen bg-white relative p-5 shadow-lg" style={{ width: '300px'}}>
|
||||
Hello this is test
|
||||
</div>
|
||||
)
|
||||
}
|
||||
import withModal from 'App/components/Modal/withModal';
|
||||
import DashboardModal from '../DashboardModal'
|
||||
|
||||
function DashboardView(props) {
|
||||
let { handleModal } = React.useContext(ModalContext);
|
||||
// let { handleModal } = React.useContext(ModalContext);
|
||||
const { store } = props;
|
||||
const dashboard = store.selectedDashboard
|
||||
const list = dashboard?.widgets;
|
||||
useEffect(() => {
|
||||
handleModal(<ModalContent />)
|
||||
// handleModal(<ModalContent />)
|
||||
props.showModal(DashboardModal)
|
||||
}, [])
|
||||
return (
|
||||
<div>
|
||||
|
|
@ -36,4 +29,4 @@ function DashboardView(props) {
|
|||
)
|
||||
}
|
||||
|
||||
export default withDashboardStore(observer(DashboardView));
|
||||
export default withDashboardStore(withModal(observer(DashboardView)));
|
||||
|
|
@ -2,14 +2,14 @@ import { makeAutoObservable, runInAction, observable, action, reaction } from "m
|
|||
import Dashboard from "./dashboard"
|
||||
import APIClient from 'App/api_client';
|
||||
import Widget from "./widget";
|
||||
|
||||
export default class DashboardStore {
|
||||
dashboards: Dashboard[] = []
|
||||
widgets: Widget[] = []
|
||||
widgetTemplates: any[] = []
|
||||
selectedDashboard: Dashboard | null = new Dashboard()
|
||||
isLoading: boolean = false
|
||||
siteId: any = null
|
||||
currentWidget: Widget = new Widget()
|
||||
widgetCategories: any[] = []
|
||||
|
||||
private client = new APIClient()
|
||||
|
||||
|
|
@ -50,16 +50,27 @@ export default class DashboardStore {
|
|||
// this.selectedDashboard?.swapWidgetPosition(2, 0)
|
||||
// }, 3000)
|
||||
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const widget = getRandomWidget();
|
||||
widget.position = i;
|
||||
widget.name = `Widget ${i}`;
|
||||
widget.isPrivate = [true, false][Math.floor(Math.random() * 2)];
|
||||
widget.dashboardIds = [this.selectedDashboard?.dashboardId];
|
||||
widget.owner = ["John", "Jane", "Jack", "Jill"][i % 4];
|
||||
widget.metricType = ['timeseries', 'table'][Math.floor(Math.random() * 2)];
|
||||
this.widgets.push(widget);
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const cat: any = {
|
||||
name: `Category ${i + 1}`,
|
||||
categoryId: i,
|
||||
description: `Category ${i + 1} description`,
|
||||
widgets: []
|
||||
}
|
||||
// const randomBumberBetween = (min: number, max: number) => Math.floor(Math.random() * (max - min + 1)) + min
|
||||
const randomNumber = Math.floor(Math.random() * (5 - 2 + 1)) + 2
|
||||
|
||||
for (let j = 0; j < randomNumber; j++) {
|
||||
const widget: any= {};
|
||||
widget.widgetId = `${i}-${j}`
|
||||
widget.name = `Widget ${i}-${j}`;
|
||||
widget.metricType = ['timeseries', 'table'][Math.floor(Math.random() * 2)];
|
||||
cat.widgets.push(widget);
|
||||
}
|
||||
|
||||
this.widgetCategories.push(cat)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
resetCurrentWidget() {
|
||||
|
|
|
|||
|
|
@ -1,23 +1,16 @@
|
|||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { ModalContext } from "./modalContext";
|
||||
import ModalOverlay from "./ModalOverlay";
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
const Modal = () => {
|
||||
let { modalContent, handleModal, modal } = React.useContext(ModalContext);
|
||||
if (modal) {
|
||||
export default class Modal extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.el = document.createElement('div');
|
||||
}
|
||||
|
||||
render() {
|
||||
return ReactDOM.createPortal(
|
||||
<div
|
||||
className="fixed top-0 left-0 h-screen relative"
|
||||
style={{ background: "rgba(0,0,0,0.8)", zIndex: '9999' }}
|
||||
>
|
||||
<ModalOverlay handleModal={handleModal}>
|
||||
{modalContent}
|
||||
</ModalOverlay>
|
||||
</div>,
|
||||
document.querySelector("#modal-root")
|
||||
this.props.children,
|
||||
this.el,
|
||||
);
|
||||
} else return null;
|
||||
};
|
||||
|
||||
export default Modal;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +1,40 @@
|
|||
import React from "react";
|
||||
import useModal from "./useModal";
|
||||
import Modal from "./modal";
|
||||
import React, { Component, createContext } from 'react';
|
||||
|
||||
let ModalContext;
|
||||
let { Provider } = (ModalContext = React.createContext());
|
||||
const ModalContext = createContext({
|
||||
component: null,
|
||||
props: {},
|
||||
showModal: () => {},
|
||||
hideModal: () => {}
|
||||
});
|
||||
|
||||
let ModalProvider = ({ children }) => {
|
||||
let { modal, handleModal, modalContent } = useModal();
|
||||
return (
|
||||
<Provider value={{ modal, handleModal, modalContent }}>
|
||||
<Modal />
|
||||
{children}
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
export class ModalProvider extends Component {
|
||||
showModal = (component, props = {}) => {
|
||||
this.setState({
|
||||
component,
|
||||
props
|
||||
});
|
||||
};
|
||||
|
||||
export { ModalContext, ModalProvider };
|
||||
hideModal = () =>
|
||||
this.setState({
|
||||
component: null,
|
||||
props: {}
|
||||
});
|
||||
|
||||
state = {
|
||||
component: null,
|
||||
props: {},
|
||||
showModal: this.showModal,
|
||||
hideModal: this.hideModal
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ModalContext.Provider value={this.state}>
|
||||
{this.props.children}
|
||||
</ModalContext.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const ModalConsumer = ModalContext.Consumer;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
import React from 'react';
|
||||
import { ModalConsumer } from './ModalContext';
|
||||
|
||||
const ModalRoot = () => (
|
||||
<ModalConsumer>
|
||||
{({ component: Component, props, hideModal }) =>
|
||||
|
|
@ -6,4 +9,4 @@ const ModalRoot = () => (
|
|||
</ModalConsumer>
|
||||
);
|
||||
|
||||
export default ModalRoot
|
||||
export default ModalRoot;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@ import store from './store';
|
|||
import Router from './Router';
|
||||
import DashboardStore from './components/Dashboard/store';
|
||||
import { DashboardStoreProvider } from './components/Dashboard/store/store';
|
||||
import { ModalProvider } from './components/Modal/modalContext';
|
||||
import { ModalProvider } from './components/Modal/ModalContext';
|
||||
import ModalRoot from './components/Modal/ModalRoot';
|
||||
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
|
|
@ -15,11 +16,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
render(
|
||||
(
|
||||
<Provider store={ store }>
|
||||
<ModalProvider>
|
||||
<DashboardStoreProvider store={ dashboardStore }>
|
||||
<Router />
|
||||
</DashboardStoreProvider>
|
||||
</ModalProvider>
|
||||
<DashboardStoreProvider store={ dashboardStore }>
|
||||
<ModalProvider>
|
||||
<ModalRoot />
|
||||
<Router />
|
||||
</ModalProvider>
|
||||
</DashboardStoreProvider>
|
||||
</Provider>
|
||||
),
|
||||
document.getElementById('app'),
|
||||
|
|
|
|||
|
|
@ -62,6 +62,37 @@
|
|||
.color-white { color: $white }
|
||||
.color-borderColor { color: $borderColor }
|
||||
|
||||
/* border-color */
|
||||
.border-color-main { border-color: $main }
|
||||
.border-color-gray-light-shade { border-color: $gray-light-shade }
|
||||
.border-color-gray-lightest { border-color: $gray-lightest }
|
||||
.border-color-gray-light { border-color: $gray-light }
|
||||
.border-color-gray-medium { border-color: $gray-medium }
|
||||
.border-color-gray-dark { border-color: $gray-dark }
|
||||
.border-color-gray-darkest { border-color: $gray-darkest }
|
||||
.border-color-teal { border-color: $teal }
|
||||
.border-color-teal-dark { border-color: $teal-dark }
|
||||
.border-color-teal-light { border-color: $teal-light }
|
||||
.border-color-tealx { border-color: $tealx }
|
||||
.border-color-tealx-light { border-color: $tealx-light }
|
||||
.border-color-tealx-light-border { border-color: $tealx-light-border }
|
||||
.border-color-orange { border-color: $orange }
|
||||
.border-color-yellow { border-color: $yellow }
|
||||
.border-color-yellow2 { border-color: $yellow2 }
|
||||
.border-color-orange-dark { border-color: $orange-dark }
|
||||
.border-color-green { border-color: $green }
|
||||
.border-color-green2 { border-color: $green2 }
|
||||
.border-color-green-dark { border-color: $green-dark }
|
||||
.border-color-red { border-color: $red }
|
||||
.border-color-red2 { border-color: $red2 }
|
||||
.border-color-blue { border-color: $blue }
|
||||
.border-color-blue2 { border-color: $blue2 }
|
||||
.border-color-active-blue { border-color: $active-blue }
|
||||
.border-color-active-blue-border { border-color: $active-blue-border }
|
||||
.border-color-pink { border-color: $pink }
|
||||
.border-color-white { border-color: $white }
|
||||
.border-color-borderColor { border-color: $borderColor }
|
||||
|
||||
/* color */
|
||||
.hover-main:hover { color: $main }
|
||||
.hover-gray-light-shade:hover { color: $gray-light-shade }
|
||||
|
|
|
|||
|
|
@ -12,6 +12,9 @@ ${ colors.map(color => `.fill-${ color } { fill: $${ color } }`).join('\n') }
|
|||
/* color */
|
||||
${ colors.map(color => `.color-${ color } { color: $${ color } }`).join('\n') }
|
||||
|
||||
/* border-color */
|
||||
${ colors.map(color => `.border-color-${ color } { border-color: $${ color } }`).join('\n') }
|
||||
|
||||
/* color */
|
||||
${ colors.map(color => `.hover-${ color }:hover { color: $${ color } }`).join('\n') }
|
||||
`)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue