diff --git a/src/app/(main)/reports/funnel/FunnelChart.module.css b/src/app/(main)/reports/funnel/FunnelChart.module.css
index 9e1690b3..60254922 100644
--- a/src/app/(main)/reports/funnel/FunnelChart.module.css
+++ b/src/app/(main)/reports/funnel/FunnelChart.module.css
@@ -1,3 +1,84 @@
-.loading {
- height: 300px;
+.chart {
+ display: grid;
+}
+
+.num {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 100%;
+ width: 50px;
+ height: 50px;
+ font-size: 16px;
+ font-weight: 700;
+ color: var(--base800);
+ background: var(--base100);
+ z-index: 1;
+}
+
+.step {
+ display: grid;
+ grid-template-columns: max-content 1fr;
+ column-gap: 30px;
+ position: relative;
+ padding-bottom: 60px;
+}
+
+.step::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 25px;
+ bottom: 0;
+ width: 2px;
+ background-color: var(--base100);
+}
+
+.card {
+ display: grid;
+ gap: 20px;
+}
+
+.header {
+ display: flex;
+ align-items: center;
+ font-weight: 700;
+ gap: 20px;
+}
+
+.bar {
+ display: flex;
+ align-items: center;
+ justify-content: end;
+ background: var(--base900);
+ height: 50px;
+ border-radius: 5px;
+ overflow: hidden;
+ position: relative;
+}
+
+.label {
+ color: var(--base700);
+}
+
+.value {
+ color: var(--base50);
+ margin-right: 20px;
+}
+
+.track {
+ background-color: var(--base100);
+ border-radius: 5px;
+}
+
+.info {
+ display: flex;
+ justify-content: space-between;
+ text-transform: lowercase;
+}
+
+.item {
+ padding: 6px 10px;
+ border-radius: 4px;
+ border: 1px solid var(--base300);
}
diff --git a/src/app/(main)/reports/funnel/FunnelChart.tsx b/src/app/(main)/reports/funnel/FunnelChart.tsx
index 38373517..6207a177 100644
--- a/src/app/(main)/reports/funnel/FunnelChart.tsx
+++ b/src/app/(main)/reports/funnel/FunnelChart.tsx
@@ -1,83 +1,57 @@
-import { JSX, useCallback, useContext, useMemo } from 'react';
-import { Loading, StatusLight } from 'react-basics';
-import { useMessages, useTheme } from 'components/hooks';
-import BarChart from 'components/metrics/BarChart';
-import { formatLongNumber } from 'lib/format';
+import { useContext } from 'react';
+import classNames from 'classnames';
+import { useMessages } from 'components/hooks';
import { ReportContext } from '../[reportId]/Report';
import styles from './FunnelChart.module.css';
+import { formatLongNumber } from 'lib/format';
export interface FunnelChartProps {
className?: string;
isLoading?: boolean;
}
-export function FunnelChart({ className, isLoading }: FunnelChartProps) {
+export function FunnelChart({ className }: FunnelChartProps) {
const { report } = useContext(ReportContext);
const { formatMessage, labels } = useMessages();
- const { colors } = useTheme();
- const { parameters, data } = report || {};
-
- const renderXLabel = useCallback(
- (label: string, index: number) => {
- return parameters.urls[index];
- },
- [parameters],
- );
-
- const renderTooltipPopup = useCallback(
- (
- setTooltipPopup: (arg0: JSX.Element) => void,
- model: { tooltip: { opacity: any; labelColors: any; dataPoints: any } },
- ) => {
- const { opacity, labelColors, dataPoints } = model.tooltip;
-
- if (!dataPoints?.length || !opacity) {
- setTooltipPopup(null);
- return;
- }
-
- setTooltipPopup(
- <>
-
- {formatLongNumber(dataPoints[0].raw.y)} {formatMessage(labels.visitors)}
-
-
-
- {formatLongNumber(dataPoints[0].raw.z)}% {formatMessage(labels.dropoff)}
-
-
- >,
- );
- },
- [],
- );
-
- const datasets = useMemo(() => {
- return [
- {
- label: formatMessage(labels.uniqueVisitors),
- data: data,
- borderWidth: 1,
- ...colors.chart.visitors,
- },
- ];
- }, [data, colors, formatMessage, labels]);
-
- if (isLoading) {
- return ;
- }
+ const { data } = report || {};
return (
-
+
+ {data?.map(({ url, visitors, dropped, dropoff, remaining }, index: number) => {
+ return (
+
+
{index + 1}
+
+
+ {formatMessage(labels.viewedPage)}:
+ {url}
+
+
+
+
+ {remaining > 0.1 && `${(remaining * 100).toFixed(2)}%`}
+
+
+
+
+
+ {formatLongNumber(visitors)}
+ {formatMessage(labels.visitors)}
+ ({(remaining * 100).toFixed(2)}%)
+
+ {dropoff > 0 && (
+
+ {formatLongNumber(dropped)} {formatMessage(labels.visitorsDroppedOff)} (
+ {(dropoff * 100).toFixed(2)}%)
+
+ )}
+
+
+
+ );
+ })}
+
);
}
diff --git a/src/app/(main)/reports/funnel/FunnelReport.tsx b/src/app/(main)/reports/funnel/FunnelReport.tsx
index 1fd1a80f..7b9a6677 100644
--- a/src/app/(main)/reports/funnel/FunnelReport.tsx
+++ b/src/app/(main)/reports/funnel/FunnelReport.tsx
@@ -1,5 +1,4 @@
import FunnelChart from './FunnelChart';
-import FunnelTable from './FunnelTable';
import FunnelParameters from './FunnelParameters';
import Report from '../[reportId]/Report';
import ReportHeader from '../[reportId]/ReportHeader';
@@ -22,7 +21,6 @@ export default function FunnelReport({ reportId }: { reportId?: string }) {
-
);
diff --git a/src/app/(main)/reports/funnel/FunnelTable.tsx b/src/app/(main)/reports/funnel/FunnelTable.tsx
deleted file mode 100644
index 1fe1fdd9..00000000
--- a/src/app/(main)/reports/funnel/FunnelTable.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { useContext } from 'react';
-import ListTable from 'components/metrics/ListTable';
-import { useMessages } from 'components/hooks';
-import { ReportContext } from '../[reportId]/Report';
-
-export function FunnelTable() {
- const { report } = useContext(ReportContext);
- const { formatMessage, labels } = useMessages();
- return (
-
- );
-}
-
-export default FunnelTable;
diff --git a/src/components/messages.ts b/src/components/messages.ts
index 2710e99d..b38e5bcd 100644
--- a/src/components/messages.ts
+++ b/src/components/messages.ts
@@ -209,6 +209,18 @@ export const labels = defineMessages({
select: { id: 'label.select', defaultMessage: 'Select' },
myAccount: { id: 'label.my-account', defaultMessage: 'My account' },
transfer: { id: 'label.transfer', defaultMessage: 'Transfer' },
+ viewedPage: {
+ id: 'message.viewed-page',
+ defaultMessage: 'Viewed page',
+ },
+ triggeredEvent: {
+ id: 'message.triggered-event',
+ defaultMessage: 'Triggered event',
+ },
+ visitorsDroppedOff: {
+ id: 'message.visitors-dropped-off',
+ defaultMessage: 'Visitors droppped off',
+ },
});
export const messages = defineMessages({
diff --git a/src/queries/analytics/reports/getFunnel.ts b/src/queries/analytics/reports/getFunnel.ts
index 4387cf09..0a5dfd96 100644
--- a/src/queries/analytics/reports/getFunnel.ts
+++ b/src/queries/analytics/reports/getFunnel.ts
@@ -2,6 +2,25 @@ import clickhouse from 'lib/clickhouse';
import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db';
import prisma from 'lib/prisma';
+const formatResults = (urls: string[]) => (results: unknown) => {
+ return urls.map((url: string, i: number) => {
+ const visitors = Number(results[i]?.count) || 0;
+ const previous = Number(results[i - 1]?.count) || 0;
+ const dropped = previous > 0 ? previous - visitors : 0;
+ const dropoff = 1 - visitors / previous;
+ const remaining = visitors / Number(results[0].count);
+
+ return {
+ url,
+ visitors,
+ previous,
+ dropped,
+ dropoff,
+ remaining,
+ };
+ });
+};
+
export async function getFunnel(
...args: [
websiteId: string,
@@ -29,9 +48,9 @@ async function relationalQuery(
},
): Promise<
{
- x: string;
- y: number;
- z: number;
+ url: string;
+ visitors: number;
+ dropoff: number;
}[]
> {
const { windowMinutes, startDate, endDate, urls } = criteria;
@@ -98,13 +117,7 @@ async function relationalQuery(
endDate,
...urls,
},
- ).then(results => {
- return urls.map((a, i) => ({
- x: a,
- y: results[i]?.count || 0,
- z: (1 - Number(results[i]?.count) / Number(results[i - 1]?.count)) * 100 || 0, // drop off
- }));
- });
+ ).then(formatResults(urls));
}
async function clickhouseQuery(
@@ -117,9 +130,9 @@ async function clickhouseQuery(
},
): Promise<
{
- x: string;
- y: number;
- z: number;
+ url: string;
+ visitors: number;
+ dropoff: number;
}[]
> {
const { windowMinutes, startDate, endDate, urls } = criteria;
@@ -198,11 +211,5 @@ async function clickhouseQuery(
endDate,
...urlParams,
},
- ).then(results => {
- return urls.map((a, i) => ({
- x: a,
- y: Number(results[i]?.count) || 0,
- z: (1 - Number(results[i]?.count) / Number(results[i - 1]?.count)) * 100 || 0, // drop off
- }));
- });
+ ).then(formatResults(urls));
}
diff --git a/src/store/app.ts b/src/store/app.ts
index 53fdbd92..8fa1a0d5 100644
--- a/src/store/app.ts
+++ b/src/store/app.ts
@@ -20,27 +20,27 @@ const initialState = {
const store = create(() => ({ ...initialState }));
-export function setTheme(theme) {
+export function setTheme(theme: string) {
store.setState({ theme });
}
-export function setLocale(locale) {
+export function setLocale(locale: string) {
store.setState({ locale });
}
-export function setShareToken(shareToken) {
+export function setShareToken(shareToken: string) {
store.setState({ shareToken });
}
-export function setUser(user) {
+export function setUser(user: object) {
store.setState({ user });
}
-export function setConfig(config) {
+export function setConfig(config: object) {
store.setState({ config });
}
-export function setDateRange(dateRange) {
+export function setDateRange(dateRange: object) {
store.setState({ dateRange });
}