- {children}
-
+ {icon && {icon} }
+
diff --git a/src/app/(main)/reports/[reportId]/Report.module.css b/src/app/(main)/reports/[reportId]/Report.module.css
index be5bb815bc..6aa6a9b329 100644
--- a/src/app/(main)/reports/[reportId]/Report.module.css
+++ b/src/app/(main)/reports/[reportId]/Report.module.css
@@ -3,4 +3,5 @@
grid-template-rows: max-content 1fr;
grid-template-columns: max-content 1fr;
margin-bottom: 60px;
+ height: 90vh;
}
diff --git a/src/app/(main)/reports/[reportId]/ReportBody.module.css b/src/app/(main)/reports/[reportId]/ReportBody.module.css
index 5fb4259a66..9af1070ab7 100644
--- a/src/app/(main)/reports/[reportId]/ReportBody.module.css
+++ b/src/app/(main)/reports/[reportId]/ReportBody.module.css
@@ -1,5 +1,5 @@
.body {
padding-inline-start: 20px;
- grid-row: 2/3;
+ grid-row: 2 / 3;
grid-column: 2 / 3;
}
diff --git a/src/app/(main)/reports/[reportId]/ReportBody.tsx b/src/app/(main)/reports/[reportId]/ReportBody.tsx
index 6f4627f687..9a740c5ec7 100644
--- a/src/app/(main)/reports/[reportId]/ReportBody.tsx
+++ b/src/app/(main)/reports/[reportId]/ReportBody.tsx
@@ -1,6 +1,6 @@
-import styles from './ReportBody.module.css';
import { useContext } from 'react';
import { ReportContext } from './Report';
+import styles from './ReportBody.module.css';
export function ReportBody({ children }) {
const { report } = useContext(ReportContext);
diff --git a/src/app/(main)/reports/[reportId]/ReportHeader.tsx b/src/app/(main)/reports/[reportId]/ReportHeader.tsx
index 2936d806ca..7ab80fcdfb 100644
--- a/src/app/(main)/reports/[reportId]/ReportHeader.tsx
+++ b/src/app/(main)/reports/[reportId]/ReportHeader.tsx
@@ -60,7 +60,7 @@ export function ReportHeader({ icon }) {
{children}
+ {children}
;
+ return (
+
+
+ );
}
export default ReportMenu;
diff --git a/src/app/(main)/reports/[reportId]/ReportPage.tsx b/src/app/(main)/reports/[reportId]/ReportPage.tsx
index 7ecebd3182..da1a0342e6 100644
--- a/src/app/(main)/reports/[reportId]/ReportPage.tsx
+++ b/src/app/(main)/reports/[reportId]/ReportPage.tsx
@@ -1,10 +1,13 @@
'use client';
-import FunnelReport from '../funnel/FunnelReport';
+import { useReport } from 'components/hooks';
import EventDataReport from '../event-data/EventDataReport';
+import FunnelReport from '../funnel/FunnelReport';
+import GoalReport from '../goals/GoalsReport';
import InsightsReport from '../insights/InsightsReport';
+import JourneyReport from '../journey/JourneyReport';
import RetentionReport from '../retention/RetentionReport';
import UTMReport from '../utm/UTMReport';
-import { useReport } from 'components/hooks';
+import RevenueReport from '../revenue/RevenueReport';
const reports = {
funnel: FunnelReport,
@@ -12,6 +15,9 @@ const reports = {
insights: InsightsReport,
retention: RetentionReport,
utm: UTMReport,
+ goals: GoalReport,
+ journey: JourneyReport,
+ revenue: RevenueReport,
};
export default function ReportPage({ reportId }: { reportId: string }) {
diff --git a/src/app/(main)/reports/create/ReportTemplates.tsx b/src/app/(main)/reports/create/ReportTemplates.tsx
index 1bd84aec58..e37efc0367 100644
--- a/src/app/(main)/reports/create/ReportTemplates.tsx
+++ b/src/app/(main)/reports/create/ReportTemplates.tsx
@@ -1,12 +1,14 @@
-import Link from 'next/link';
-import { Button, Icons, Text, Icon } from 'react-basics';
-import PageHeader from 'components/layout/PageHeader';
import Funnel from 'assets/funnel.svg';
import Lightbulb from 'assets/lightbulb.svg';
import Magnet from 'assets/magnet.svg';
+import Path from 'assets/path.svg';
import Tag from 'assets/tag.svg';
-import styles from './ReportTemplates.module.css';
+import Target from 'assets/target.svg';
import { useMessages, useTeamUrl } from 'components/hooks';
+import PageHeader from 'components/layout/PageHeader';
+import Link from 'next/link';
+import { Button, Icon, Icons, Text } from 'react-basics';
+import styles from './ReportTemplates.module.css';
export function ReportTemplates({ showHeader = true }: { showHeader?: boolean }) {
const { formatMessage, labels } = useMessages();
@@ -37,6 +39,18 @@ export function ReportTemplates({ showHeader = true }: { showHeader?: boolean })
url: renderTeamUrl('/reports/utm'),
icon: setCollapsed(!collapsed)}>
+
+
+
+
+ {!collapsed && children}
+
-
diff --git a/src/app/(main)/reports/goals/GoalsAddForm.module.css b/src/app/(main)/reports/goals/GoalsAddForm.module.css
new file mode 100644
index 0000000000..a254ff088e
--- /dev/null
+++ b/src/app/(main)/reports/goals/GoalsAddForm.module.css
@@ -0,0 +1,7 @@
+.dropdown {
+ width: 140px;
+}
+
+.input {
+ width: 200px;
+}
diff --git a/src/app/(main)/reports/goals/GoalsAddForm.tsx b/src/app/(main)/reports/goals/GoalsAddForm.tsx
new file mode 100644
index 0000000000..a82eea28c9
--- /dev/null
+++ b/src/app/(main)/reports/goals/GoalsAddForm.tsx
@@ -0,0 +1,143 @@
+import { useMessages } from 'components/hooks';
+import { useState } from 'react';
+import { Button, Dropdown, Flexbox, FormRow, Item, TextField } from 'react-basics';
+import styles from './GoalsAddForm.module.css';
+
+export function GoalsAddForm({
+ type: defaultType = 'url',
+ value: defaultValue = '',
+ property: defaultProperty = '',
+ operator: defaultAggregae = null,
+ goal: defaultGoal = 10,
+ onChange,
+}: {
+ type?: string;
+ value?: string;
+ operator?: string;
+ property?: string;
+ goal?: number;
+ onChange?: (step: {
+ type: string;
+ value: string;
+ goal: number;
+ operator?: string;
+ property?: string;
+ }) => void;
+}) {
+ const [type, setType] = useState(defaultType);
+ const [value, setValue] = useState(defaultValue);
+ const [operator, setOperator] = useState(defaultAggregae);
+ const [property, setProperty] = useState(defaultProperty);
+ const [goal, setGoal] = useState(defaultGoal);
+ const { formatMessage, labels } = useMessages();
+ const items = [
+ { label: formatMessage(labels.url), value: 'url' },
+ { label: formatMessage(labels.event), value: 'event' },
+ { label: formatMessage(labels.eventData), value: 'event-data' },
+ ];
+ const operators = [
+ { label: formatMessage(labels.count), value: 'count' },
+ { label: formatMessage(labels.average), value: 'average' },
+ { label: formatMessage(labels.sum), value: 'sum' },
+ ];
+ const isDisabled = !type || !value;
+
+ const handleSave = () => {
+ onChange(
+ type === 'event-data' ? { type, value, goal, operator, property } : { type, value, goal },
+ );
+ setValue('');
+ setProperty('');
+ setGoal(10);
+ };
+
+ const handleChange = (e, set) => {
+ set(e.target.value);
+ };
+
+ const handleKeyDown = e => {
+ if (e.key === 'Enter') {
+ e.stopPropagation();
+ handleSave();
+ }
+ };
+
+ const renderTypeValue = (value: any) => {
+ return items.find(item => item.value === value)?.label;
+ };
+
+ const renderoperatorValue = (value: any) => {
+ return operators.find(item => item.value === value)?.label;
+ };
+
+ return (
+
- {step.type === 'url' ? : }
-
{step.value}
+ {data?.map(({ type, value, goal, result, property, operator }, index: number) => {
+ const percent = result > goal ? 100 : (result / goal) * 100;
+
+ return (
+
+ );
+}
+
+export default GoalsChart;
diff --git a/src/app/(main)/reports/goals/GoalsParameters.module.css b/src/app/(main)/reports/goals/GoalsParameters.module.css
new file mode 100644
index 0000000000..cd72f524e8
--- /dev/null
+++ b/src/app/(main)/reports/goals/GoalsParameters.module.css
@@ -0,0 +1,25 @@
+.value {
+ width: 100%;
+ margin-bottom: 8px;
+ font-weight: 600;
+}
+
+.eventData {
+ color: var(--orange900);
+ background-color: var(--orange100);
+ font-size: 12px;
+ font-weight: 900;
+ padding: 2px 8px;
+ border-radius: 5px;
+ width: fit-content;
+}
+
+.goal {
+ color: var(--blue900);
+ background-color: var(--blue100);
+ font-size: 12px;
+ font-weight: 900;
+ padding: 2px 8px;
+ border-radius: 5px;
+ width: fit-content;
+}
diff --git a/src/app/(main)/reports/goals/GoalsParameters.tsx b/src/app/(main)/reports/goals/GoalsParameters.tsx
new file mode 100644
index 0000000000..8d85dc2043
--- /dev/null
+++ b/src/app/(main)/reports/goals/GoalsParameters.tsx
@@ -0,0 +1,141 @@
+import { useMessages } from 'components/hooks';
+import Icons from 'components/icons';
+import { formatNumber } from 'lib/format';
+import { useContext } from 'react';
+import {
+ Button,
+ Flexbox,
+ Form,
+ FormButtons,
+ FormRow,
+ Icon,
+ Popup,
+ PopupTrigger,
+ SubmitButton,
+} from 'react-basics';
+import BaseParameters from '../[reportId]/BaseParameters';
+import ParameterList from '../[reportId]/ParameterList';
+import PopupForm from '../[reportId]/PopupForm';
+import { ReportContext } from '../[reportId]/Report';
+import GoalsAddForm from './GoalsAddForm';
+import styles from './GoalsParameters.module.css';
+
+export function GoalsParameters() {
+ const { report, runReport, updateReport, isRunning } = useContext(ReportContext);
+ const { formatMessage, labels } = useMessages();
+
+ const { id, parameters } = report || {};
+ const { websiteId, dateRange, goals } = parameters || {};
+ const queryDisabled = !websiteId || !dateRange || goals?.length < 1;
+
+ const handleSubmit = (data: any, e: any) => {
+ e.stopPropagation();
+ e.preventDefault();
+
+ if (!queryDisabled) {
+ runReport(data);
+ }
+ };
+
+ const handleAddGoals = (goal: { type: string; value: string }) => {
+ updateReport({ parameters: { goals: parameters.goals.concat(goal) } });
+ };
+
+ const handleUpdateGoals = (
+ close: () => void,
+ index: number,
+ goal: { type: string; value: string },
+ ) => {
+ const goals = [...parameters.goals];
+ goals[index] = goal;
+ updateReport({ parameters: { goals } });
+ close();
+ };
+
+ const handleRemoveGoals = (index: number) => {
+ const goals = [...parameters.goals];
+ delete goals[index];
+ updateReport({ parameters: { goals: goals.filter(n => n) } });
+ };
+
+ const AddGoalsButton = () => {
+ return (
+
+
+ );
+ })}
+
+
+
+ {formatMessage(getLabel(type))}
+ {`${value}${
+ type === 'event-data' ? `:(${operator}):${property}` : ''
+ }`}
+
+
+
+ 20 && percent <= 40,
+ [styles.level3]: percent > 40 && percent <= 60,
+ [styles.level4]: percent > 60 && percent <= 80,
+ [styles.level5]: percent > 80,
+ }),
+ )}
+ style={{ width: `${percent}%` }}
+ >
+
+
+
+ {formatLongNumber(result)}
+ / {formatLongNumber(goal)}
+
+ {((result / goal) * 100).toFixed(2)}%
+
+
+ );
+}
diff --git a/src/app/(main)/reports/journey/page.tsx b/src/app/(main)/reports/journey/page.tsx
new file mode 100644
index 0000000000..447747cc3a
--- /dev/null
+++ b/src/app/(main)/reports/journey/page.tsx
@@ -0,0 +1,10 @@
+import { Metadata } from 'next';
+import JourneyReportPage from './JourneyReportPage';
+
+export default function () {
+ return
+ {columns.map((column, columnIndex) => {
+ const dropOffPercent = `${~~column.dropOff}%`;
+ return (
+
+
+
+ );
+ })}
+
+
+ {columnIndex + 1}
+
+
+
+ {formatLongNumber(column.visitorCount)} {formatMessage(labels.visitors)}
+
+ {columnIndex > 0 && {dropOffPercent}
}
+
+ {column.nodes.map(
+ ({
+ name,
+ totalCount,
+ selected,
+ active,
+ paths,
+ activeCount,
+ selectedCount,
+ lines,
+ }) => {
+ const nodeCount = selected
+ ? active
+ ? activeCount
+ : selectedCount
+ : totalCount;
+
+ return (
+
+ selected && setActiveNode({ name, columnIndex, paths })}
+ onMouseLeave={() => selected && setActiveNode(null)}
+ >
+
+ );
+ },
+ )}
+ handleClick(name, columnIndex, paths)}
+ >
+
+
+ {columnIndex < columns.length &&
+ lines.map(([fromIndex, nodeIndex], i) => {
+ const height =
+ (Math.abs(nodeIndex - fromIndex) + 1) * (NODE_HEIGHT + NODE_GAP) -
+ NODE_GAP;
+ const midHeight =
+ (Math.abs(nodeIndex - fromIndex) - 1) * (NODE_HEIGHT + NODE_GAP) +
+ NODE_GAP +
+ LINE_WIDTH;
+ const nodeName = columns[columnIndex - 1]?.nodes[fromIndex].name;
+
+ return (
+
+ {name}
+
+ {formatLongNumber(nodeCount)}
+
+
+ path.items[columnIndex] === name &&
+ path.items[columnIndex - 1] === nodeName,
+ ),
+ [styles.up]: fromIndex < nodeIndex,
+ [styles.down]: fromIndex > nodeIndex,
+ [styles.flat]: fromIndex === nodeIndex,
+ })}
+ style={{ height }}
+ >
+
+
+
+
+ );
+ })}
+