change(ui): new cards

This commit is contained in:
Shekar Siri 2024-06-21 15:21:19 +02:00
parent cb24dd4bb9
commit 403b28f2d9
11 changed files with 707 additions and 529 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 && (

View file

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