feat(ui) - dashboards - widget drap and other changes
This commit is contained in:
parent
3efe0aed5b
commit
6607d3cb74
17 changed files with 188 additions and 60 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { DNDSource, DNDTarget } from 'Components/hocs/dnd';
|
||||
// import { DNDSource, DNDTarget } from 'Components/hocs/dnd';
|
||||
import Event, { TYPES } from 'Types/filter/event';
|
||||
import { operatorOptions } from 'Types/filter';
|
||||
import { editEvent, removeEvent, clearEvents, applyFilter } from 'Duck/filters';
|
||||
|
|
@ -25,8 +25,8 @@ const getLabel = ({ type }) => {
|
|||
return getPlaceholder({ type });
|
||||
};
|
||||
|
||||
@DNDTarget('event')
|
||||
@DNDSource('event')
|
||||
// @DNDTarget('event')
|
||||
// @DNDSource('event')
|
||||
@connect(state => ({
|
||||
isLastEvent: state.getIn([ 'filters', 'appliedFilter', 'events' ]).size === 1,
|
||||
}), { editEvent, removeEvent, clearEvents, applyFilter })
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { Input } from 'semantic-ui-react';
|
||||
import { DNDContext } from 'Components/hocs/dnd';
|
||||
// import { DNDContext } from 'Components/hocs/dnd';
|
||||
import {
|
||||
addEvent, applyFilter, moveEvent, clearEvents, edit,
|
||||
addCustomFilter, addAttribute, setSearchQuery, setActiveFlow, setFilterOption
|
||||
|
|
@ -45,7 +45,7 @@ import SaveFilterButton from 'Shared/SaveFilterButton';
|
|||
setBlink,
|
||||
edit,
|
||||
})
|
||||
@DNDContext
|
||||
// @DNDContext
|
||||
export default class EventFilter extends React.PureComponent {
|
||||
state = { search: '', showFilterModal: false, showPlacehoder: true }
|
||||
fetchEventList = debounce(this.props.fetchEventList, 500)
|
||||
|
|
|
|||
|
|
@ -26,7 +26,10 @@ function NewDashboard(props) {
|
|||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!dashboard || !dashboard.dashboardId) {
|
||||
if (dashboardId) {
|
||||
store.selectDashboardById(dashboardId);
|
||||
}
|
||||
if (!dashboardId) {
|
||||
if (dashboardId) {
|
||||
store.selectDashboardById(dashboardId);
|
||||
} else {
|
||||
|
|
@ -35,19 +38,9 @@ function NewDashboard(props) {
|
|||
});
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
// if (dashboard) {
|
||||
// if (dashboard.dashboardId !== dashboardId) {
|
||||
// history.push(withSiteId(dashboardSelected(dashboard.dashboardId), parseInt(siteId)));
|
||||
// }
|
||||
|
||||
// history.replace(withSiteId(dashboardSelected(dashboard.dashboardId), parseInt(siteId)));
|
||||
// }
|
||||
|
||||
// console.log('dashboard', dashboard)
|
||||
}, [dashboard]);
|
||||
|
||||
console.log('rendering dashboard', props.match.params);
|
||||
// console.log('rendering dashboard', props.match.params);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -1,18 +1,61 @@
|
|||
import React from 'react';
|
||||
import React, { useRef } from 'react';
|
||||
import { useDashboardStore } from '../store/store';
|
||||
import cn from 'classnames';
|
||||
import { ItemMenu } from 'UI';
|
||||
import { useDrag, useDrop } from 'react-dnd';
|
||||
|
||||
function WidgetWrapper(props) {
|
||||
const { widget } = props;
|
||||
// const store: any = useDashboardStore();
|
||||
// const dashboard = store.selectedDashboard;
|
||||
// const siteId = store.siteId;
|
||||
const { widget, index, moveListItem } = props;
|
||||
|
||||
// useDrag - the list item is draggable
|
||||
const [{ opacity, isDragging }, dragRef] = useDrag({
|
||||
type: 'item',
|
||||
item: { index },
|
||||
collect: (monitor) => ({
|
||||
isDragging: monitor.isDragging(),
|
||||
opacity: monitor.isDragging() ? 0.5 : 1,
|
||||
}),
|
||||
}, [index]);
|
||||
|
||||
// useDrop - the list item is also a drop area
|
||||
const [spec, dropRef] = useDrop({
|
||||
accept: 'item',
|
||||
drop: (item: any) => {
|
||||
if (item.index === index) return;
|
||||
moveListItem(item.index, index);
|
||||
},
|
||||
// hover: (item: any, monitor: any) => {
|
||||
// const dragIndex = item.index
|
||||
// const hoverIndex = index
|
||||
// const hoverBoundingRect = ref.current?.getBoundingClientRect()
|
||||
// const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2
|
||||
// const hoverActualY = monitor.getClientOffset().y - hoverBoundingRect.top
|
||||
|
||||
// // if dragging down, continue only when hover is smaller than middle Y
|
||||
// if (dragIndex < hoverIndex && hoverActualY < hoverMiddleY) return
|
||||
// // if dragging up, continue only when hover is bigger than middle Y
|
||||
// if (dragIndex > hoverIndex && hoverActualY > hoverMiddleY) return
|
||||
|
||||
// moveListItem(dragIndex, hoverIndex)
|
||||
// item.index = hoverIndex
|
||||
// },
|
||||
}, [])
|
||||
|
||||
console.log('spec', spec)
|
||||
|
||||
const ref: any = useRef(null)
|
||||
const dragDropRef: any = dragRef(dropRef(ref))
|
||||
|
||||
return (
|
||||
<div className={cn("border rounded bg-white", 'col-span-' + widget.colSpan)} style={{ userSelect: 'none'}}>
|
||||
<div
|
||||
className={cn("border rounded bg-white", 'col-span-' + widget.colSpan)}
|
||||
style={{ userSelect: 'none', opacity }}
|
||||
ref={dragDropRef}
|
||||
>
|
||||
{/* <Link to={withSiteId(dashboardMetricDetails(dashboard.dashboardId, widget.widgetId), siteId)}> */}
|
||||
<div className="p-3 cursor-pointe border-b flex items-center justify-between">
|
||||
<div
|
||||
className="p-3 cursor-move border-b flex items-center justify-between"
|
||||
>
|
||||
{widget.name} - {widget.position}
|
||||
<div>
|
||||
<ItemMenu
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { Button, PageTitle, Link } from 'UI';
|
|||
import { withSiteId, dashboardMetricCreate } from 'App/routes';
|
||||
import withModal from 'App/components/Modal/withModal';
|
||||
import DashboardModal from '../DashboardModal'
|
||||
import DashboardWidgetGrid from '../DashboardWidgetGrid';
|
||||
|
||||
function DashboardView(props) {
|
||||
// let { handleModal } = React.useContext(ModalContext);
|
||||
|
|
@ -17,13 +18,16 @@ function DashboardView(props) {
|
|||
}, [])
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center mb-4">
|
||||
<PageTitle title={dashboard.name} className="mr-3" />
|
||||
<Link to={withSiteId(dashboardMetricCreate(dashboard.dashboardId), store.siteId)}><Button primary size="small">Add Metric</Button></Link>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{list && list.map(item => <WidgetWrapper widget={item} key={item.widgetId} />)}
|
||||
<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), store.siteId)}><Button primary size="small">Add Metric</Button></Link>
|
||||
</div>
|
||||
<div>
|
||||
Right
|
||||
</div>
|
||||
</div>
|
||||
<DashboardWidgetGrid />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
import React from 'react';
|
||||
import { useDashboardStore } from '../../store/store';
|
||||
import WidgetWrapper from '../../WidgetWrapper';
|
||||
import { NoContent, Button, Loader } from 'UI';
|
||||
import { useObserver } from 'mobx-react-lite';
|
||||
// import { divider } from '../../Filters/filters.css';
|
||||
|
||||
function DashboardWidgetGrid(props) {
|
||||
const store: any = useDashboardStore();
|
||||
const loading = store.isLoading;
|
||||
const dashbaord = store.selectedDashboard;
|
||||
const list = dashbaord.widgets;
|
||||
return useObserver(() => (
|
||||
<Loader loading={loading}>
|
||||
<NoContent
|
||||
show={list.length === 0}
|
||||
icon="exclamation-circle"
|
||||
title="No metrics added to this dashboard"
|
||||
subtext={
|
||||
<div>
|
||||
<p>Metrics helps you visualize trends from sessions captured by OpenReplay</p>
|
||||
<Button size="small" primary>Add Metric</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{list && list.map((item, index) => (
|
||||
<WidgetWrapper
|
||||
index={index}
|
||||
widget={item}
|
||||
key={item.widgetId}
|
||||
moveListItem={(dragIndex, hoverIndex) => dashbaord.swapWidgetPosition(dragIndex, hoverIndex)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</NoContent>
|
||||
</Loader>
|
||||
));
|
||||
}
|
||||
|
||||
export default DashboardWidgetGrid;
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from './DashboardWidgetGrid';
|
||||
|
|
@ -1,20 +1,24 @@
|
|||
import { useObserver } from 'mobx-react-lite';
|
||||
import React from 'react';
|
||||
import { Icon, NoContent, Label, Link } from 'UI';
|
||||
import { Icon, NoContent, Label, Link, Pagination } from 'UI';
|
||||
import { useDashboardStore } from '../../store/store';
|
||||
import { getRE } from 'App/utils';
|
||||
|
||||
interface Props { }
|
||||
function MetricsList(props: Props) {
|
||||
const store: any = useDashboardStore();
|
||||
const widgets = store.widgets;
|
||||
const lenth = widgets.length;
|
||||
const currentPage = store.metricsPage;
|
||||
const totalPages = widgets.length;
|
||||
const currentPage = useObserver(() => store.metricsPage);
|
||||
const metricsSearch = useObserver(() => store.metricsSearch);
|
||||
|
||||
const filterRE = getRE(metricsSearch, 'i');
|
||||
const list = widgets.filter(w => filterRE.test(w.name))
|
||||
|
||||
const totalPages = list.length;
|
||||
const pageSize = store.metricsPageSize;
|
||||
const start = (currentPage - 1) * pageSize;
|
||||
const end = currentPage * pageSize;
|
||||
const list = widgets.slice(start, end);
|
||||
|
||||
return useObserver(() => (
|
||||
<NoContent show={lenth === 0} icon="exclamation-circle">
|
||||
|
|
@ -28,7 +32,7 @@ function MetricsList(props: Props) {
|
|||
<div>Last Modified</div>
|
||||
</div>
|
||||
|
||||
{list.map((metric: any) => (
|
||||
{list.slice(start, end).map((metric: any) => (
|
||||
<div className="grid grid-cols-7 p-3 border-t select-none">
|
||||
<div className="col-span-2">
|
||||
<Link to="/dashboard/metrics/create" className="link">
|
||||
|
|
@ -55,6 +59,16 @@ function MetricsList(props: Props) {
|
|||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="w-full flex items-center justify-center py-6">
|
||||
<Pagination
|
||||
page={currentPage}
|
||||
totalPages={Math.ceil(totalPages / pageSize)}
|
||||
onPageChange={(page) => store.updateKey('metricsPage', page)}
|
||||
limit={pageSize}
|
||||
debounceRequest={100}
|
||||
/>
|
||||
</div>
|
||||
</NoContent>
|
||||
));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
import { useObserver } from 'mobx-react-lite';
|
||||
import React from 'react';
|
||||
import { useDashboardStore } from '../../store/store';
|
||||
import { Icon } from 'UI';
|
||||
|
||||
function MetricsSearch(props) {
|
||||
const store: any = useDashboardStore();
|
||||
const metricsSearch = useObserver(() => store.metricsSearch);
|
||||
|
||||
|
||||
return useObserver(() => (
|
||||
<div className="relative">
|
||||
<Icon name="search" className="absolute top-0 bottom-0 ml-2 m-auto" size="18" />
|
||||
<input
|
||||
value={metricsSearch}
|
||||
name="metricsSearch"
|
||||
className="bg-white p-2 border rounded w-full pl-10"
|
||||
placeholder="Filter by title, type, dashboard and owner"
|
||||
onChange={({ target: { name, value } }) => store.updateKey(name, value)}
|
||||
/>
|
||||
</div>
|
||||
));
|
||||
}
|
||||
|
||||
export default MetricsSearch;
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from './MetricsSearch';
|
||||
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||
import { Button, PageTitle, Icon, Link } from 'UI';
|
||||
import { withSiteId, dashboardMetricCreate } from 'App/routes';
|
||||
import MetricsList from '../MetricsList';
|
||||
import MetricsSearch from '../MetricsSearch';
|
||||
|
||||
function MetricsView(props) {
|
||||
return (
|
||||
|
|
@ -9,11 +10,8 @@ function MetricsView(props) {
|
|||
<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> */}
|
||||
<div className="ml-auto w-1/4">
|
||||
<input
|
||||
className="bg-white p-2 border rounded w-full"
|
||||
placeholder="Filter by title, type, dashboard and owner"
|
||||
/>
|
||||
<div className="ml-auto w-1/3">
|
||||
<MetricsSearch />
|
||||
</div>
|
||||
</div>
|
||||
<MetricsList />
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@ export default class Dashboard {
|
|||
}
|
||||
|
||||
swapWidgetPosition(positionA, positionB) {
|
||||
console.log('swapWidgetPosition', positionA, positionB)
|
||||
const widgetA = this.widgets[positionA]
|
||||
const widgetB = this.widgets[positionB]
|
||||
this.widgets[positionA] = widgetB
|
||||
|
|
|
|||
|
|
@ -14,15 +14,12 @@ export default class DashboardStore {
|
|||
widgets: Widget[] = []
|
||||
metricsPage: number = 1
|
||||
metricsPageSize: number = 10
|
||||
metricsSearch: string = ''
|
||||
|
||||
private client = new APIClient()
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this, {
|
||||
dashboards: observable,
|
||||
selectedDashboard: observable,
|
||||
isLoading: observable,
|
||||
|
||||
resetCurrentWidget: action,
|
||||
addDashboard: action,
|
||||
removeDashboard: action,
|
||||
|
|
@ -38,6 +35,7 @@ export default class DashboardStore {
|
|||
fromJson: action,
|
||||
setSiteId: action,
|
||||
editWidget: action,
|
||||
updateKey: action,
|
||||
})
|
||||
|
||||
|
||||
|
|
@ -54,7 +52,7 @@ export default class DashboardStore {
|
|||
// this.selectedDashboard?.swapWidgetPosition(2, 0)
|
||||
// }, 3000)
|
||||
|
||||
for (let i = 0; i < 20; i++) {
|
||||
for (let i = 0; i < 15; i++) {
|
||||
const widget: any= {};
|
||||
widget.widgetId = `${i}`
|
||||
widget.name = `Widget ${i}`;
|
||||
|
|
@ -84,6 +82,10 @@ export default class DashboardStore {
|
|||
|
||||
}
|
||||
|
||||
updateKey(key: any, value: any) {
|
||||
this[key] = value
|
||||
}
|
||||
|
||||
resetCurrentWidget() {
|
||||
this.currentWidget = new Widget()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import HTML5Backend from 'react-dnd-html5-backend';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
import { findDOMNode } from 'react-dom';
|
||||
import { DragSource, DropTarget, DragDropContext } from 'react-dnd';
|
||||
import { DragSource, DropTarget, DndContext } from 'react-dnd';
|
||||
|
||||
const cardSource = {
|
||||
beginDrag(props) {
|
||||
|
|
@ -50,7 +50,7 @@ const cardTarget = {
|
|||
},
|
||||
};
|
||||
|
||||
export const DNDContext = DragDropContext(HTML5Backend);
|
||||
export const DNDContext = DndContext(HTML5Backend);
|
||||
|
||||
export const DNDSource = name => DragSource(name, cardSource, (connect, monitor) => ({
|
||||
connectDragSource: connect.dragSource(),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { DNDSource, DNDTarget } from 'Components/hocs/dnd';
|
||||
// import { DNDSource, DNDTarget } from 'Components/hocs/dnd';
|
||||
import Event, { TYPES } from 'Types/filter/event';
|
||||
import { operatorOptions } from 'Types/filter';
|
||||
import { editEvent, removeEvent, clearEvents, applyFilter } from 'Duck/funnelFilters';
|
||||
|
|
@ -24,8 +24,8 @@ const getLabel = ({ type }) => {
|
|||
return getPlaceholder({ type });
|
||||
};
|
||||
|
||||
@DNDTarget('event')
|
||||
@DNDSource('event')
|
||||
// @DNDTarget('event')
|
||||
// @DNDSource('event')
|
||||
@connect(state => ({
|
||||
isLastEvent: state.getIn([ 'filters', 'appliedFilter', 'events' ]).size === 1,
|
||||
funnel: state.getIn(['funnels', 'instance']),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { DNDContext } from 'Components/hocs/dnd';
|
||||
// import { DNDContext } from 'Components/hocs/dnd';
|
||||
import {
|
||||
addEvent, applyFilter, moveEvent, clearEvents,
|
||||
addCustomFilter, addAttribute, setSearchQuery, setActiveFlow, setFilterOption
|
||||
|
|
@ -39,7 +39,7 @@ import CustomFilters from './CustomFilters';
|
|||
updateFunnelFilters,
|
||||
refreshFunnel
|
||||
})
|
||||
@DNDContext
|
||||
// @DNDContext
|
||||
export default class EventFilter extends React.PureComponent {
|
||||
state = { search: '', showFilterModal: false, showPlacehoder: true, showSaveModal: false }
|
||||
fetchEventList = debounce(this.props.fetchEventList, 500)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ import DashboardStore from './components/Dashboard/store';
|
|||
import { DashboardStoreProvider } from './components/Dashboard/store/store';
|
||||
import { ModalProvider } from './components/Modal/ModalContext';
|
||||
import ModalRoot from './components/Modal/ModalRoot';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend'
|
||||
import { DndProvider } from 'react-dnd'
|
||||
|
||||
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
|
|
@ -16,12 +19,14 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
render(
|
||||
(
|
||||
<Provider store={ store }>
|
||||
<DashboardStoreProvider store={ dashboardStore }>
|
||||
<ModalProvider>
|
||||
<ModalRoot />
|
||||
<Router />
|
||||
</ModalProvider>
|
||||
</DashboardStoreProvider>
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<DashboardStoreProvider store={ dashboardStore }>
|
||||
<ModalProvider>
|
||||
<ModalRoot />
|
||||
<Router />
|
||||
</ModalProvider>
|
||||
</DashboardStoreProvider>
|
||||
</DndProvider>
|
||||
</Provider>
|
||||
),
|
||||
document.getElementById('app'),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue