feature(ui): new dashboard modal
This commit is contained in:
parent
0f5b80fe97
commit
b0785d1435
24 changed files with 840 additions and 267 deletions
|
|
@ -0,0 +1,94 @@
|
|||
import React from 'react';
|
||||
import {Button, Space} from "antd";
|
||||
import {ArrowLeft, ArrowRight} from "lucide-react";
|
||||
import CardBuilder from "Components/Dashboard/components/WidgetForm/CardBuilder";
|
||||
import {useHistory} from "react-router";
|
||||
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";
|
||||
|
||||
const getTitleByType = (type: string) => {
|
||||
switch (type) {
|
||||
case CLICKMAP:
|
||||
return 'Clickmap';
|
||||
default:
|
||||
return 'Trend Single';
|
||||
}
|
||||
}
|
||||
|
||||
interface Props {
|
||||
// cardType: string,
|
||||
onBack: () => void
|
||||
}
|
||||
|
||||
function CreateCard(props: Props) {
|
||||
const history = useHistory();
|
||||
const {metricStore, dashboardStore, aiFiltersStore} = useStore();
|
||||
const metric = metricStore.instance;
|
||||
const siteId = history.location.pathname.split('/')[1];
|
||||
// const title = getTitleByType(metric.metricType)
|
||||
|
||||
|
||||
const createNewDashboard = async () => {
|
||||
dashboardStore.initDashboard();
|
||||
return await dashboardStore
|
||||
.save(dashboardStore.dashboardInstance)
|
||||
.then(async (syncedDashboard) => {
|
||||
dashboardStore.selectDashboardById(syncedDashboard.dashboardId);
|
||||
return syncedDashboard.dashboardId;
|
||||
});
|
||||
}
|
||||
|
||||
const addCardToDashboard = async (dashboardId: string, metricId: string) => {
|
||||
return dashboardStore.addWidgetToDashboard(
|
||||
dashboardStore.getDashboard(parseInt(dashboardId, 10))!, [metricId]
|
||||
);
|
||||
}
|
||||
|
||||
const createCard = async () => {
|
||||
const isClickmap = metric.metricType === CLICKMAP;
|
||||
if (isClickmap) {
|
||||
try {
|
||||
metric.thumbnail = await renderClickmapThumbnail();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
const savedMetric = await metricStore.save(metric);
|
||||
return savedMetric.metricId;
|
||||
}
|
||||
|
||||
const createDashboardAndAddCard = async () => {
|
||||
const dashboardId = await createNewDashboard();
|
||||
const cardId = await createCard();
|
||||
await addCardToDashboard(dashboardId, cardId);
|
||||
|
||||
history.replace(`${history.location.pathname}/${dashboardId}`);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex gap-4 flex-col">
|
||||
<div className="flex items-center justify-between">
|
||||
<Space>
|
||||
<Button type="text" onClick={props.onBack}>
|
||||
<ArrowLeft size={16}/>
|
||||
</Button>
|
||||
<div className="text-2xl leading-4 font-semibold">
|
||||
{metric.name}
|
||||
</div>
|
||||
</Space>
|
||||
<Button type="primary" onClick={createDashboardAndAddCard}>
|
||||
<Space>
|
||||
<ArrowRight size={14}/>Create
|
||||
</Space>
|
||||
</Button>
|
||||
</div>
|
||||
<CardBuilder siteId={siteId}/>
|
||||
<WidgetPreview className="mt-8" name={metric.name} isEditing={true}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default CreateCard;
|
||||
|
|
@ -16,7 +16,7 @@ const TYPES = {
|
|||
Users: 'users',
|
||||
};
|
||||
|
||||
function ExampleCount({ onCard }: { onCard: (card: string) => void }) {
|
||||
function ExampleCount(props: any) {
|
||||
const [type, setType] = React.useState(TYPES.Frustrations);
|
||||
|
||||
const el = {
|
||||
|
|
@ -26,11 +26,10 @@ function ExampleCount({ onCard }: { onCard: (card: string) => void }) {
|
|||
};
|
||||
return (
|
||||
<ExCard
|
||||
onCard={onCard}
|
||||
type={'count' + `-${type}`}
|
||||
{...props}
|
||||
title={
|
||||
<div className={'flex items-center gap-2'}>
|
||||
<div>Sessions by</div>
|
||||
<div>{props.title}</div>
|
||||
<div className={'font-normal'}>
|
||||
<Segmented
|
||||
options={[
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ function ExCard({
|
|||
return (
|
||||
<div
|
||||
className={'rounded overflow-hidden border p-4 bg-white hover:border-gray-light hover:shadow'}
|
||||
style={{ width: 400, height: 286 }}
|
||||
style={{ width: '100%', height: 286 }}
|
||||
>
|
||||
<div className={'font-semibold text-lg'}>{title}</div>
|
||||
<div className={'flex flex-col gap-2 mt-2 cursor-pointer'} onClick={() => onCard(type)}>{children}</div>
|
||||
|
|
@ -22,4 +22,4 @@ function ExCard({
|
|||
);
|
||||
}
|
||||
|
||||
export default ExCard
|
||||
export default ExCard
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@ import { ArrowRight } from 'lucide-react';
|
|||
import React from 'react';
|
||||
|
||||
import ExCard from './ExCard';
|
||||
import {FUNNEL} from "App/constants/card";
|
||||
|
||||
function ExampleFunnel({ onCard }: { onCard: (card: string) => void }) {
|
||||
function ExampleFunnel(props: any) {
|
||||
const steps = [
|
||||
{
|
||||
progress: 500,
|
||||
|
|
@ -17,9 +18,7 @@ function ExampleFunnel({ onCard }: { onCard: (card: string) => void }) {
|
|||
];
|
||||
return (
|
||||
<ExCard
|
||||
title={'Funnel'}
|
||||
onCard={onCard}
|
||||
type={'funnel'}
|
||||
{...props}
|
||||
>
|
||||
<>
|
||||
{steps.map((step, index) => (
|
||||
|
|
|
|||
|
|
@ -1,59 +1,59 @@
|
|||
import React from 'react';
|
||||
import { ResponsiveContainer, Sankey } from 'recharts';
|
||||
import {ResponsiveContainer, Sankey} from 'recharts';
|
||||
|
||||
import CustomLink from 'App/components/shared/Insights/SankeyChart/CustomLink';
|
||||
import CustomNode from 'App/components/shared/Insights/SankeyChart/CustomNode';
|
||||
|
||||
import ExCard from './ExCard';
|
||||
import {USER_PATH} from "App/constants/card";
|
||||
|
||||
function ExamplePath({ onCard }: { onCard: (card: string) => void }) {
|
||||
const data = {
|
||||
nodes: [
|
||||
{ idd: 0, name: 'Home' },
|
||||
{ idd: 1, name: 'Google' },
|
||||
{ idd: 2, name: 'Facebook' },
|
||||
{ idd: 3, name: 'Search' },
|
||||
{ idd: 4, name: 'Product' },
|
||||
{ idd: 5, name: 'Chart' },
|
||||
],
|
||||
links: [
|
||||
{ source: 0, target: 3, value: 40 },
|
||||
{ source: 0, target: 4, value: 60 },
|
||||
function ExamplePath(props: any) {
|
||||
const data = {
|
||||
nodes: [
|
||||
{idd: 0, name: 'Home'},
|
||||
{idd: 1, name: 'Google'},
|
||||
{idd: 2, name: 'Facebook'},
|
||||
{idd: 3, name: 'Search'},
|
||||
{idd: 4, name: 'Product'},
|
||||
{idd: 5, name: 'Chart'},
|
||||
],
|
||||
links: [
|
||||
{source: 0, target: 3, value: 40},
|
||||
{source: 0, target: 4, value: 60},
|
||||
|
||||
{ source: 1, target: 3, value: 100 },
|
||||
{ source: 2, target: 3, value: 100 },
|
||||
{source: 1, target: 3, value: 100},
|
||||
{source: 2, target: 3, value: 100},
|
||||
|
||||
{ source: 3, target: 4, value: 50 },
|
||||
{ source: 3, target: 5, value: 50 },
|
||||
{source: 3, target: 4, value: 50},
|
||||
{source: 3, target: 5, value: 50},
|
||||
|
||||
{ source: 4, target: 5, value: 15 },
|
||||
],
|
||||
};
|
||||
return (
|
||||
<ExCard
|
||||
title={'Path Finder'}
|
||||
onCard={onCard}
|
||||
type={'path-finder'}
|
||||
>
|
||||
<ResponsiveContainer width={'100%'} height={230}>
|
||||
<Sankey
|
||||
nodeWidth={6}
|
||||
sort={false}
|
||||
iterations={128}
|
||||
node={<CustomNode isDemo />}
|
||||
link={(linkProps) => <CustomLink {...linkProps} />}
|
||||
data={data}
|
||||
{source: 4, target: 5, value: 15},
|
||||
],
|
||||
};
|
||||
|
||||
return (
|
||||
<ExCard
|
||||
{...props}
|
||||
>
|
||||
<defs>
|
||||
<linearGradient id={'linkGradient'}>
|
||||
<stop offset="0%" stopColor="rgba(57, 78, 255, 0.2)" />
|
||||
<stop offset="100%" stopColor="rgba(57, 78, 255, 0.2)" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</Sankey>
|
||||
</ResponsiveContainer>
|
||||
</ExCard>
|
||||
);
|
||||
<ResponsiveContainer width={'100%'} height={230}>
|
||||
<Sankey
|
||||
nodeWidth={6}
|
||||
sort={false}
|
||||
iterations={128}
|
||||
node={<CustomNode />}
|
||||
link={(linkProps) => <CustomLink {...linkProps} />}
|
||||
data={data}
|
||||
>
|
||||
<defs>
|
||||
<linearGradient id={'linkGradient'}>
|
||||
<stop offset="0%" stopColor="rgba(57, 78, 255, 0.2)"/>
|
||||
<stop offset="100%" stopColor="rgba(57, 78, 255, 0.2)"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</Sankey>
|
||||
</ResponsiveContainer>
|
||||
</ExCard>
|
||||
);
|
||||
}
|
||||
|
||||
export default ExamplePath
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@ import { GitCommitHorizontal } from 'lucide-react';
|
|||
import React from 'react';
|
||||
|
||||
import ExCard from './ExCard';
|
||||
import {PERFORMANCE} from "App/constants/card";
|
||||
|
||||
function PerfBreakdown({ onCard }: { onCard: (card: string) => void }) {
|
||||
function PerfBreakdown(props: any) {
|
||||
const rows = [
|
||||
['5K', '1K'],
|
||||
['4K', '750'],
|
||||
|
|
@ -22,9 +23,7 @@ function PerfBreakdown({ onCard }: { onCard: (card: string) => void }) {
|
|||
const bgs = ['#E2E4F6', '#A7BFFF', '#394EFF'];
|
||||
return (
|
||||
<ExCard
|
||||
title={'Breakdown'}
|
||||
onCard={onCard}
|
||||
type={'perf-breakdown'}
|
||||
{...props}
|
||||
>
|
||||
<div className={'relative'}>
|
||||
<div className={'flex flex-col gap-4'}>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { Icon } from 'UI';
|
|||
import ExCard from '../ExCard';
|
||||
import ByComponent from './Component';
|
||||
|
||||
function ByBrowser({ onCard }: { onCard: (card: string) => void }) {
|
||||
function ByBrowser(props: any) {
|
||||
const rows = [
|
||||
{
|
||||
label: 'Chrome',
|
||||
|
|
@ -42,9 +42,7 @@ function ByBrowser({ onCard }: { onCard: (card: string) => void }) {
|
|||
const lineWidth = 200;
|
||||
return (
|
||||
<ByComponent
|
||||
onCard={onCard}
|
||||
type={'sessions-by-browser'}
|
||||
title={'Sessions by Browser'}
|
||||
{...props}
|
||||
rows={rows}
|
||||
lineWidth={lineWidth}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { Icon } from 'UI';
|
|||
|
||||
import ByComponent from './Component';
|
||||
|
||||
function ByCountry({ onCard }: { onCard: (card: string) => void }) {
|
||||
function ByCountry(props: any) {
|
||||
const rows = [
|
||||
{
|
||||
label: 'United States',
|
||||
|
|
@ -41,10 +41,8 @@ function ByCountry({ onCard }: { onCard: (card: string) => void }) {
|
|||
return (
|
||||
<ByComponent
|
||||
rows={rows}
|
||||
title={'Sessions by Country'}
|
||||
lineWidth={180}
|
||||
onCard={onCard}
|
||||
type={'sessions-by-country'}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { Icon } from 'UI';
|
|||
|
||||
import ByComponent from './Component';
|
||||
|
||||
function BySystem({ onCard }: { onCard: (card: string) => void }) {
|
||||
function BySystem(props: any) {
|
||||
const rows = [
|
||||
{
|
||||
label: 'Windows',
|
||||
|
|
@ -41,9 +41,7 @@ function BySystem({ onCard }: { onCard: (card: string) => void }) {
|
|||
const lineWidth = 200;
|
||||
return (
|
||||
<ByComponent
|
||||
onCard={onCard}
|
||||
type={'sessions-by-system'}
|
||||
title={'Sessions by Operating System'}
|
||||
{...props}
|
||||
rows={rows}
|
||||
lineWidth={lineWidth}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import React from 'react';
|
|||
import { Circle } from '../Count';
|
||||
import ExCard from '../ExCard';
|
||||
|
||||
function ByUrl({ onCard }: { onCard: (card: string) => void }) {
|
||||
function ByUrl(props: any) {
|
||||
const [mode, setMode] = React.useState(0);
|
||||
const rows = [
|
||||
{
|
||||
|
|
@ -48,11 +48,10 @@ function ByUrl({ onCard }: { onCard: (card: string) => void }) {
|
|||
const lineWidth = 240;
|
||||
return (
|
||||
<ExCard
|
||||
onCard={onCard}
|
||||
type={'sessions-by-url'}
|
||||
{...props}
|
||||
title={
|
||||
<div className={'flex gap-2 items-center'}>
|
||||
<div>Sessions by</div>
|
||||
<div>{props.title}</div>
|
||||
<div className={'font-normal'}><Segmented
|
||||
options={[
|
||||
{ label: 'URL', value: '0' },
|
||||
|
|
|
|||
|
|
@ -2,16 +2,14 @@ import React from 'react'
|
|||
import ExCard from "./ExCard";
|
||||
import { Errors } from "./Count";
|
||||
|
||||
function SessionsByErrors({ onCard }: { onCard: (card: string) => void }) {
|
||||
function SessionsByErrors(props: any) {
|
||||
return (
|
||||
<ExCard
|
||||
onCard={onCard}
|
||||
type={'sessions-by-errors'}
|
||||
title={'Sessions by Errors'}
|
||||
{...props}
|
||||
>
|
||||
<Errors />
|
||||
</ExCard>
|
||||
);
|
||||
}
|
||||
|
||||
export default SessionsByErrors
|
||||
export default SessionsByErrors
|
||||
|
|
|
|||
|
|
@ -2,16 +2,14 @@ import React from 'react'
|
|||
import ExCard from "./ExCard";
|
||||
import { Frustrations } from "./Count";
|
||||
|
||||
function SessionsByIssues({ onCard }: { onCard: (card: string) => void }) {
|
||||
function SessionsByIssues(props: any) {
|
||||
return (
|
||||
<ExCard
|
||||
onCard={onCard}
|
||||
type={'sessions-by-issues'}
|
||||
title={'Sessions by Issues'}
|
||||
{...props}
|
||||
>
|
||||
<Frustrations />
|
||||
</ExCard>
|
||||
);
|
||||
}
|
||||
|
||||
export default SessionsByIssues
|
||||
export default SessionsByIssues
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import React from 'react';
|
|||
import { Circle } from './Count';
|
||||
import ExCard from './ExCard';
|
||||
|
||||
function SlowestDomain({ onCard }: { onCard: (card: string) => void }) {
|
||||
function SlowestDomain(props: any) {
|
||||
const rows = [
|
||||
{
|
||||
label: 'kroger.com',
|
||||
|
|
@ -42,9 +42,7 @@ function SlowestDomain({ onCard }: { onCard: (card: string) => void }) {
|
|||
|
||||
return (
|
||||
<ExCard
|
||||
type={'slowest'}
|
||||
onCard={onCard}
|
||||
title={'Slowest Domain'}
|
||||
{...props}
|
||||
>
|
||||
<div className={'flex gap-1 flex-col'}>
|
||||
{rows.map((r) => (
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
import React from 'react';
|
||||
import PerfBreakdown from '../PerfBreakdown';
|
||||
import SlowestDomain from '../SlowestDomain';
|
||||
import SessionsByIssues from '../SessionsByIssues';
|
||||
import SessionsByErrors from '../SessionsByErrors';
|
||||
|
||||
interface ExampleProps {
|
||||
onCard: (card: string) => void;
|
||||
}
|
||||
|
||||
const CoreWebVitals: React.FC<ExampleProps> = ({onCard}) => (
|
||||
<>
|
||||
<PerfBreakdown onCard={onCard}/>
|
||||
<SlowestDomain onCard={onCard}/>
|
||||
<SessionsByIssues onCard={onCard}/>
|
||||
<SessionsByErrors onCard={onCard}/>
|
||||
</>
|
||||
);
|
||||
|
||||
export default CoreWebVitals;
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import React from 'react';
|
||||
import PerfBreakdown from '../PerfBreakdown';
|
||||
import SlowestDomain from '../SlowestDomain';
|
||||
import SessionsByErrors from '../SessionsByErrors';
|
||||
import SessionsByIssues from '../SessionsByIssues';
|
||||
|
||||
interface ExampleProps {
|
||||
onCard: (card: string) => void;
|
||||
}
|
||||
|
||||
const PerformanceMonitoring: React.FC<ExampleProps> = ({onCard}) => (
|
||||
<>
|
||||
<PerfBreakdown onCard={onCard}/>
|
||||
<SlowestDomain onCard={onCard}/>
|
||||
<SessionsByErrors onCard={onCard}/>
|
||||
<SessionsByIssues onCard={onCard}/>
|
||||
</>
|
||||
);
|
||||
|
||||
export default PerformanceMonitoring;
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import React from 'react';
|
||||
import ExampleFunnel from '../Funnel';
|
||||
import ExamplePath from '../Path';
|
||||
import ExampleTrend from '../Trend';
|
||||
import ExampleCount from '../Count';
|
||||
|
||||
interface ExampleProps {
|
||||
onCard: (card: string) => void;
|
||||
}
|
||||
|
||||
const ProductAnalytics: React.FC<ExampleProps> = ({ onCard }) => (
|
||||
<>
|
||||
<ExampleFunnel onCard={onCard} />
|
||||
<ExamplePath onCard={onCard} />
|
||||
<ExampleTrend onCard={onCard} />
|
||||
<ExampleCount onCard={onCard} />
|
||||
</>
|
||||
);
|
||||
|
||||
export default ProductAnalytics;
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import React from 'react';
|
||||
import ByBrowser from '../SessionsBy/ByBrowser';
|
||||
import BySystem from '../SessionsBy/BySystem';
|
||||
import ByCountry from '../SessionsBy/ByCountry';
|
||||
import ByUrl from '../SessionsBy/ByUrl';
|
||||
|
||||
interface ExampleProps {
|
||||
onCard: (card: string) => void;
|
||||
}
|
||||
|
||||
const WebAnalytics: React.FC<ExampleProps> = ({onCard}) => (
|
||||
<>
|
||||
<ByBrowser onCard={onCard}/>
|
||||
<BySystem onCard={onCard}/>
|
||||
<ByCountry onCard={onCard}/>
|
||||
<ByUrl onCard={onCard}/>
|
||||
</>
|
||||
);
|
||||
|
||||
export default WebAnalytics;
|
||||
|
|
@ -3,18 +3,19 @@ import React from 'react';
|
|||
|
||||
import ExCard from './ExCard';
|
||||
|
||||
function ExampleTrend({ onCard }: { onCard: (card: string) => void }) {
|
||||
function ExampleTrend(props: any) {
|
||||
const rows = [50, 40, 30, 20, 10];
|
||||
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May'];
|
||||
|
||||
const [isMulti, setIsMulti] = React.useState(false);
|
||||
return (
|
||||
<ExCard
|
||||
onCard={onCard}
|
||||
type={'trend' + (isMulti ? '-multi' : '-single')}
|
||||
{...props}
|
||||
// onCard={onCard}
|
||||
// type={'trend' + (isMulti ? '-multi' : '-single')}
|
||||
title={
|
||||
<div className={'flex items-center gap-2'}>
|
||||
<div>Trend</div>
|
||||
<div>{props.title}</div>
|
||||
<div className={'font-normal'}>
|
||||
<Segmented
|
||||
options={[
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
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;
|
||||
}
|
||||
|
||||
const NewDashboardModal: React.FC<NewDashboardModalProps> = ({onClose, open}) => {
|
||||
const [step, setStep] = React.useState<number>(0);
|
||||
const [selectedCard, setSelectedCard] = React.useState<string>('trend-single');
|
||||
const {metricStore} = useStore();
|
||||
|
||||
const onCard = (card: any) => {
|
||||
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);
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<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 === 1 && <CreateCard onBack={() => setStep(0)}/>}
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default NewDashboardModal;
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import React from 'react';
|
||||
import {LucideIcon} from "lucide-react";
|
||||
|
||||
interface OptionProps {
|
||||
label: string;
|
||||
Icon: LucideIcon;
|
||||
}
|
||||
|
||||
const Option: React.FC<OptionProps> = ({label, Icon}) => (
|
||||
<div className="flex items-center gap-2">
|
||||
<Icon size={16} strokeWidth={1}/>
|
||||
<div>{label}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default Option;
|
||||
|
|
@ -0,0 +1,228 @@
|
|||
import React, {useMemo} from 'react';
|
||||
import {Segmented} from 'antd';
|
||||
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";
|
||||
|
||||
interface SelectCardProps {
|
||||
onClose: () => void;
|
||||
onCard: (card: any) => void;
|
||||
}
|
||||
|
||||
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 [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 item = 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}/>
|
||||
</div>
|
||||
));
|
||||
}, [selected]);
|
||||
|
||||
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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SelectCard;
|
||||
|
|
@ -0,0 +1 @@
|
|||
export {default} from './NewDashboardModal';
|
||||
|
|
@ -1,174 +0,0 @@
|
|||
import { Segmented } from 'antd';
|
||||
import { Activity, BarChart, TableCellsMerge, TrendingUp } from 'lucide-react';
|
||||
import React from 'react';
|
||||
|
||||
import { Modal } from 'UI';
|
||||
|
||||
import ExampleCount from './Examples/Count';
|
||||
import ExampleFunnel from './Examples/Funnel';
|
||||
import ExamplePath from './Examples/Path';
|
||||
import PerfBreakdown from './Examples/PerfBreakdown';
|
||||
import ByBrowser from './Examples/SessionsBy/ByBrowser';
|
||||
import ByCountry from './Examples/SessionsBy/ByCountry';
|
||||
import BySystem from './Examples/SessionsBy/BySystem';
|
||||
import ByUrl from './Examples/SessionsBy/ByUrl';
|
||||
import SessionsByErrors from './Examples/SessionsByErrors';
|
||||
import SessionsByIssues from './Examples/SessionsByIssues';
|
||||
import SlowestDomain from './Examples/SlowestDomain';
|
||||
import ExampleTrend from './Examples/Trend';
|
||||
|
||||
function NewDashboardModal(props: { onClose: () => void; open: boolean }) {
|
||||
const [step, setStep] = React.useState(0);
|
||||
|
||||
const onCard = (card: string) => {
|
||||
console.log(card);
|
||||
};
|
||||
return (
|
||||
<Modal onClose={props.onClose} open={props.open} size={'xlarge'}>
|
||||
<Modal.Content className={'bg-[#FAFAFA]'}>
|
||||
<div className={'flex flex-col gap-4'}>
|
||||
{step === 0 ? (
|
||||
<SelectCard onClose={props.onClose} onCard={onCard} />
|
||||
) : null}
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
function SelectCard({
|
||||
onClose,
|
||||
onCard,
|
||||
}: {
|
||||
onClose: () => void;
|
||||
onCard: (card: string) => void;
|
||||
}) {
|
||||
const initial = 'product-analytics';
|
||||
const [selected, setSelected] = React.useState(initial);
|
||||
let item;
|
||||
switch (selected) {
|
||||
case 'product-analytics':
|
||||
item = <ProductAnalytics onCard={onCard} />;
|
||||
break;
|
||||
case 'performance-monitoring':
|
||||
item = <PerformanceMonitoring onCard={onCard} />;
|
||||
break;
|
||||
case 'web-analytics':
|
||||
item = <WebAnalytics onCard={onCard} />;
|
||||
break;
|
||||
case 'core-web-vitals':
|
||||
item = <CoreWebVitals onCard={onCard} />;
|
||||
break;
|
||||
default:
|
||||
item = <div>under construction</div>;
|
||||
break;
|
||||
}
|
||||
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 className={'link'} onClick={onClose}>
|
||||
Close
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Segmented
|
||||
options={[
|
||||
{
|
||||
label: (
|
||||
<div className={'flex items-center gap-2'}>
|
||||
<TrendingUp size={16} strokeWidth={1} />
|
||||
<div>Product Analytics</div>
|
||||
</div>
|
||||
),
|
||||
value: 'product-analytics',
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<div className={'flex items-center gap-2'}>
|
||||
<Activity size={16} strokeWidth={1} />
|
||||
<div>Performance Monitoring</div>
|
||||
</div>
|
||||
),
|
||||
value: 'performance-monitoring',
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<div className={'flex items-center gap-2'}>
|
||||
<BarChart size={16} strokeWidth={1} />
|
||||
<div>Web Analytics</div>
|
||||
</div>
|
||||
),
|
||||
value: 'web-analytics',
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<div className={'flex items-center gap-2'}>
|
||||
<TableCellsMerge size={16} strokeWidth={1} />
|
||||
<div>Core Web Vitals</div>
|
||||
</div>
|
||||
),
|
||||
value: 'core-web-vitals',
|
||||
},
|
||||
]}
|
||||
onChange={(v) => setSelected(v)}
|
||||
/>
|
||||
|
||||
<div
|
||||
style={{ maxHeight: 'calc(100vh - 210px)' }}
|
||||
className={'mt-2 w-full flex flex-wrap gap-2 overflow-scroll'}
|
||||
>
|
||||
{item}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function ProductAnalytics({ onCard }: { onCard: (card: string) => void }) {
|
||||
return (
|
||||
<>
|
||||
<ExampleFunnel onCard={onCard} />
|
||||
<ExamplePath onCard={onCard} />
|
||||
<ExampleTrend onCard={onCard} />
|
||||
<ExampleCount onCard={onCard} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function PerformanceMonitoring({ onCard }: { onCard: (card: string) => void }) {
|
||||
return (
|
||||
<>
|
||||
<PerfBreakdown onCard={onCard} />
|
||||
<SlowestDomain onCard={onCard} />
|
||||
<SessionsByErrors onCard={onCard} />
|
||||
<SessionsByIssues onCard={onCard} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function WebAnalytics({ onCard }: { onCard: (card: string) => void }) {
|
||||
return (
|
||||
<>
|
||||
<ByBrowser onCard={onCard} />
|
||||
<BySystem onCard={onCard} />
|
||||
<ByCountry onCard={onCard} />
|
||||
<ByUrl onCard={onCard} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function CoreWebVitals({ onCard }: { onCard: (card: string) => void }) {
|
||||
return (
|
||||
<>
|
||||
<PerfBreakdown onCard={onCard} />
|
||||
<SlowestDomain onCard={onCard} />
|
||||
<SessionsByIssues onCard={onCard} />
|
||||
<SessionsByErrors onCard={onCard} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default NewDashboardModal;
|
||||
|
|
@ -0,0 +1,292 @@
|
|||
import React, {useEffect, useState, useCallback} from 'react';
|
||||
import {observer} from 'mobx-react-lite';
|
||||
import {useStore} from 'App/mstore';
|
||||
import {metricOf, issueOptions, issueCategories} from 'App/constants/filterOptions';
|
||||
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 {AudioWaveform} from "lucide-react";
|
||||
import FilterSeries from '../FilterSeries';
|
||||
import Select from 'Shared/Select';
|
||||
import MetricTypeDropdown from './components/MetricTypeDropdown';
|
||||
import MetricSubtypeDropdown from './components/MetricSubtypeDropdown';
|
||||
import {eventKeys} from 'App/types/filter/newFilter';
|
||||
import {renderClickmapThumbnail} from './renderMap';
|
||||
import FilterItem from 'Shared/Filters/FilterItem';
|
||||
import {
|
||||
TIMESERIES, TABLE, CLICKMAP, FUNNEL, ERRORS, RESOURCE_MONITORING,
|
||||
PERFORMANCE, WEB_VITALS, INSIGHTS, USER_PATH, RETENTION
|
||||
} from 'App/constants/card';
|
||||
import {useParams} from 'react-router-dom';
|
||||
import {useHistory} from "react-router";
|
||||
|
||||
const AIInput = ({value, setValue, placeholder, onEnter}) => (
|
||||
<Input
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
className='w-full mb-2'
|
||||
onKeyDown={(e) => e.key === 'Enter' && onEnter()}
|
||||
/>
|
||||
);
|
||||
|
||||
const PredefinedMessage = () => (
|
||||
<div className='flex items-center my-6 justify-center'>
|
||||
<Icon name='info-circle' size='18' color='gray-medium'/>
|
||||
<div className='ml-2'>Filtering and drill-downs will be supported soon for this card type.</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const MetricOptions = ({metric, writeOption}) => {
|
||||
const isUserPath = metric.metricType === USER_PATH;
|
||||
|
||||
return (
|
||||
<div className='form-group'>
|
||||
<div className='flex items-center'>
|
||||
<span className='mr-2'>Card showing</span>
|
||||
<MetricTypeDropdown onSelect={writeOption}/>
|
||||
<MetricSubtypeDropdown onSelect={writeOption}/>
|
||||
{isUserPath && (
|
||||
<>
|
||||
<span className='mx-3'></span>
|
||||
<Select
|
||||
name='startType'
|
||||
options={[
|
||||
{value: 'start', label: 'With Start Point'},
|
||||
{value: 'end', label: 'With End Point'}
|
||||
]}
|
||||
defaultValue={metric.startType}
|
||||
onChange={writeOption}
|
||||
placeholder='All Issues'
|
||||
/>
|
||||
<span className='mx-3'>showing</span>
|
||||
<Select
|
||||
name='metricValue'
|
||||
options={[
|
||||
{value: 'location', label: 'Pages'},
|
||||
{value: 'click', label: 'Clicks'},
|
||||
{value: 'input', label: 'Input'},
|
||||
{value: 'custom', label: 'Custom'},
|
||||
]}
|
||||
defaultValue={metric.metricValue}
|
||||
isMulti
|
||||
onChange={writeOption}
|
||||
placeholder='All Issues'
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{metric.metricOf === FilterKey.ISSUE && metric.metricType === TABLE && (
|
||||
<>
|
||||
<span className='mx-3'>issue type</span>
|
||||
<Select
|
||||
name='metricValue'
|
||||
options={issueOptions}
|
||||
value={metric.metricValue}
|
||||
onChange={writeOption}
|
||||
isMulti
|
||||
placeholder='All Issues'
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{metric.metricType === INSIGHTS && (
|
||||
<>
|
||||
<span className='mx-3'>of</span>
|
||||
<Select
|
||||
name='metricValue'
|
||||
options={issueCategories}
|
||||
value={metric.metricValue}
|
||||
onChange={writeOption}
|
||||
isMulti
|
||||
placeholder='All Categories'
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{metric.metricType === TABLE &&
|
||||
!(metric.metricOf === FilterKey.ERRORS || metric.metricOf === FilterKey.SESSIONS) && (
|
||||
<>
|
||||
<span className='mx-3'>showing</span>
|
||||
<Select
|
||||
name='metricFormat'
|
||||
options={[{value: 'sessionCount', label: 'Session Count'}]}
|
||||
defaultValue={metric.metricFormat}
|
||||
onChange={writeOption}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const PathAnalysisFilter = ({metric}) => (
|
||||
<div className='form-group flex flex-col'>
|
||||
{metric.startType === 'start' ? 'Start Point' : 'End Point'}
|
||||
<FilterItem
|
||||
hideDelete
|
||||
filter={metric.startPoint}
|
||||
allowedFilterKeys={[FilterKey.LOCATION, FilterKey.CLICK, FilterKey.INPUT, FilterKey.CUSTOM]}
|
||||
onUpdate={val => metric.updateStartPoint(val)}
|
||||
onRemoveFilter={() => {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
const SeriesList = observer(() => {
|
||||
const {metricStore, dashboardStore, aiFiltersStore} = useStore();
|
||||
const metric = metricStore.instance;
|
||||
const excludeFilterKeys = [CLICKMAP, USER_PATH].includes(metric.metricType) ? eventKeys : [];
|
||||
const hasSeries = ![TABLE, FUNNEL, CLICKMAP, INSIGHTS, USER_PATH, RETENTION].includes(metric.metricType);
|
||||
const canAddSeries = metric.series.length < 3;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{metric.series.length > 0 && metric.series
|
||||
.slice(0, hasSeries ? metric.series.length : 1)
|
||||
.map((series, index) => (
|
||||
<div className='mb-2' key={series.name}>
|
||||
<FilterSeries
|
||||
canExclude={metric.metricType === USER_PATH}
|
||||
supportsEmpty={![CLICKMAP, USER_PATH].includes(metric.metricType)}
|
||||
excludeFilterKeys={excludeFilterKeys}
|
||||
observeChanges={() => metric.updateKey('hasChanged', true)}
|
||||
hideHeader={[TABLE, CLICKMAP, INSIGHTS, USER_PATH, FUNNEL].includes(metric.metricType)}
|
||||
seriesIndex={index}
|
||||
series={series}
|
||||
onRemoveSeries={() => metric.removeSeries(index)}
|
||||
canDelete={metric.series.length > 1}
|
||||
emptyMessage={
|
||||
metric.metricType === TABLE
|
||||
? 'Filter data using any event or attribute. Use Add Step button below to do so.'
|
||||
: 'Add user event or filter to define the series by clicking Add Step.'
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
{hasSeries && (
|
||||
<Card styles={{body: {padding: '4px'}}}>
|
||||
<Button
|
||||
type='link'
|
||||
onClick={() => metric.addSeries()}
|
||||
disabled={!canAddSeries}
|
||||
size="small"
|
||||
>
|
||||
<Space>
|
||||
<AudioWaveform size={16}/>
|
||||
New Chart Series
|
||||
</Space>
|
||||
</Button>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
interface RouteParams {
|
||||
siteId: string;
|
||||
dashboardId: string;
|
||||
metricId: string;
|
||||
}
|
||||
|
||||
const CardBuilder = observer(() => {
|
||||
const history = useHistory();
|
||||
const {siteId, dashboardId} = useParams<RouteParams>();
|
||||
console.log('siteId', siteId);
|
||||
const {metricStore, dashboardStore, aiFiltersStore} = useStore();
|
||||
const [aiQuery, setAiQuery] = useState('');
|
||||
const [aiAskChart, setAiAskChart] = useState('');
|
||||
const [initialInstance, setInitialInstance] = useState(null);
|
||||
const metric = metricStore.instance;
|
||||
const timeseriesOptions = metricOf.filter(i => i.type === 'timeseries');
|
||||
const tableOptions = metricOf.filter(i => i.type === 'table');
|
||||
const isPredefined = [ERRORS, PERFORMANCE, RESOURCE_MONITORING, WEB_VITALS].includes(metric.metricType);
|
||||
const testingKey = localStorage.getItem('__mauricio_testing_access') === 'true';
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (metric && !initialInstance) setInitialInstance(metric.toJson());
|
||||
}, [metric]);
|
||||
|
||||
const writeOption = useCallback(({value, name}) => {
|
||||
value = Array.isArray(value) ? value : value.value;
|
||||
const obj: any = {[name]: value};
|
||||
if (name === 'metricType') {
|
||||
if (value === TIMESERIES) obj.metricOf = timeseriesOptions[0].value;
|
||||
if (value === TABLE) obj.metricOf = tableOptions[0].value;
|
||||
}
|
||||
metricStore.merge(obj);
|
||||
}, [metricStore, timeseriesOptions, tableOptions]);
|
||||
|
||||
const onSave = useCallback(async () => {
|
||||
const wasCreating = !metric.exists();
|
||||
if (metric.metricType === CLICKMAP) {
|
||||
try {
|
||||
metric.thumbnail = await renderClickmapThumbnail();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
const savedMetric = await metricStore.save(metric);
|
||||
setInitialInstance(metric.toJson());
|
||||
if (wasCreating) {
|
||||
const route = parseInt(dashboardId, 10) > 0
|
||||
? withSiteId(dashboardMetricDetails(dashboardId, savedMetric.metricId), siteId)
|
||||
: withSiteId(metricDetails(savedMetric.metricId), siteId);
|
||||
history.replace(route);
|
||||
if (parseInt(dashboardId, 10) > 0) {
|
||||
dashboardStore.addWidgetToDashboard(
|
||||
dashboardStore.getDashboard(parseInt(dashboardId, 10)),
|
||||
[savedMetric.metricId]
|
||||
);
|
||||
}
|
||||
}
|
||||
}, [dashboardId, dashboardStore, history, metric, metricStore, siteId]);
|
||||
|
||||
const onDelete = useCallback(async () => {
|
||||
if (await confirm({
|
||||
header: 'Confirm',
|
||||
confirmButton: 'Yes, delete',
|
||||
confirmation: 'Are you sure you want to permanently delete this card?'
|
||||
})) {
|
||||
metricStore.delete(metric).then(onDelete);
|
||||
}
|
||||
}, [metric, metricStore]);
|
||||
|
||||
const undoChanges = useCallback(() => {
|
||||
const w = new Widget();
|
||||
metricStore.merge(w.fromJson(initialInstance), false);
|
||||
}, [initialInstance, metricStore]);
|
||||
|
||||
const fetchResults = useCallback(() => aiFiltersStore.getCardFilters(aiQuery, metric.metricType)
|
||||
.then(f => metric.createSeries(f.filters)), [aiFiltersStore, aiQuery, metric]);
|
||||
|
||||
const fetchChartData = useCallback(() => aiFiltersStore.getCardData(aiAskChart, metric.toJson()),
|
||||
[aiAskChart, aiFiltersStore, metric]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<MetricOptions
|
||||
metric={metric}
|
||||
writeOption={writeOption}
|
||||
/>
|
||||
{metric.metricType === USER_PATH && <PathAnalysisFilter metric={metric}/>}
|
||||
{isPredefined && <PredefinedMessage/>}
|
||||
{testingKey && (
|
||||
<>
|
||||
<AIInput value={aiQuery} setValue={setAiQuery} placeholder="AI Query" onEnter={fetchResults}/>
|
||||
<AIInput value={aiAskChart} setValue={setAiAskChart} placeholder="AI Ask Chart"
|
||||
onEnter={fetchChartData}/>
|
||||
</>
|
||||
)}
|
||||
{aiFiltersStore.isLoading && (
|
||||
<div>
|
||||
<div className='flex items-center font-medium py-2'>Loading</div>
|
||||
</div>
|
||||
)}
|
||||
{!isPredefined && <SeriesList/>}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default CardBuilder;
|
||||
Loading…
Add table
Reference in a new issue