feat(ui) - dashboards wip

This commit is contained in:
Shekar Siri 2022-03-25 18:57:23 +01:00
parent af39d9c32f
commit b551fd5084
13 changed files with 283 additions and 69 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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') }
`)