diff --git a/src/app/(main)/reports/create/ReportTemplates.tsx b/src/app/(main)/reports/create/ReportTemplates.tsx
index fdf5c5f5..0777cc1f 100644
--- a/src/app/(main)/reports/create/ReportTemplates.tsx
+++ b/src/app/(main)/reports/create/ReportTemplates.tsx
@@ -6,6 +6,7 @@ import Lightbulb from 'assets/lightbulb.svg';
import Magnet from 'assets/magnet.svg';
import Tag from 'assets/tag.svg';
import Target from 'assets/target.svg';
+import Path from 'assets/path.svg';
import styles from './ReportTemplates.module.css';
import { useMessages, useTeamUrl } from 'components/hooks';
@@ -44,6 +45,12 @@ export function ReportTemplates({ showHeader = true }: { showHeader?: boolean })
url: renderTeamUrl('/reports/goals'),
icon: ,
},
+ {
+ title: formatMessage(labels.journey),
+ description: formatMessage(labels.journeyDescription),
+ url: renderTeamUrl('/reports/journey'),
+ icon: ,
+ },
];
return (
diff --git a/src/app/(main)/reports/journey/JourneyView.module.css b/src/app/(main)/reports/journey/JourneyView.module.css
index fa7cc0b4..4d5a96a4 100644
--- a/src/app/(main)/reports/journey/JourneyView.module.css
+++ b/src/app/(main)/reports/journey/JourneyView.module.css
@@ -1,14 +1,74 @@
-.title {
- font-size: 24px;
- line-height: 36px;
- font-weight: 700;
+.container {
+ height: 900px;
+ position: relative;
}
-.row {
- display: grid;
- grid-template-columns: 50% 50%;
- gap: 20px;
- border-bottom: 1px solid var(--base300);
- padding-bottom: 30px;
- margin-bottom: 30px;
+.view {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ display: flex;
+ flex-direction: row;
+ flex-wrap: nowrap;
+ gap: 60px;
+ overflow: auto;
+}
+
+.header {
+ display: flex;
+ margin-bottom: 20px;
+}
+
+.num {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 100%;
+ width: 50px;
+ height: 50px;
+ font-size: 16px;
+ font-weight: 700;
+ color: var(--base100);
+ background: var(--base800);
+ z-index: 1;
+ margin: 0 auto;
+}
+
+.column {
+ min-width: 300px;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.item {
+ cursor: pointer;
+ padding: 10px 20px;
+ background: var(--base75);
+ border-radius: 5px;
+}
+
+.item:hover:not(.highlight) {
+ color: var(--base900);
+ background: var(--base100);
+}
+
+.highlight {
+ color: var(--base75);
+ background: var(--base900);
+ font-weight: 400;
+}
+
+.behind {
+ color: var(--base400);
+}
+
+.ahead {
+ color: var(--base400);
+}
+
+.current {
+ color: var(--base500);
}
diff --git a/src/app/(main)/reports/journey/JourneyView.tsx b/src/app/(main)/reports/journey/JourneyView.tsx
index 6905d74c..ff1941dc 100644
--- a/src/app/(main)/reports/journey/JourneyView.tsx
+++ b/src/app/(main)/reports/journey/JourneyView.tsx
@@ -1,13 +1,103 @@
-import { useContext } from 'react';
+import { useContext, useMemo, useState } from 'react';
import { ReportContext } from '../[reportId]/Report';
+import { firstBy } from 'thenby';
+import styles from './JourneyView.module.css';
+import classNames from 'classnames';
+import { useEscapeKey } from 'components/hooks';
export default function JourneyView() {
+ const [selected, setSelected] = useState(null);
const { report } = useContext(ReportContext);
const { data } = report || {};
+ useEscapeKey(() => setSelected(null));
+
+ const columns = useMemo(() => {
+ if (!data) {
+ return [];
+ }
+ return Array(data[0].items.length)
+ .fill(undefined)
+ .map((col = {}, index) => {
+ data.forEach(({ items, count }) => {
+ const item = items[index];
+
+ if (item) {
+ if (!col[item]) {
+ col[item] = {
+ item,
+ total: +count,
+ index,
+ paths: [
+ data.filter((d, i) => {
+ return d.items[index] === item && i !== index;
+ }),
+ ],
+ };
+ } else {
+ col[item].total += +count;
+ }
+ }
+ });
+
+ return Object.keys(col)
+ .map(key => col[key])
+ .sort(firstBy('total', -1));
+ });
+ }, [data]);
+
+ const handleClick = (item: string, index: number, paths: any[]) => {
+ if (item !== selected?.item || index !== selected?.index) {
+ setSelected({ item, index, paths });
+ } else {
+ setSelected(null);
+ }
+ };
if (!data) {
return null;
}
- return
{JSON.stringify(data)}
;
+ return (
+
+
+ {columns.map((column, index) => {
+ const current = index === selected?.index;
+ const behind = index <= selected?.index - 1;
+ const ahead = index > selected?.index;
+
+ return (
+
+
+ {column.map(({ item, total, paths }) => {
+ const highlight = selected?.paths.find(arr => {
+ return arr.find(a => a.items[index] === item);
+ });
+
+ return (
+
handleClick(item, index, paths)}
+ >
+ {item} ({total})
+
+ );
+ })}
+
+ );
+ })}
+
+
+ );
}
diff --git a/src/queries/analytics/reports/getJourney.ts b/src/queries/analytics/reports/getJourney.ts
index 088f7ee8..a02bf9bf 100644
--- a/src/queries/analytics/reports/getJourney.ts
+++ b/src/queries/analytics/reports/getJourney.ts
@@ -2,6 +2,15 @@ import clickhouse from 'lib/clickhouse';
import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db';
import prisma from 'lib/prisma';
+interface JourneyResult {
+ e1: string;
+ e2: string;
+ e3: string;
+ e4: string;
+ e5: string;
+ count: string;
+}
+
export async function getJourney(
...args: [
websiteId: string,
@@ -23,16 +32,7 @@ async function relationalQuery(
startDate: Date;
endDate: Date;
},
-): Promise<
- {
- e1: string;
- e2: string;
- e3: string;
- e4: string;
- e5: string;
- count: string;
- }[]
-> {
+): Promise {
const { startDate, endDate } = filters;
const { rawQuery } = prisma;
@@ -79,7 +79,7 @@ async function relationalQuery(
startDate,
endDate,
},
- );
+ ).then(parseResult);
}
async function clickhouseQuery(
@@ -88,16 +88,7 @@ async function clickhouseQuery(
startDate: Date;
endDate: Date;
},
-): Promise<
- {
- e1: string;
- e2: string;
- e3: string;
- e4: string;
- e5: string;
- count: string;
- }[]
-> {
+): Promise {
const { startDate, endDate } = filters;
const { rawQuery } = clickhouse;
@@ -144,5 +135,9 @@ async function clickhouseQuery(
startDate,
endDate,
},
- );
+ ).then(parseResult);
+}
+
+function parseResult(data: any) {
+ return data.map(({ e1, e2, e3, e4, e5, count }) => ({ items: [e1, e2, e3, e4, e5], count }));
}