change(ui): new cards
This commit is contained in:
parent
cb24dd4bb9
commit
403b28f2d9
11 changed files with 707 additions and 529 deletions
|
|
@ -0,0 +1,56 @@
|
|||
import React from 'react';
|
||||
import {Card, Col, Modal, Row, Typography} from "antd";
|
||||
import {Grid2X2, Plus} from "lucide-react";
|
||||
import NewDashboardModal from "Components/Dashboard/components/DashboardList/NewDashModal";
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
function AddCardSelectionModal(props: Props) {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [isLibrary, setIsLibrary] = React.useState(false);
|
||||
|
||||
const onCloseModal = () => {
|
||||
setOpen(false);
|
||||
// props.onClose && props.onClose();
|
||||
}
|
||||
|
||||
const onClick = (isLibrary: boolean) => {
|
||||
setIsLibrary(isLibrary);
|
||||
setOpen(true);
|
||||
}
|
||||
return (
|
||||
<Modal
|
||||
title="Add card to dashboard"
|
||||
open={props.open}
|
||||
footer={null}
|
||||
onCancel={props.onClose}
|
||||
>
|
||||
<Row gutter={16} justify="center">
|
||||
<Col span={12}>
|
||||
<Card hoverable onClick={() => onClick(true)}>
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<Grid2X2 style={{fontSize: '24px', color: '#1890ff'}}/>
|
||||
<Typography.Text strong>Add from library</Typography.Text>
|
||||
<p>Select from 12 available</p>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Card hoverable onClick={() => onClick(false)}>
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<Plus style={{fontSize: '24px', color: '#1890ff'}}/>
|
||||
<p>Create New Card</p>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{open && <NewDashboardModal open={true} onClose={onCloseModal} isAddingFromLibrary={isLibrary}/>}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default AddCardSelectionModal;
|
||||
|
|
@ -1,134 +1,145 @@
|
|||
import React from 'react';
|
||||
import Breadcrumb from 'Shared/Breadcrumb';
|
||||
import { withSiteId } from 'App/routes';
|
||||
import { withRouter, RouteComponentProps } from 'react-router-dom';
|
||||
import { Button, PageTitle, confirm, Tooltip } from 'UI';
|
||||
import {withSiteId} from 'App/routes';
|
||||
import {withRouter, RouteComponentProps} from 'react-router-dom';
|
||||
import {Button, PageTitle, confirm, Tooltip} from 'UI';
|
||||
import SelectDateRange from 'Shared/SelectDateRange';
|
||||
import { useStore } from 'App/mstore';
|
||||
import { useModal } from 'App/components/Modal';
|
||||
import {useStore} from 'App/mstore';
|
||||
import {useModal} from 'App/components/Modal';
|
||||
import DashboardOptions from '../DashboardOptions';
|
||||
import withModal from 'App/components/Modal/withModal';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import {observer} from 'mobx-react-lite';
|
||||
import DashboardEditModal from '../DashboardEditModal';
|
||||
import AddCardModal from '../AddCardModal';
|
||||
import AddCardSelectionModal from "Components/Dashboard/components/AddCardSelectionModal";
|
||||
|
||||
interface IProps {
|
||||
dashboardId: string;
|
||||
siteId: string;
|
||||
renderReport?: any;
|
||||
dashboardId: string;
|
||||
siteId: string;
|
||||
renderReport?: any;
|
||||
}
|
||||
|
||||
type Props = IProps & RouteComponentProps;
|
||||
const MAX_CARDS = 29;
|
||||
function DashboardHeader(props: Props) {
|
||||
const { siteId, dashboardId } = props;
|
||||
const { dashboardStore } = useStore();
|
||||
const { showModal } = useModal();
|
||||
const [focusTitle, setFocusedInput] = React.useState(true);
|
||||
const [showEditModal, setShowEditModal] = React.useState(false);
|
||||
const period = dashboardStore.period;
|
||||
|
||||
const dashboard: any = dashboardStore.selectedDashboard;
|
||||
const canAddMore: boolean = dashboard?.widgets?.length <= MAX_CARDS;
|
||||
function AddCard(props: { disabled: boolean }) {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
|
||||
const onEdit = (isTitle: boolean) => {
|
||||
dashboardStore.initDashboard(dashboard);
|
||||
setFocusedInput(isTitle);
|
||||
setShowEditModal(true);
|
||||
};
|
||||
|
||||
const onDelete = async () => {
|
||||
if (
|
||||
await confirm({
|
||||
header: 'Confirm',
|
||||
confirmButton: 'Yes, delete',
|
||||
confirmation: `Are you sure you want to permanently delete this Dashboard?`,
|
||||
})
|
||||
) {
|
||||
dashboardStore.deleteDashboard(dashboard).then(() => {
|
||||
props.history.push(withSiteId(`/dashboard`, siteId));
|
||||
});
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<DashboardEditModal
|
||||
show={showEditModal}
|
||||
closeHandler={() => setShowEditModal(false)}
|
||||
focusTitle={focusTitle}
|
||||
/>
|
||||
<Breadcrumb
|
||||
items={[
|
||||
{
|
||||
label: 'Dashboards',
|
||||
to: withSiteId('/dashboard', siteId),
|
||||
},
|
||||
{ label: (dashboard && dashboard.name) || '' },
|
||||
]}
|
||||
/>
|
||||
<div className="flex items-center mb-2 justify-between">
|
||||
<div className="flex items-center" style={{ flex: 3 }}>
|
||||
<PageTitle
|
||||
title={
|
||||
// @ts-ignore
|
||||
<Tooltip delay={100} arrow title="Double click to edit">
|
||||
{dashboard?.name}
|
||||
</Tooltip>
|
||||
}
|
||||
onDoubleClick={() => onEdit(true)}
|
||||
className="mr-3 select-none border-b border-b-borderColor-transparent hover:border-dotted hover:border-gray-medium cursor-pointer"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center" style={{ flex: 1, justifyContent: 'end' }}>
|
||||
<Tooltip delay={0} disabled={canAddMore} title="The number of cards in one dashboard is limited to 30.">
|
||||
// showModal(<AddCardModal dashboardId={dashboardId} siteId={siteId}/>, {right: true})
|
||||
return <>
|
||||
<Tooltip delay={0} disabled={props.disabled}
|
||||
title="The number of cards in one dashboard is limited to 30.">
|
||||
<Button
|
||||
disabled={!canAddMore}
|
||||
variant="primary"
|
||||
onClick={() =>
|
||||
showModal(<AddCardModal dashboardId={dashboardId} siteId={siteId} />, { right: true })
|
||||
}
|
||||
icon="plus"
|
||||
iconSize={24}
|
||||
disabled={!props.disabled}
|
||||
variant="primary"
|
||||
onClick={() => setOpen(true)}
|
||||
icon="plus"
|
||||
iconSize={24}
|
||||
>
|
||||
Add Card
|
||||
Add Card
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<div className="mx-4"></div>
|
||||
<div
|
||||
className="flex items-center flex-shrink-0 justify-end"
|
||||
style={{ width: 'fit-content' }}
|
||||
>
|
||||
<SelectDateRange
|
||||
style={{ width: '300px' }}
|
||||
period={period}
|
||||
onChange={(period: any) => dashboardStore.setPeriod(period)}
|
||||
right={true}
|
||||
/>
|
||||
</div>
|
||||
<div className="mx-4" />
|
||||
<div className="flex items-center flex-shrink-0">
|
||||
<DashboardOptions
|
||||
editHandler={onEdit}
|
||||
deleteHandler={onDelete}
|
||||
renderReport={props.renderReport}
|
||||
isTitlePresent={!!dashboard?.description}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pb-4">
|
||||
{/* @ts-ignore */}
|
||||
<Tooltip delay={100} arrow title="Double click to edit" className="w-fit !block">
|
||||
<h2
|
||||
className="my-2 font-normal w-fit text-disabled-text border-b border-b-borderColor-transparent hover:border-dotted hover:border-gray-medium cursor-pointer"
|
||||
onDoubleClick={() => onEdit(false)}
|
||||
>
|
||||
{dashboard?.description || 'Describe the purpose of this dashboard'}
|
||||
</h2>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
<AddCardSelectionModal open={open} onClose={() => setOpen(false)}/>
|
||||
</>;
|
||||
}
|
||||
|
||||
function DashboardHeader(props: Props) {
|
||||
const {siteId, dashboardId} = props;
|
||||
const {dashboardStore} = useStore();
|
||||
const {showModal} = useModal();
|
||||
const [focusTitle, setFocusedInput] = React.useState(true);
|
||||
const [showEditModal, setShowEditModal] = React.useState(false);
|
||||
const period = dashboardStore.period;
|
||||
|
||||
const dashboard: any = dashboardStore.selectedDashboard;
|
||||
const canAddMore: boolean = dashboard?.widgets?.length <= MAX_CARDS;
|
||||
|
||||
const onEdit = (isTitle: boolean) => {
|
||||
dashboardStore.initDashboard(dashboard);
|
||||
setFocusedInput(isTitle);
|
||||
setShowEditModal(true);
|
||||
};
|
||||
|
||||
const onDelete = async () => {
|
||||
if (
|
||||
await confirm({
|
||||
header: 'Confirm',
|
||||
confirmButton: 'Yes, delete',
|
||||
confirmation: `Are you sure you want to permanently delete this Dashboard?`,
|
||||
})
|
||||
) {
|
||||
dashboardStore.deleteDashboard(dashboard).then(() => {
|
||||
props.history.push(withSiteId(`/dashboard`, siteId));
|
||||
});
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<DashboardEditModal
|
||||
show={showEditModal}
|
||||
closeHandler={() => setShowEditModal(false)}
|
||||
focusTitle={focusTitle}
|
||||
/>
|
||||
<Breadcrumb
|
||||
items={[
|
||||
{
|
||||
label: 'Dashboards',
|
||||
to: withSiteId('/dashboard', siteId),
|
||||
},
|
||||
{label: (dashboard && dashboard.name) || ''},
|
||||
]}
|
||||
/>
|
||||
<div className="flex items-center mb-2 justify-between">
|
||||
<div className="flex items-center" style={{flex: 3}}>
|
||||
<PageTitle
|
||||
title={
|
||||
// @ts-ignore
|
||||
<Tooltip delay={100} arrow title="Double click to edit">
|
||||
{dashboard?.name}
|
||||
</Tooltip>
|
||||
}
|
||||
onDoubleClick={() => onEdit(true)}
|
||||
className="mr-3 select-none border-b border-b-borderColor-transparent hover:border-dotted hover:border-gray-medium cursor-pointer"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center" style={{flex: 1, justifyContent: 'end'}}>
|
||||
<AddCard disabled={canAddMore}/>
|
||||
<div className="mx-4"></div>
|
||||
<div
|
||||
className="flex items-center flex-shrink-0 justify-end"
|
||||
style={{width: 'fit-content'}}
|
||||
>
|
||||
<SelectDateRange
|
||||
style={{width: '300px'}}
|
||||
period={period}
|
||||
onChange={(period: any) => dashboardStore.setPeriod(period)}
|
||||
right={true}
|
||||
/>
|
||||
</div>
|
||||
<div className="mx-4"/>
|
||||
<div className="flex items-center flex-shrink-0">
|
||||
<DashboardOptions
|
||||
editHandler={onEdit}
|
||||
deleteHandler={onDelete}
|
||||
renderReport={props.renderReport}
|
||||
isTitlePresent={!!dashboard?.description}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pb-4">
|
||||
{/* @ts-ignore */}
|
||||
<Tooltip delay={100} arrow title="Double click to edit" className="w-fit !block">
|
||||
<h2
|
||||
className="my-2 font-normal w-fit text-disabled-text border-b border-b-borderColor-transparent hover:border-dotted hover:border-gray-medium cursor-pointer"
|
||||
onDoubleClick={() => onEdit(false)}
|
||||
>
|
||||
{dashboard?.description || 'Describe the purpose of this dashboard'}
|
||||
</h2>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default withRouter(withModal(observer(DashboardHeader)));
|
||||
|
|
|
|||
|
|
@ -1,62 +1,47 @@
|
|||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { Button } from 'antd';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import {PlusOutlined} from '@ant-design/icons';
|
||||
import {Button} from 'antd';
|
||||
import {observer} from 'mobx-react-lite';
|
||||
import React from 'react';
|
||||
|
||||
import { useStore } from 'App/mstore';
|
||||
import { withSiteId } from 'App/routes';
|
||||
import { PageTitle } from 'UI';
|
||||
import {PageTitle} from 'UI';
|
||||
|
||||
import DashboardSearch from './DashboardSearch';
|
||||
import NewDashboardModal from './NewDashModal';
|
||||
|
||||
function Header({ history, siteId }: { history: any; siteId: string }) {
|
||||
const [showModal, setShowModal] = React.useState(false);
|
||||
const { dashboardStore } = useStore();
|
||||
function Header() {
|
||||
const [showModal, setShowModal] = React.useState(true);
|
||||
|
||||
const onSaveDashboard = () => {
|
||||
dashboardStore.initDashboard();
|
||||
dashboardStore
|
||||
.save(dashboardStore.dashboardInstance)
|
||||
.then(async (syncedDashboard) => {
|
||||
dashboardStore.selectDashboardById(syncedDashboard.dashboardId);
|
||||
history.push(
|
||||
withSiteId(`/dashboard/${syncedDashboard.dashboardId}`, siteId)
|
||||
);
|
||||
});
|
||||
};
|
||||
const onAddDashboardClick = () => {
|
||||
setShowModal(true);
|
||||
};
|
||||
|
||||
const onAddDashboardClick = () => {
|
||||
setShowModal(true);
|
||||
};
|
||||
const onClose = () => {
|
||||
setShowModal(false);
|
||||
};
|
||||
|
||||
const onClose = () => {
|
||||
setShowModal(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center justify-between px-4 pb-2">
|
||||
<div className="flex items-baseline mr-3">
|
||||
<PageTitle title="Dashboards" />
|
||||
</div>
|
||||
<div className="ml-auto flex items-center">
|
||||
<Button
|
||||
icon={<PlusOutlined />}
|
||||
type="primary"
|
||||
onClick={onAddDashboardClick}
|
||||
>
|
||||
Create Dashboard
|
||||
</Button>
|
||||
<div className="mx-2"></div>
|
||||
<div className="w-1/4" style={{ minWidth: 300 }}>
|
||||
<DashboardSearch />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<NewDashboardModal onClose={onClose} open={showModal} onSave={onSaveDashboard} />
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center justify-between px-4 pb-2">
|
||||
<div className="flex items-baseline mr-3">
|
||||
<PageTitle title="Dashboards"/>
|
||||
</div>
|
||||
<div className="ml-auto flex items-center">
|
||||
<Button
|
||||
icon={<PlusOutlined/>}
|
||||
type="primary"
|
||||
onClick={onAddDashboardClick}
|
||||
>
|
||||
Create Dashboard
|
||||
</Button>
|
||||
<div className="mx-2"></div>
|
||||
<div className="w-1/4" style={{minWidth: 300}}>
|
||||
<DashboardSearch/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<NewDashboardModal onClose={onClose} open={showModal}/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default observer(Header);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,76 @@
|
|||
import React, {useEffect, useMemo} from 'react';
|
||||
import {useStore} from "App/mstore";
|
||||
import WidgetWrapper from "Components/Dashboard/components/WidgetWrapper/WidgetWrapper";
|
||||
import {observer} from "mobx-react-lite";
|
||||
import {Loader} from "UI";
|
||||
import WidgetChart from "Components/Dashboard/components/WidgetChart/WidgetChart";
|
||||
import LazyLoad from 'react-lazyload';
|
||||
import {Card} from "antd";
|
||||
import {CARD_CATEGORIES} from "Components/Dashboard/components/DashboardList/NewDashModal/ExampleCards";
|
||||
|
||||
const CARD_TYPES_MAP = CARD_CATEGORIES.reduce((acc: any, category: any) => {
|
||||
acc[category.key] = category.types;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
interface Props {
|
||||
category?: string;
|
||||
selectedList: any;
|
||||
onCard: (metricId: number) => void;
|
||||
}
|
||||
|
||||
function CardsLibrary(props: Props) {
|
||||
const {selectedList} = props;
|
||||
const {metricStore, dashboardStore} = useStore();
|
||||
|
||||
const cards = useMemo(() => {
|
||||
return metricStore.filteredCards.filter((card: any) => {
|
||||
return CARD_TYPES_MAP[props.category || 'default'].includes(card.metricType);
|
||||
});
|
||||
}, [metricStore.filteredCards, props.category]);
|
||||
|
||||
useEffect(() => {
|
||||
metricStore.fetchList();
|
||||
}, []);
|
||||
|
||||
const onItemClick = (metricId: number) => {
|
||||
props.onCard(metricId);
|
||||
}
|
||||
|
||||
return (
|
||||
<Loader loading={metricStore.isLoading}>
|
||||
<div className="grid grid-cols-4 gap-4 items-start">
|
||||
{cards.map((metric: any) => (
|
||||
<React.Fragment key={metric.metricId}>
|
||||
<div className={'col-span-' + metric.config.col}
|
||||
onClick={() => onItemClick(metric.metricId)}>
|
||||
<LazyLoad>
|
||||
<Card hoverable
|
||||
style={{
|
||||
border: selectedList.includes(metric.metricId) ? '1px solid #1890ff' : '1px solid #f0f0f0',
|
||||
}}
|
||||
styles={{
|
||||
header: {padding: '4px 14px', minHeight: '36px', fontSize: '14px'},
|
||||
body: {padding: '14px'},
|
||||
cover: {
|
||||
border: '2px solid #1890ff',
|
||||
// border: selectedList.includes(metric.metricId) ? '2px solid #1890ff' : 'none',
|
||||
}
|
||||
}} title={metric.name}>
|
||||
<WidgetChart
|
||||
// isPreview={true}
|
||||
metric={metric}
|
||||
// isTemplate={true}
|
||||
// isWidget={false}
|
||||
/>
|
||||
</Card>
|
||||
</LazyLoad>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
</Loader>
|
||||
);
|
||||
}
|
||||
|
||||
export default observer(CardsLibrary);
|
||||
|
|
@ -7,6 +7,7 @@ import {useStore} from "App/mstore";
|
|||
import {CLICKMAP} from "App/constants/card";
|
||||
import {renderClickmapThumbnail} from "Components/Dashboard/components/WidgetForm/renderMap";
|
||||
import WidgetPreview from "Components/Dashboard/components/WidgetPreview/WidgetPreview";
|
||||
import {number} from "Player/player/localStorage";
|
||||
|
||||
const getTitleByType = (type: string) => {
|
||||
switch (type) {
|
||||
|
|
@ -26,10 +27,10 @@ function CreateCard(props: Props) {
|
|||
const history = useHistory();
|
||||
const {metricStore, dashboardStore, aiFiltersStore} = useStore();
|
||||
const metric = metricStore.instance;
|
||||
const siteId = history.location.pathname.split('/')[1];
|
||||
const siteId: string = history.location.pathname.split('/')[1];
|
||||
const dashboardId: string = history.location.pathname.split('/')[4];
|
||||
// const title = getTitleByType(metric.metricType)
|
||||
|
||||
|
||||
const createNewDashboard = async () => {
|
||||
dashboardStore.initDashboard();
|
||||
return await dashboardStore
|
||||
|
|
|
|||
|
|
@ -0,0 +1,142 @@
|
|||
import ExampleFunnel from "./Examples/Funnel";
|
||||
import ExamplePath from "./Examples/Path";
|
||||
import ExampleTrend from "./Examples/Trend";
|
||||
import ExampleCount from "./Examples/Count";
|
||||
import PerfBreakdown from "./Examples/PerfBreakdown";
|
||||
import SlowestDomain from "./Examples/SlowestDomain";
|
||||
import SessionsByErrors from "./Examples/SessionsByErrors";
|
||||
import SessionsByIssues from "./Examples/SessionsByIssues";
|
||||
import ByBrowser from "./Examples/SessionsBy/ByBrowser";
|
||||
import BySystem from "./Examples/SessionsBy/BySystem";
|
||||
import ByCountry from "./Examples/SessionsBy/ByCountry";
|
||||
import ByUrl from "./Examples/SessionsBy/ByUrl";
|
||||
import {ERRORS, FUNNEL, PERFORMANCE, TABLE, TIMESERIES, USER_PATH, WEB_VITALS} from "App/constants/card";
|
||||
import {FilterKey} from "Types/filter/filterType";
|
||||
import {Activity, BarChart, TableCellsMerge, TrendingUp} from "lucide-react";
|
||||
|
||||
const TYPE = {
|
||||
FUNNEL: 'funnel',
|
||||
PATH_FINDER: 'path-finder',
|
||||
TREND: 'trend',
|
||||
SESSIONS_BY: 'sessions-by',
|
||||
BREAKDOWN: 'breakdown',
|
||||
SLOWEST_DOMAIN: 'slowest-domain',
|
||||
SESSIONS_BY_ERRORS: 'sessions-by-errors',
|
||||
SESSIONS_BY_ISSUES: 'sessions-by-issues',
|
||||
SESSIONS_BY_BROWSER: 'sessions-by-browser',
|
||||
SESSIONS_BY_SYSTEM: 'sessions-by-system',
|
||||
SESSIONS_BY_COUNTRY: 'sessions-by-country',
|
||||
SESSIONS_BY_URL: 'sessions-by-url',
|
||||
}
|
||||
|
||||
export const CARD_CATEGORIES = [
|
||||
{
|
||||
key: 'product-analytics', label: 'Product Analytics', icon: TrendingUp, types: [USER_PATH, ERRORS]
|
||||
},
|
||||
{key: 'performance-monitoring', label: 'Performance Monitoring', icon: Activity, types: [TIMESERIES]},
|
||||
{key: 'web-analytics', label: 'Web Analytics', icon: BarChart, types: [TABLE]},
|
||||
{key: 'core-web-vitals', label: 'Core Web Vitals', icon: TableCellsMerge, types: [WEB_VITALS]}
|
||||
];
|
||||
|
||||
export interface CardType {
|
||||
title: string;
|
||||
key: string;
|
||||
cardType: string;
|
||||
category: string;
|
||||
example: any;
|
||||
metricOf?: string;
|
||||
}
|
||||
|
||||
export const CARD_LIST: CardType[] = [
|
||||
{
|
||||
title: 'Funnel',
|
||||
key: TYPE.FUNNEL,
|
||||
cardType: FUNNEL,
|
||||
category: CARD_CATEGORIES[0].key,
|
||||
example: ExampleFunnel,
|
||||
},
|
||||
{
|
||||
title: 'Path Finder',
|
||||
key: TYPE.PATH_FINDER,
|
||||
cardType: USER_PATH,
|
||||
category: CARD_CATEGORIES[0].key,
|
||||
example: ExamplePath,
|
||||
},
|
||||
{
|
||||
title: 'Trend',
|
||||
key: TYPE.TREND,
|
||||
cardType: TIMESERIES,
|
||||
category: CARD_CATEGORIES[0].key,
|
||||
example: ExampleTrend,
|
||||
},
|
||||
{
|
||||
title: 'Sessions by',
|
||||
key: TYPE.SESSIONS_BY,
|
||||
cardType: TABLE,
|
||||
metricOf: 'userBrowser',
|
||||
category: CARD_CATEGORIES[0].key,
|
||||
example: ExampleCount,
|
||||
},
|
||||
|
||||
{
|
||||
title: 'Breakdown',
|
||||
key: TYPE.BREAKDOWN,
|
||||
cardType: PERFORMANCE,
|
||||
category: CARD_CATEGORIES[1].key,
|
||||
example: PerfBreakdown,
|
||||
},
|
||||
{
|
||||
title: 'Slowest Domain',
|
||||
key: TYPE.SLOWEST_DOMAIN,
|
||||
cardType: TIMESERIES,
|
||||
category: CARD_CATEGORIES[1].key,
|
||||
example: SlowestDomain,
|
||||
},
|
||||
{
|
||||
title: 'Sessions by Errors',
|
||||
key: TYPE.SESSIONS_BY_ERRORS,
|
||||
cardType: TIMESERIES,
|
||||
category: CARD_CATEGORIES[1].key,
|
||||
example: SessionsByErrors,
|
||||
},
|
||||
{
|
||||
title: 'Sessions by Issues',
|
||||
key: TYPE.SESSIONS_BY_ISSUES,
|
||||
cardType: TIMESERIES,
|
||||
category: CARD_CATEGORIES[1].key,
|
||||
example: SessionsByIssues,
|
||||
},
|
||||
|
||||
{
|
||||
title: 'Sessions by Browser',
|
||||
key: TYPE.SESSIONS_BY_BROWSER,
|
||||
cardType: TABLE,
|
||||
metricOf: FilterKey.USER_BROWSER,
|
||||
category: CARD_CATEGORIES[2].key,
|
||||
example: ByBrowser,
|
||||
},
|
||||
{
|
||||
title: 'Sessions by System',
|
||||
key: TYPE.SESSIONS_BY_SYSTEM,
|
||||
cardType: TABLE,
|
||||
metricOf: FilterKey.USER_OS,
|
||||
category: CARD_CATEGORIES[2].key,
|
||||
example: BySystem,
|
||||
},
|
||||
{
|
||||
title: 'Sessions by Country',
|
||||
key: TYPE.SESSIONS_BY_COUNTRY,
|
||||
cardType: TABLE,
|
||||
metricOf: FilterKey.USER_COUNTRY,
|
||||
category: CARD_CATEGORIES[2].key,
|
||||
example: ByCountry,
|
||||
},
|
||||
{
|
||||
title: 'Sessions by URL',
|
||||
key: TYPE.SESSIONS_BY_URL,
|
||||
cardType: TABLE,
|
||||
metricOf: FilterKey.LOCATION,
|
||||
category: CARD_CATEGORIES[2].key,
|
||||
example: ByUrl,
|
||||
},
|
||||
]
|
||||
|
|
@ -2,35 +2,29 @@ import React, {useEffect} from 'react';
|
|||
import {Modal} from 'antd';
|
||||
import SelectCard from './SelectCard';
|
||||
import CreateCard from "Components/Dashboard/components/DashboardList/NewDashModal/CreateCard";
|
||||
import {useStore} from "App/mstore";
|
||||
import {TIMESERIES} from "App/constants/card";
|
||||
|
||||
interface NewDashboardModalProps {
|
||||
onClose: () => void;
|
||||
open: boolean;
|
||||
isAddingFromLibrary?: boolean;
|
||||
isCreatingNewCard?: boolean;
|
||||
}
|
||||
|
||||
const NewDashboardModal: React.FC<NewDashboardModalProps> = ({onClose, open}) => {
|
||||
const NewDashboardModal: React.FC<NewDashboardModalProps> = ({
|
||||
onClose,
|
||||
open,
|
||||
isAddingFromLibrary = false,
|
||||
isCreatingNewCard = false
|
||||
}) => {
|
||||
const [step, setStep] = React.useState<number>(0);
|
||||
const [selectedCard, setSelectedCard] = React.useState<string>('trend-single');
|
||||
const {metricStore} = useStore();
|
||||
|
||||
const onCard = (card: any) => {
|
||||
const onCard = () => {
|
||||
setStep(step + 1);
|
||||
// setSelectedCard(card);
|
||||
// console.log('Selected card:', card)
|
||||
console.log('Selected card:', card)
|
||||
metricStore.merge({
|
||||
name: card.title,
|
||||
});
|
||||
metricStore.changeType(card.cardType);
|
||||
};
|
||||
|
||||
const [modalOpen, setModalOpen] = React.useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
setStep(1);
|
||||
setStep(0);
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
|
|
@ -39,7 +33,7 @@ const NewDashboardModal: React.FC<NewDashboardModalProps> = ({onClose, open}) =>
|
|||
<Modal open={open} onCancel={onClose} width={900} destroyOnClose={true} footer={null} closeIcon={false}>
|
||||
<div>
|
||||
<div className="flex flex-col gap-4">
|
||||
{step === 0 && <SelectCard onClose={onClose} onCard={onCard}/>}
|
||||
{step === 0 && <SelectCard onClose={onClose} onCard={onCard} isLibrary={isAddingFromLibrary}/>}
|
||||
{step === 1 && <CreateCard onBack={() => setStep(0)}/>}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,228 +1,109 @@
|
|||
import React, {useMemo} from 'react';
|
||||
import {Segmented} from 'antd';
|
||||
import {Button, Segmented} from 'antd';
|
||||
import {CARD_LIST, CARD_CATEGORIES, CardType} from './ExampleCards';
|
||||
import {useStore} from 'App/mstore';
|
||||
import Option from './Option';
|
||||
// import ProductAnalytics from './Examples/ProductAnalytics';
|
||||
// import PerformanceMonitoring from './Examples/PerformanceMonitoring';
|
||||
// import WebAnalytics from './Examples/WebAnalytics';
|
||||
// import CoreWebVitals from './Examples/CoreWebVitals';
|
||||
import {TrendingUp, Activity, BarChart, TableCellsMerge} from "lucide-react";
|
||||
import ExampleFunnel from "Components/Dashboard/components/DashboardList/NewDashModal/Examples/Funnel";
|
||||
import ExamplePath from "Components/Dashboard/components/DashboardList/NewDashModal/Examples/Path";
|
||||
import ExampleTrend from "Components/Dashboard/components/DashboardList/NewDashModal/Examples/Trend";
|
||||
import ExampleCount from "Components/Dashboard/components/DashboardList/NewDashModal/Examples/Count";
|
||||
import PerfBreakdown from "Components/Dashboard/components/DashboardList/NewDashModal/Examples/PerfBreakdown";
|
||||
import SlowestDomain from "Components/Dashboard/components/DashboardList/NewDashModal/Examples/SlowestDomain";
|
||||
import SessionsByErrors from "Components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsByErrors";
|
||||
import SessionsByIssues from "Components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsByIssues";
|
||||
import ByBrowser from "Components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsBy/ByBrowser";
|
||||
import BySystem from "Components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsBy/BySystem";
|
||||
import ByCountry from "Components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsBy/ByCountry";
|
||||
import ByUrl from "Components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsBy/ByUrl";
|
||||
import {ERRORS, FUNNEL, TIMESERIES, USER_PATH} from "App/constants/card";
|
||||
import CardsLibrary from "Components/Dashboard/components/DashboardList/NewDashModal/CardsLibrary";
|
||||
|
||||
interface SelectCardProps {
|
||||
onClose: () => void;
|
||||
onCard: (card: any) => void;
|
||||
onCard: () => void;
|
||||
isLibrary?: boolean;
|
||||
}
|
||||
|
||||
const CARD_CATEGORY = {
|
||||
PRODUCT_ANALYTICS: 'product-analytics',
|
||||
PERFORMANCE_MONITORING: 'performance-monitoring',
|
||||
WEB_ANALYTICS: 'web-analytics',
|
||||
CORE_WEB_VITALS: 'core-web-vitals',
|
||||
}
|
||||
|
||||
const segmentedOptions = [
|
||||
{label: 'Product Analytics', Icon: TrendingUp, value: CARD_CATEGORY.PRODUCT_ANALYTICS},
|
||||
{label: 'Performance Monitoring', Icon: Activity, value: CARD_CATEGORY.PERFORMANCE_MONITORING},
|
||||
{label: 'Web Analytics', Icon: BarChart, value: CARD_CATEGORY.WEB_ANALYTICS},
|
||||
{label: 'Core Web Vitals', Icon: TableCellsMerge, value: CARD_CATEGORY.CORE_WEB_VITALS},
|
||||
];
|
||||
|
||||
const TYPE = {
|
||||
FUNNEL: 'funnel',
|
||||
PATH_FINDER: 'path-finder',
|
||||
TREND: 'trend',
|
||||
SESSIONS_BY: 'sessions-by',
|
||||
BREAKDOWN: 'breakdown',
|
||||
SLOWEST_DOMAIN: 'slowest-domain',
|
||||
SESSIONS_BY_ERRORS: 'sessions-by-errors',
|
||||
SESSIONS_BY_ISSUES: 'sessions-by-issues',
|
||||
SESSIONS_BY_BROWSER: 'sessions-by-browser',
|
||||
SESSIONS_BY_SYSTEM: 'sessions-by-system',
|
||||
SESSIONS_BY_COUNTRY: 'sessions-by-country',
|
||||
SESSIONS_BY_URL: 'sessions-by-url',
|
||||
}
|
||||
|
||||
const CARD_TYPE_MAP = {
|
||||
[TYPE.FUNNEL]: FUNNEL,
|
||||
[TYPE.PATH_FINDER]: USER_PATH,
|
||||
[TYPE.TREND]: TIMESERIES,
|
||||
[TYPE.SESSIONS_BY]: TIMESERIES,
|
||||
[TYPE.BREAKDOWN]: TIMESERIES,
|
||||
[TYPE.SLOWEST_DOMAIN]: TIMESERIES,
|
||||
[TYPE.SESSIONS_BY_ERRORS]: ERRORS,
|
||||
[TYPE.SESSIONS_BY_ISSUES]: TIMESERIES,
|
||||
[TYPE.SESSIONS_BY_BROWSER]: TIMESERIES,
|
||||
[TYPE.SESSIONS_BY_SYSTEM]: TIMESERIES,
|
||||
[TYPE.SESSIONS_BY_COUNTRY]: TIMESERIES,
|
||||
[TYPE.SESSIONS_BY_URL]: TIMESERIES,
|
||||
}
|
||||
|
||||
export const CARD_LIST = [
|
||||
{
|
||||
title: 'Funnel',
|
||||
key: TYPE.FUNNEL,
|
||||
cardType: FUNNEL,
|
||||
category: CARD_CATEGORY.PRODUCT_ANALYTICS,
|
||||
example: ExampleFunnel,
|
||||
},
|
||||
{
|
||||
title: 'Path Finder',
|
||||
key: TYPE.PATH_FINDER,
|
||||
cardType: USER_PATH,
|
||||
category: CARD_CATEGORY.PRODUCT_ANALYTICS,
|
||||
example: ExamplePath,
|
||||
},
|
||||
{
|
||||
title: 'Trend',
|
||||
key: TYPE.TREND,
|
||||
cardType: TIMESERIES,
|
||||
category: CARD_CATEGORY.PRODUCT_ANALYTICS,
|
||||
example: ExampleTrend,
|
||||
},
|
||||
{
|
||||
title: 'Sessions by',
|
||||
key: TYPE.SESSIONS_BY,
|
||||
cardType: TIMESERIES,
|
||||
category: CARD_CATEGORY.PRODUCT_ANALYTICS,
|
||||
example: ExampleCount,
|
||||
},
|
||||
{
|
||||
title: 'Breakdown',
|
||||
key: TYPE.BREAKDOWN,
|
||||
cardType: TIMESERIES,
|
||||
category: CARD_CATEGORY.PERFORMANCE_MONITORING,
|
||||
example: PerfBreakdown,
|
||||
},
|
||||
{
|
||||
title: 'Slowest Domain',
|
||||
key: TYPE.SLOWEST_DOMAIN,
|
||||
cardType: TIMESERIES,
|
||||
category: CARD_CATEGORY.PERFORMANCE_MONITORING,
|
||||
example: SlowestDomain,
|
||||
},
|
||||
{
|
||||
title: 'Sessions by Errors',
|
||||
key: TYPE.SESSIONS_BY_ERRORS,
|
||||
cardType: TIMESERIES,
|
||||
category: CARD_CATEGORY.PERFORMANCE_MONITORING,
|
||||
example: SessionsByErrors,
|
||||
},
|
||||
{
|
||||
title: 'Sessions by Issues',
|
||||
key: TYPE.SESSIONS_BY_ISSUES,
|
||||
cardType: TIMESERIES,
|
||||
category: CARD_CATEGORY.PERFORMANCE_MONITORING,
|
||||
example: SessionsByIssues,
|
||||
},
|
||||
|
||||
{
|
||||
title: 'Sessions by Browser',
|
||||
key: TYPE.SESSIONS_BY_BROWSER,
|
||||
cardType: TIMESERIES,
|
||||
category: CARD_CATEGORY.WEB_ANALYTICS,
|
||||
example: ByBrowser,
|
||||
},
|
||||
{
|
||||
title: 'Sessions by System',
|
||||
key: TYPE.SESSIONS_BY_SYSTEM,
|
||||
cardType: TIMESERIES,
|
||||
category: CARD_CATEGORY.WEB_ANALYTICS,
|
||||
example: BySystem,
|
||||
},
|
||||
{
|
||||
title: 'Sessions by Country',
|
||||
key: TYPE.SESSIONS_BY_COUNTRY,
|
||||
cardType: TIMESERIES,
|
||||
category: CARD_CATEGORY.WEB_ANALYTICS,
|
||||
example: ByCountry,
|
||||
},
|
||||
{
|
||||
title: 'Sessions by URL',
|
||||
key: TYPE.SESSIONS_BY_URL,
|
||||
cardType: TIMESERIES,
|
||||
category: CARD_CATEGORY.WEB_ANALYTICS,
|
||||
example: ByUrl,
|
||||
},
|
||||
|
||||
// {
|
||||
// title: 'Breakdown',
|
||||
// key: TYPE.BREAKDOWN,
|
||||
// category: CARD_CATEGORY.CORE_WEB_VITALS,
|
||||
// example: PerfBreakdown,
|
||||
// },
|
||||
// {
|
||||
// title: 'Slowest Domain',
|
||||
// key: TYPE.SLOWEST_DOMAIN,
|
||||
// category: CARD_CATEGORY.CORE_WEB_VITALS,
|
||||
// example: SlowestDomain,
|
||||
// },
|
||||
// {
|
||||
// title: 'Sessions by Issues',
|
||||
// key: TYPE.SESSIONS_BY_ISSUES,
|
||||
// category: CARD_CATEGORY.CORE_WEB_VITALS,
|
||||
// example: SessionsByIssues,
|
||||
// },
|
||||
// {
|
||||
// title: 'Sessions by Errors',
|
||||
// key: TYPE.SESSIONS_BY_ISSUES,
|
||||
// category: CARD_CATEGORY.CORE_WEB_VITALS,
|
||||
// example: SessionsByErrors,
|
||||
// },
|
||||
]
|
||||
|
||||
const SelectCard: React.FC<SelectCardProps> = (props: SelectCardProps) => {
|
||||
const SelectCard: React.FC<SelectCardProps> = ({onCard, isLibrary = false}) => {
|
||||
const [selected, setSelected] = React.useState<string>('product-analytics');
|
||||
// const item = getSelectedItem(selected, onCard);
|
||||
|
||||
const onCard = (card: string) => {
|
||||
const _card = CARD_LIST.find((c) => c.key === card);
|
||||
props.onCard(_card);
|
||||
// props.onClose();
|
||||
}
|
||||
const [selectedCards, setSelectedCards] = React.useState<number[]>([]);
|
||||
const {metricStore, dashboardStore} = useStore();
|
||||
const dashboardId = window.location.pathname.split('/')[4];
|
||||
|
||||
|
||||
const item = useMemo(() => {
|
||||
const handleCardSelection = (card: string) => {
|
||||
const selectedCard = CARD_LIST.find((c) => c.key === card) as CardType;
|
||||
metricStore.merge({
|
||||
metricType: selectedCard.cardType,
|
||||
name: selectedCard.title,
|
||||
metricOf: selectedCard.metricOf,
|
||||
});
|
||||
onCard();
|
||||
};
|
||||
|
||||
const cardItems = useMemo(() => {
|
||||
return CARD_LIST.filter((card) => card.category === selected).map((card) => (
|
||||
<div key={card.key}>
|
||||
<card.example onCard={onCard} type={card.key} title={card.title}/>
|
||||
<card.example onCard={handleCardSelection} type={card.key} title={card.title}/>
|
||||
</div>
|
||||
));
|
||||
}, [selected]);
|
||||
|
||||
const onCardClick = (cardId: number) => {
|
||||
if (selectedCards.includes(cardId)) {
|
||||
setSelectedCards(selectedCards.filter((id) => id !== cardId));
|
||||
} else {
|
||||
setSelectedCards([...selectedCards, cardId]);
|
||||
}
|
||||
}
|
||||
|
||||
const onAddSelected = () => {
|
||||
console.log(selectedCards);
|
||||
dashboardStore.addWidgetToDashboard(dashboardId, selectedCards);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-2xl leading-4 font-semibold">
|
||||
Select your first card type to add to the dashboard
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Segmented
|
||||
options={segmentedOptions.map(({label, Icon, value}) => ({
|
||||
label: <Option key={value} label={label} Icon={Icon}/>,
|
||||
value,
|
||||
}))}
|
||||
onChange={setSelected}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full grid grid-cols-2 gap-4 overflow-scroll"
|
||||
style={{maxHeight: 'calc(100vh - 210px)'}}>
|
||||
{item}
|
||||
</div>
|
||||
<Header selectedCount={selectedCards.length} onAdd={onAddSelected}/>
|
||||
<CategorySelector setSelected={setSelected}/>
|
||||
{isLibrary ? <CardsLibrary selectedList={selectedCards} category={selected} onCard={onCardClick}/> :
|
||||
<ExampleCardsGrid items={cardItems}/>}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
interface HeaderProps {
|
||||
selectedCount?: number,
|
||||
onAdd?: () => void;
|
||||
}
|
||||
|
||||
const Header: React.FC<HeaderProps> = ({selectedCount = 0, onAdd = () => null}) => (
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-2xl leading-4 font-semibold">
|
||||
Select your first card type to add to the dashboard
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
{selectedCount > 0 ? (
|
||||
<Button type="link" onClick={onAdd}>
|
||||
Add {selectedCount} Selected
|
||||
</Button>
|
||||
) : ''}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
interface CategorySelectorProps {
|
||||
setSelected: React.Dispatch<React.SetStateAction<string>>;
|
||||
}
|
||||
|
||||
const CategorySelector: React.FC<CategorySelectorProps> = ({setSelected}) => (
|
||||
<Segmented
|
||||
options={CARD_CATEGORIES.map(({key, label, icon}) => ({
|
||||
label: <Option key={key} label={label} Icon={icon}/>,
|
||||
value: key,
|
||||
}))}
|
||||
onChange={setSelected}
|
||||
/>
|
||||
);
|
||||
|
||||
interface ExampleCardsGridProps {
|
||||
items: JSX.Element[];
|
||||
}
|
||||
|
||||
const ExampleCardsGrid: React.FC<ExampleCardsGridProps> = ({items}) => (
|
||||
<div
|
||||
className="w-full grid grid-cols-2 gap-4 overflow-scroll"
|
||||
style={{maxHeight: 'calc(100vh - 210px)'}}
|
||||
>
|
||||
{items}
|
||||
</div>
|
||||
);
|
||||
|
||||
export default SelectCard;
|
||||
|
|
|
|||
|
|
@ -1,135 +1,146 @@
|
|||
import React from 'react';
|
||||
import { PageTitle, Button, Toggler, Icon } from "UI";
|
||||
import { Segmented } from 'antd';
|
||||
import {PageTitle, Button, Toggler, Icon} from "UI";
|
||||
import {Segmented} from 'antd';
|
||||
import MetricsSearch from '../MetricsSearch';
|
||||
import Select from 'Shared/Select';
|
||||
import { useStore } from 'App/mstore';
|
||||
import { observer, useObserver } from 'mobx-react-lite';
|
||||
import { DROPDOWN_OPTIONS } from 'App/constants/card';
|
||||
import {useStore} from 'App/mstore';
|
||||
import {observer, useObserver} from 'mobx-react-lite';
|
||||
import {DROPDOWN_OPTIONS} from 'App/constants/card';
|
||||
import AddCardModal from 'Components/Dashboard/components/AddCardModal';
|
||||
import { useModal } from 'Components/Modal';
|
||||
import {useModal} from 'Components/Modal';
|
||||
import AddCardSelectionModal from "Components/Dashboard/components/AddCardSelectionModal";
|
||||
import NewDashboardModal from "Components/Dashboard/components/DashboardList/NewDashModal";
|
||||
|
||||
function MetricViewHeader({ siteId }: { siteId: string }) {
|
||||
const { metricStore } = useStore();
|
||||
const filter = metricStore.filter;
|
||||
const { showModal } = useModal();
|
||||
function MetricViewHeader({siteId}: { siteId: string }) {
|
||||
const {metricStore} = useStore();
|
||||
const filter = metricStore.filter;
|
||||
const {showModal} = useModal();
|
||||
const [showAddCardModal, setShowAddCardModal] = React.useState(false);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='flex items-center justify-between px-6'>
|
||||
<div className='flex items-baseline mr-3'>
|
||||
<PageTitle title='Cards' className='' />
|
||||
return (
|
||||
<div>
|
||||
<div className='flex items-center justify-between px-6'>
|
||||
<div className='flex items-baseline mr-3'>
|
||||
<PageTitle title='Cards' className=''/>
|
||||
</div>
|
||||
<div className='ml-auto flex items-center'>
|
||||
<Button variant='primary'
|
||||
// onClick={() => showModal(<AddCardModal siteId={siteId}/>, {right: true})}
|
||||
onClick={() => setShowAddCardModal(true)}
|
||||
>New Card</Button>
|
||||
<div className='ml-4 w-1/4' style={{minWidth: 300}}>
|
||||
<MetricsSearch/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='border-y px-6 py-1 mt-2 flex items-center w-full justify-between'>
|
||||
<div className='items-center flex gap-4'>
|
||||
<Toggler
|
||||
label='My Cards'
|
||||
checked={filter.showMine}
|
||||
name='test'
|
||||
className='font-medium mr-2'
|
||||
onChange={() =>
|
||||
metricStore.updateKey('filter', {...filter, showMine: !filter.showMine})
|
||||
}
|
||||
/>
|
||||
<Select
|
||||
options={[{label: 'All Types', value: 'all'}, ...DROPDOWN_OPTIONS]}
|
||||
name='type'
|
||||
defaultValue={filter.type}
|
||||
onChange={({value}) =>
|
||||
metricStore.updateKey('filter', {...filter, type: value.value})
|
||||
}
|
||||
plain={true}
|
||||
isSearchable={true}
|
||||
/>
|
||||
|
||||
<DashboardDropdown
|
||||
plain={true}
|
||||
onChange={(value: any) =>
|
||||
metricStore.updateKey('filter', {...filter, dashboard: value})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='flex items-center'>
|
||||
<ListViewToggler/>
|
||||
|
||||
<Select
|
||||
options={[
|
||||
{label: 'Newest', value: 'desc'},
|
||||
{label: 'Oldest', value: 'asc'}
|
||||
]}
|
||||
name='sort'
|
||||
defaultValue={metricStore.sort.by}
|
||||
onChange={({value}) => metricStore.updateKey('sort', {by: value.value})}
|
||||
plain={true}
|
||||
className='ml-4'
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/*<AddCardSelectionModal open={showAddCardModal}/>*/}
|
||||
<NewDashboardModal
|
||||
onClose={() => setShowAddCardModal(false)}
|
||||
open={showAddCardModal}
|
||||
isCreatingNewCard={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='ml-auto flex items-center'>
|
||||
<Button variant='primary'
|
||||
onClick={() => showModal(<AddCardModal siteId={siteId} />, { right: true })}
|
||||
>New Card</Button>
|
||||
<div className='ml-4 w-1/4' style={{ minWidth: 300 }}>
|
||||
<MetricsSearch />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='border-y px-6 py-1 mt-2 flex items-center w-full justify-between'>
|
||||
<div className='items-center flex gap-4'>
|
||||
<Toggler
|
||||
label='My Cards'
|
||||
checked={filter.showMine}
|
||||
name='test'
|
||||
className='font-medium mr-2'
|
||||
onChange={() =>
|
||||
metricStore.updateKey('filter', { ...filter, showMine: !filter.showMine })
|
||||
}
|
||||
/>
|
||||
<Select
|
||||
options={[{ label: 'All Types', value: 'all' }, ...DROPDOWN_OPTIONS]}
|
||||
name='type'
|
||||
defaultValue={filter.type}
|
||||
onChange={({ value }) =>
|
||||
metricStore.updateKey('filter', { ...filter, type: value.value })
|
||||
}
|
||||
plain={true}
|
||||
isSearchable={true}
|
||||
/>
|
||||
|
||||
<DashboardDropdown
|
||||
plain={true}
|
||||
onChange={(value: any) =>
|
||||
metricStore.updateKey('filter', { ...filter, dashboard: value })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='flex items-center'>
|
||||
<ListViewToggler />
|
||||
|
||||
<Select
|
||||
options={[
|
||||
{ label: 'Newest', value: 'desc' },
|
||||
{ label: 'Oldest', value: 'asc' }
|
||||
]}
|
||||
name='sort'
|
||||
defaultValue={metricStore.sort.by}
|
||||
onChange={({ value }) => metricStore.updateKey('sort', { by: value.value })}
|
||||
plain={true}
|
||||
className='ml-4'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
export default observer(MetricViewHeader);
|
||||
|
||||
function DashboardDropdown({ onChange, plain = false }: { plain?: boolean; onChange: any }) {
|
||||
const { dashboardStore, metricStore } = useStore();
|
||||
const dashboardOptions = dashboardStore.dashboards.map((i: any) => ({
|
||||
key: i.id,
|
||||
label: i.name,
|
||||
value: i.dashboardId
|
||||
}));
|
||||
function DashboardDropdown({onChange, plain = false}: { plain?: boolean; onChange: any }) {
|
||||
const {dashboardStore, metricStore} = useStore();
|
||||
const dashboardOptions = dashboardStore.dashboards.map((i: any) => ({
|
||||
key: i.id,
|
||||
label: i.name,
|
||||
value: i.dashboardId
|
||||
}));
|
||||
|
||||
return (
|
||||
<Select
|
||||
isSearchable={true}
|
||||
placeholder='Filter by Dashboard'
|
||||
plain={plain}
|
||||
options={dashboardOptions}
|
||||
value={metricStore.filter.dashboard}
|
||||
onChange={({ value }: any) => onChange(value)}
|
||||
isMulti={true}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<Select
|
||||
isSearchable={true}
|
||||
placeholder='Filter by Dashboard'
|
||||
plain={plain}
|
||||
options={dashboardOptions}
|
||||
value={metricStore.filter.dashboard}
|
||||
onChange={({value}: any) => onChange(value)}
|
||||
isMulti={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function ListViewToggler() {
|
||||
const { metricStore } = useStore();
|
||||
const listView = useObserver(() => metricStore.listView);
|
||||
return (
|
||||
<div className='flex items-center'>
|
||||
<Segmented
|
||||
options={[
|
||||
{
|
||||
label: <div className={'flex items-center gap-2'}>
|
||||
<Icon name={'list-alt'} color={'inherit'} />
|
||||
<div>List</div>
|
||||
</div>,
|
||||
value: 'list'
|
||||
},
|
||||
{
|
||||
label: <div className={'flex items-center gap-2'}>
|
||||
<Icon name={'grid'} color={'inherit'} />
|
||||
<div>Grid</div>
|
||||
</div>,
|
||||
value: 'grid'
|
||||
}
|
||||
]}
|
||||
onChange={(val) => {
|
||||
metricStore.updateKey('listView', val === 'list')
|
||||
}}
|
||||
value={listView ? 'list' : 'grid'}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
const {metricStore} = useStore();
|
||||
const listView = useObserver(() => metricStore.listView);
|
||||
return (
|
||||
<div className='flex items-center'>
|
||||
<Segmented
|
||||
options={[
|
||||
{
|
||||
label: <div className={'flex items-center gap-2'}>
|
||||
<Icon name={'list-alt'} color={'inherit'}/>
|
||||
<div>List</div>
|
||||
</div>,
|
||||
value: 'list'
|
||||
},
|
||||
{
|
||||
label: <div className={'flex items-center gap-2'}>
|
||||
<Icon name={'grid'} color={'inherit'}/>
|
||||
<div>Grid</div>
|
||||
</div>,
|
||||
value: 'grid'
|
||||
}
|
||||
]}
|
||||
onChange={(val) => {
|
||||
metricStore.updateKey('listView', val === 'list')
|
||||
}}
|
||||
value={listView ? 'list' : 'grid'}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import {metricOf, issueOptions, issueCategories} from 'App/constants/filterOptio
|
|||
import {FilterKey} from 'Types/filter/filterType';
|
||||
import {withSiteId, dashboardMetricDetails, metricDetails} from 'App/routes';
|
||||
import {Icon, confirm} from 'UI';
|
||||
import {Card, Input, Space, Button} from 'antd';
|
||||
import {Card, Input, Space, Button, Segmented} from 'antd';
|
||||
import {AudioWaveform} from "lucide-react";
|
||||
import FilterSeries from '../FilterSeries';
|
||||
import Select from 'Shared/Select';
|
||||
|
|
@ -21,6 +21,8 @@ import {
|
|||
import {useParams} from 'react-router-dom';
|
||||
import {useHistory} from "react-router";
|
||||
|
||||
const tableOptions = metricOf.filter((i) => i.type === 'table');
|
||||
|
||||
const AIInput = ({value, setValue, placeholder, onEnter}) => (
|
||||
<Input
|
||||
placeholder={placeholder}
|
||||
|
|
@ -38,7 +40,24 @@ const PredefinedMessage = () => (
|
|||
</div>
|
||||
);
|
||||
|
||||
const MetricOptions = ({metric, writeOption}) => {
|
||||
const MetricTabs = ({metric, writeOption}: any) => {
|
||||
if (![TABLE].includes(metric.metricType)) return null;
|
||||
|
||||
const onChange = (value: string) => {
|
||||
console.log('value', value);
|
||||
writeOption({
|
||||
value: {
|
||||
value
|
||||
}, name: 'metricOf'
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Segmented options={tableOptions} onChange={onChange} selected={metric.metricOf} />
|
||||
)
|
||||
}
|
||||
|
||||
const MetricOptions = ({metric, writeOption}: any) => {
|
||||
const isUserPath = metric.metricType === USER_PATH;
|
||||
|
||||
return (
|
||||
|
|
@ -198,7 +217,6 @@ interface CardBuilderProps {
|
|||
const CardBuilder = observer((props: CardBuilderProps) => {
|
||||
const history = useHistory();
|
||||
const {siteId, dashboardId, metricId} = props;
|
||||
console.log('siteId', siteId);
|
||||
const {metricStore, dashboardStore, aiFiltersStore} = useStore();
|
||||
const [aiQuery, setAiQuery] = useState('');
|
||||
const [aiAskChart, setAiAskChart] = useState('');
|
||||
|
|
@ -209,6 +227,8 @@ const CardBuilder = observer((props: CardBuilderProps) => {
|
|||
const isPredefined = [ERRORS, PERFORMANCE, RESOURCE_MONITORING, WEB_VITALS].includes(metric.metricType);
|
||||
const testingKey = localStorage.getItem('__mauricio_testing_access') === 'true';
|
||||
|
||||
console.log('metric', metric);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (metric && !initialInstance) setInitialInstance(metric.toJson());
|
||||
|
|
@ -276,6 +296,8 @@ const CardBuilder = observer((props: CardBuilderProps) => {
|
|||
{/* metric={metric}*/}
|
||||
{/* writeOption={writeOption}*/}
|
||||
{/*/>*/}
|
||||
<MetricTabs metric={metric}
|
||||
writeOption={writeOption}/>
|
||||
{metric.metricType === USER_PATH && <PathAnalysisFilter metric={metric}/>}
|
||||
{isPredefined && <PredefinedMessage/>}
|
||||
{testingKey && (
|
||||
|
|
|
|||
|
|
@ -359,34 +359,33 @@ export default class DashboardStore {
|
|||
});
|
||||
}
|
||||
|
||||
deleteDashboardWidget(dashboardId: string, widgetId: string) {
|
||||
async deleteDashboardWidget(dashboardId: string, widgetId: string) {
|
||||
this.isDeleting = true;
|
||||
return dashboardService
|
||||
.deleteWidget(dashboardId, widgetId)
|
||||
.then(() => {
|
||||
toast.success('Dashboard updated successfully');
|
||||
runInAction(() => {
|
||||
this.selectedDashboard?.removeWidget(widgetId);
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
this.isDeleting = false;
|
||||
try {
|
||||
await dashboardService
|
||||
.deleteWidget(dashboardId, widgetId);
|
||||
toast.success('Dashboard updated successfully');
|
||||
runInAction(() => {
|
||||
this.selectedDashboard?.removeWidget(widgetId);
|
||||
});
|
||||
} finally {
|
||||
this.isDeleting = false;
|
||||
}
|
||||
}
|
||||
|
||||
addWidgetToDashboard(dashboard: Dashboard, metricIds: any): Promise<any> {
|
||||
async addWidgetToDashboard(dashboard: Dashboard, metricIds: any): Promise<any> {
|
||||
this.isSaving = true;
|
||||
return dashboardService
|
||||
.addWidget(dashboard, metricIds)
|
||||
.then((response) => {
|
||||
try {
|
||||
try {
|
||||
const response = await dashboardService
|
||||
.addWidget(dashboard, metricIds);
|
||||
toast.success('Card added to dashboard.');
|
||||
})
|
||||
.catch(() => {
|
||||
} catch {
|
||||
toast.error('Card could not be added.');
|
||||
})
|
||||
.finally(() => {
|
||||
this.isSaving = false;
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
this.isSaving = false;
|
||||
}
|
||||
}
|
||||
|
||||
setPeriod(period: any) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue