diff --git a/components/common/HoverTooltip.js b/components/common/HoverTooltip.js
index 2a98ab84..59fd6277 100644
--- a/components/common/HoverTooltip.js
+++ b/components/common/HoverTooltip.js
@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react';
import { Tooltip } from 'react-basics';
import styles from './HoverTooltip.module.css';
-export function HoverTooltip({ tooltip }) {
+export function HoverTooltip({ children }) {
const [position, setPosition] = useState({ x: -1000, y: -1000 });
useEffect(() => {
@@ -18,8 +18,8 @@ export function HoverTooltip({ tooltip }) {
}, []);
return (
-
-
+
+
);
}
diff --git a/components/common/WorldMap.js b/components/common/WorldMap.js
index 55a13f0b..31c6686b 100644
--- a/components/common/WorldMap.js
+++ b/components/common/WorldMap.js
@@ -15,7 +15,7 @@ import styles from './WorldMap.module.css';
export function WorldMap({ data, className }) {
const { basePath } = useRouter();
const [tooltip, setTooltip] = useState();
- const [theme] = useTheme();
+ const { theme } = useTheme();
const colors = useMemo(
() => ({
baseColor: THEME_COLORS[theme].primary,
@@ -86,7 +86,7 @@ export function WorldMap({ data, className }) {
- {tooltip &&
}
+ {tooltip &&
{tooltip}}
);
}
diff --git a/components/input/LanguageButton.js b/components/input/LanguageButton.js
index e2a51223..049b49f3 100644
--- a/components/input/LanguageButton.js
+++ b/components/input/LanguageButton.js
@@ -9,9 +9,9 @@ export function LanguageButton() {
const { locale, saveLocale, dir } = useLocale();
const items = Object.keys(languages).map(key => ({ ...languages[key], value: key }));
- function handleSelect(value) {
- //saveLocale(value);
- console.log('WTFFFF');
+ function handleSelect(value, close) {
+ saveLocale(value);
+ close();
}
return (
@@ -22,24 +22,28 @@ export function LanguageButton() {
-
- {items.map(({ value, label }) => {
- return (
-
- {label}
- {value === locale && (
-
-
-
- )}
-
- );
- })}
-
+ {close => {
+ return (
+
+ {items.map(({ value, label }) => {
+ return (
+
+ {label}
+ {value === locale && (
+
+
+
+ )}
+
+ );
+ })}
+
+ );
+ }}
);
diff --git a/components/input/ThemeButton.js b/components/input/ThemeButton.js
index b945ab7d..8ab0cdcd 100644
--- a/components/input/ThemeButton.js
+++ b/components/input/ThemeButton.js
@@ -5,7 +5,7 @@ import Icons from 'components/icons';
import styles from './ThemeButton.module.css';
export function ThemeButton() {
- const [theme, setTheme] = useTheme();
+ const { theme, saveTheme } = useTheme();
const transitions = useTransition(theme, {
initial: { opacity: 1 },
@@ -21,7 +21,7 @@ export function ThemeButton() {
});
function handleClick() {
- setTheme(theme === 'light' ? 'dark' : 'light');
+ saveTheme(theme === 'light' ? 'dark' : 'light');
}
return (
diff --git a/components/messages.js b/components/messages.js
index fb0dee3c..0de3a44c 100644
--- a/components/messages.js
+++ b/components/messages.js
@@ -123,7 +123,11 @@ export const labels = defineMessages({
reports: { id: 'label.reports', defaultMessage: 'Reports' },
eventData: { id: 'label.event-data', defaultMessage: 'Event data' },
funnel: { id: 'label.funnel', defaultMessage: 'Funnel' },
+ url: { id: 'label.url', defaultMessage: 'URL' },
urls: { id: 'label.urls', defaultMessage: 'URLs' },
+ add: { id: 'label.add', defaultMessage: 'Add' },
+ window: { id: 'label.window', defaultMessage: 'Window' },
+ addUrl: { id: 'label.add-url', defaultMessage: 'Add URL' },
});
export const messages = defineMessages({
diff --git a/components/metrics/BarChart.js b/components/metrics/BarChart.js
index 8e1784df..2aba3ea5 100644
--- a/components/metrics/BarChart.js
+++ b/components/metrics/BarChart.js
@@ -1,14 +1,13 @@
-import { useState, useRef, useEffect, useMemo, useCallback } from 'react';
-import { StatusLight, Loading } from 'react-basics';
+import { useState, useRef, useEffect, useCallback } from 'react';
+import { Loading } from 'react-basics';
import classNames from 'classnames';
import Chart from 'chart.js/auto';
import HoverTooltip from 'components/common/HoverTooltip';
import Legend from 'components/metrics/Legend';
-import { formatLongNumber } from 'lib/format';
-import { dateFormat } from 'lib/date';
import useLocale from 'hooks/useLocale';
import useTheme from 'hooks/useTheme';
-import { DEFAULT_ANIMATION_DURATION, THEME_COLORS } from 'lib/constants';
+import { DEFAULT_ANIMATION_DURATION } from 'lib/constants';
+import { renderNumberLabels } from 'lib/charts';
import styles from './BarChart.module.css';
export function BarChart({
@@ -17,86 +16,20 @@ export function BarChart({
animationDuration = DEFAULT_ANIMATION_DURATION,
stacked = false,
loading = false,
- onCreate = () => {},
- onUpdate = () => {},
+ renderXLabel,
+ renderYLabel,
+ XAxisType = 'time',
+ YAxisType = 'linear',
+ renderTooltip,
+ onCreate,
+ onUpdate,
className,
}) {
const canvas = useRef();
const chart = useRef(null);
const [tooltip, setTooltip] = useState(null);
const { locale } = useLocale();
- const [theme] = useTheme();
-
- const colors = useMemo(
- () => ({
- text: THEME_COLORS[theme].gray700,
- line: THEME_COLORS[theme].gray200,
- }),
- [theme],
- );
-
- const renderYLabel = label => {
- return +label > 1000 ? formatLongNumber(label) : label;
- };
-
- const renderXLabel = useCallback(
- (label, index, values) => {
- const d = new Date(values[index].value);
-
- switch (unit) {
- case 'minute':
- return dateFormat(d, 'h:mm', locale);
- case 'hour':
- return dateFormat(d, 'p', locale);
- case 'day':
- return dateFormat(d, 'MMM d', locale);
- case 'month':
- return dateFormat(d, 'MMM', locale);
- case 'year':
- return dateFormat(d, 'YYY', locale);
- default:
- return label;
- }
- },
- [locale, unit],
- );
-
- const renderTooltip = useCallback(
- model => {
- const { opacity, labelColors, dataPoints } = model.tooltip;
-
- if (!dataPoints?.length || !opacity) {
- setTooltip(null);
- return;
- }
-
- const formats = {
- millisecond: 'T',
- second: 'pp',
- minute: 'p',
- hour: 'h:mm aaa - PP',
- day: 'PPPP',
- week: 'PPPP',
- month: 'LLLL yyyy',
- quarter: 'qqq',
- year: 'yyyy',
- };
-
- setTooltip(
-
-
{dateFormat(new Date(dataPoints[0].raw.x), formats[unit], locale)}
-
-
-
- {formatLongNumber(dataPoints[0].raw.y)} {dataPoints[0].dataset.label}
-
-
-
-
,
- );
- },
- [unit],
- );
+ const { theme, colors } = useTheme();
const getOptions = useCallback(() => {
return {
@@ -117,12 +50,12 @@ export function BarChart({
},
tooltip: {
enabled: false,
- external: renderTooltip,
+ external: renderTooltip ? renderTooltip.bind(null, setTooltip) : undefined,
},
},
scales: {
x: {
- type: 'time',
+ type: XAxisType,
stacked: true,
time: {
unit,
@@ -131,34 +64,44 @@ export function BarChart({
display: false,
},
border: {
- color: colors.line,
+ color: colors.chart.line,
},
ticks: {
- color: colors.text,
+ color: colors.chart.text,
autoSkip: false,
maxRotation: 0,
callback: renderXLabel,
},
},
y: {
- type: 'linear',
+ type: YAxisType,
min: 0,
beginAtZero: true,
stacked,
grid: {
- color: colors.line,
+ color: colors.chart.line,
},
border: {
- color: colors.line,
+ color: colors.chart.line,
},
ticks: {
color: colors.text,
- callback: renderYLabel,
+ callback: renderYLabel || renderNumberLabels,
},
},
},
};
- }, [animationDuration, renderTooltip, renderXLabel, stacked, colors, unit, locale]);
+ }, [
+ animationDuration,
+ renderTooltip,
+ renderXLabel,
+ XAxisType,
+ YAxisType,
+ stacked,
+ colors,
+ unit,
+ locale,
+ ]);
const createChart = () => {
Chart.defaults.font.family = 'Inter';
@@ -173,7 +116,7 @@ export function BarChart({
options,
});
- onCreate(chart.current);
+ onCreate?.(chart.current);
};
const updateChart = () => {
@@ -186,7 +129,7 @@ export function BarChart({
chart.current.options = getOptions();
- onUpdate(chart.current);
+ onUpdate?.(chart.current);
chart.current.update();
};
@@ -208,7 +151,11 @@ export function BarChart({
- {tooltip && }
+ {tooltip && (
+
+ {tooltip}
+
+ )}
>
);
}
diff --git a/components/metrics/DataTable.js b/components/metrics/DataTable.js
index 086f98ae..a2c1a568 100644
--- a/components/metrics/DataTable.js
+++ b/components/metrics/DataTable.js
@@ -5,8 +5,8 @@ import { useSpring, animated, config } from 'react-spring';
import classNames from 'classnames';
import NoData from 'components/common/NoData';
import { formatNumber, formatLongNumber } from 'lib/format';
+import useMessages from 'hooks/useMessages';
import styles from './DataTable.module.css';
-import useMessages from '../../hooks/useMessages';
export function DataTable({
data = [],
diff --git a/components/metrics/DataTable.module.css b/components/metrics/DataTable.module.css
index c5b2bd7c..04e12e9b 100644
--- a/components/metrics/DataTable.module.css
+++ b/components/metrics/DataTable.module.css
@@ -1,9 +1,9 @@
.table {
position: relative;
- height: 100%;
display: grid;
grid-template-rows: fit-content(100%) auto;
overflow: hidden;
+ flex: 1;
}
.body {
diff --git a/components/metrics/EventsChart.js b/components/metrics/EventsChart.js
index eb397cc9..334ee591 100644
--- a/components/metrics/EventsChart.js
+++ b/components/metrics/EventsChart.js
@@ -2,16 +2,15 @@ import { useMemo } from 'react';
import { Loading } from 'react-basics';
import { colord } from 'colord';
import BarChart from './BarChart';
-import { getDateArray, getDateLength } from 'lib/date';
-import useApi from 'hooks/useApi';
-import useDateRange from 'hooks/useDateRange';
-import useTimezone from 'hooks/useTimezone';
-import usePageQuery from 'hooks/usePageQuery';
+import { getDateArray } from 'lib/date';
+import { useApi, useLocale, useDateRange, useTimezone, usePageQuery } from 'hooks';
import { EVENT_COLORS } from 'lib/constants';
+import { renderDateLabels, renderStatusTooltip } from 'lib/charts';
export function EventsChart({ websiteId, className, token }) {
const { get, useQuery } = useApi();
const [{ startDate, endDate, unit, modified }] = useDateRange(websiteId);
+ const { locale } = useLocale();
const [timezone] = useTimezone();
const {
query: { url, eventName },
@@ -70,9 +69,10 @@ export function EventsChart({ websiteId, className, token }) {
datasets={datasets}
unit={unit}
height={300}
- records={getDateLength(startDate, endDate, unit)}
loading={isLoading}
stacked
+ renderXLabel={renderDateLabels(unit, locale)}
+ renderTooltip={renderStatusTooltip(unit, locale)}
/>
);
}
diff --git a/components/metrics/MetricsBar.module.css b/components/metrics/MetricsBar.module.css
index 0e305c70..eaf81c48 100644
--- a/components/metrics/MetricsBar.module.css
+++ b/components/metrics/MetricsBar.module.css
@@ -1,7 +1,7 @@
.bar {
display: flex;
cursor: pointer;
- min-height: 80px;
+ min-height: 110px;
gap: 20px;
flex-wrap: wrap;
}
diff --git a/components/metrics/PageviewsChart.js b/components/metrics/PageviewsChart.js
index 6ea16226..bf56a0bd 100644
--- a/components/metrics/PageviewsChart.js
+++ b/components/metrics/PageviewsChart.js
@@ -1,34 +1,13 @@
import { useMemo } from 'react';
-import { colord } from 'colord';
import BarChart from './BarChart';
-import { THEME_COLORS } from 'lib/constants';
-import useTheme from 'hooks/useTheme';
-import useMessages from 'hooks/useMessages';
-import useLocale from 'hooks/useLocale';
+import { useLocale, useTheme, useMessages } from 'hooks';
+import { renderDateLabels, renderStatusTooltip } from 'lib/charts';
-export function PageviewsChart({ websiteId, data, unit, records, className, loading, ...props }) {
+export function PageviewsChart({ websiteId, data, unit, className, loading, ...props }) {
const { formatMessage, labels } = useMessages();
- const [theme] = useTheme();
+ const { colors } = useTheme();
const { locale } = useLocale();
- const colors = useMemo(() => {
- const primaryColor = colord(THEME_COLORS[theme].primary);
- return {
- views: {
- hoverBackgroundColor: primaryColor.alpha(0.7).toRgbString(),
- backgroundColor: primaryColor.alpha(0.4).toRgbString(),
- borderColor: primaryColor.alpha(0.7).toRgbString(),
- hoverBorderColor: primaryColor.toRgbString(),
- },
- visitors: {
- hoverBackgroundColor: primaryColor.alpha(0.9).toRgbString(),
- backgroundColor: primaryColor.alpha(0.6).toRgbString(),
- borderColor: primaryColor.alpha(0.9).toRgbString(),
- hoverBorderColor: primaryColor.toRgbString(),
- },
- };
- }, [theme]);
-
const datasets = useMemo(() => {
if (!data) return [];
@@ -37,13 +16,13 @@ export function PageviewsChart({ websiteId, data, unit, records, className, load
label: formatMessage(labels.uniqueVisitors),
data: data.sessions,
borderWidth: 1,
- ...colors.visitors,
+ ...colors.chart.visitors,
},
{
label: formatMessage(labels.pageViews),
data: data.pageviews,
borderWidth: 1,
- ...colors.views,
+ ...colors.chart.views,
},
];
}, [data, locale, colors]);
@@ -55,8 +34,9 @@ export function PageviewsChart({ websiteId, data, unit, records, className, load
className={className}
datasets={datasets}
unit={unit}
- records={records}
loading={loading}
+ renderXLabel={renderDateLabels(unit, locale)}
+ renderTooltip={renderStatusTooltip(unit, locale)}
/>
);
}
diff --git a/components/metrics/WebsiteHeader.module.css b/components/metrics/WebsiteHeader.module.css
index e5ebcca7..68fd22f8 100644
--- a/components/metrics/WebsiteHeader.module.css
+++ b/components/metrics/WebsiteHeader.module.css
@@ -1,3 +1,9 @@
+.header {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+}
+
.title {
display: flex;
flex-direction: row;
diff --git a/components/pages/reports/ReportHeader.js b/components/pages/reports/ReportHeader.js
index 90d58ca8..7da47245 100644
--- a/components/pages/reports/ReportHeader.js
+++ b/components/pages/reports/ReportHeader.js
@@ -16,8 +16,6 @@ export function ReportHeader({ report, icon }) {
const { id, websiteId, name, parameters } = report || {};
const { value, startDate, endDate } = parameters?.dateRange || {};
- console.log('REPORT HEADER', report);
-
const handleSelect = websiteId => {
updateReport(id, { websiteId });
};
diff --git a/components/pages/reports/funnel/FunnelChart.js b/components/pages/reports/funnel/FunnelChart.js
index 307c78ee..aecd48a7 100644
--- a/components/pages/reports/funnel/FunnelChart.js
+++ b/components/pages/reports/funnel/FunnelChart.js
@@ -1,184 +1,60 @@
-import Chart from 'chart.js/auto';
-import classNames from 'classnames';
-import { colord } from 'colord';
-import HoverTooltip from 'components/common/HoverTooltip';
-import Legend from 'components/metrics/Legend';
-import useLocale from 'hooks/useLocale';
+import { useCallback, useMemo } from 'react';
+import { Loading } from 'react-basics';
import useMessages from 'hooks/useMessages';
import useTheme from 'hooks/useTheme';
-import { DEFAULT_ANIMATION_DURATION, THEME_COLORS } from 'lib/constants';
+import BarChart from 'components/metrics/BarChart';
import { formatLongNumber } from 'lib/format';
-import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
-import { Loading, StatusLight } from 'react-basics';
import styles from './FunnelChart.module.css';
-export function FunnelChart({
- data,
- animationDuration = DEFAULT_ANIMATION_DURATION,
- stacked = false,
- loading = false,
- onCreate = () => {},
- onUpdate = () => {},
- className,
-}) {
+export function FunnelChart({ report, data, loading, className }) {
const { formatMessage, labels } = useMessages();
- const canvas = useRef();
- const chart = useRef(null);
- const [tooltip, setTooltip] = useState(null);
- const { locale } = useLocale();
- const [theme] = useTheme();
+ const { colors } = useTheme();
- const datasets = useMemo(() => {
- const primaryColor = colord(THEME_COLORS[theme].primary);
- return [
- {
- label: formatMessage(labels.uniqueVisitors),
- data: data,
- borderWidth: 1,
- hoverBackgroundColor: primaryColor.alpha(0.9).toRgbString(),
- backgroundColor: primaryColor.alpha(0.6).toRgbString(),
- borderColor: primaryColor.alpha(0.9).toRgbString(),
- hoverBorderColor: primaryColor.toRgbString(),
- },
- ];
- }, [data]);
+ const { parameters } = report || {};
- const colors = useMemo(
- () => ({
- text: THEME_COLORS[theme].gray700,
- line: THEME_COLORS[theme].gray200,
- }),
- [theme],
+ const renderXLabel = useCallback(
+ (label, index) => {
+ return parameters.urls[index];
+ },
+ [parameters],
);
- const renderYLabel = label => {
- return +label > 1000 ? formatLongNumber(label) : label;
- };
-
- const renderTooltip = useCallback(model => {
- const { opacity, labelColors, dataPoints } = model.tooltip;
+ const renderTooltip = useCallback((setTooltip, model) => {
+ const { opacity, dataPoints } = model.tooltip;
if (!dataPoints?.length || !opacity) {
setTooltip(null);
return;
}
- setTooltip(
-
-
-
-
-
{dataPoints[0].raw.x}
-
{formatLongNumber(dataPoints[0].raw.y)}
-
-
-
-
,
- );
+ setTooltip(`${formatLongNumber(dataPoints[0].raw.y)} ${formatMessage(labels.visitors)}`);
}, []);
- const getOptions = useCallback(() => {
- return {
- responsive: true,
- maintainAspectRatio: false,
- animation: {
- duration: animationDuration,
- resize: {
- duration: 0,
- },
- active: {
- duration: 0,
- },
+ const datasets = useMemo(() => {
+ return [
+ {
+ label: formatMessage(labels.uniqueVisitors),
+ data: data,
+ borderWidth: 1,
+ ...colors.chart.visitors,
},
- plugins: {
- legend: {
- display: false,
- },
- tooltip: {
- enabled: false,
- external: renderTooltip,
- },
- },
- scales: {
- x: {
- grid: {
- display: false,
- },
- border: {
- color: colors.line,
- },
- ticks: {
- color: colors.text,
- autoSkip: false,
- maxRotation: 0,
- },
- },
- y: {
- type: 'linear',
- min: 0,
- beginAtZero: true,
- stacked,
- grid: {
- color: colors.line,
- },
- border: {
- color: colors.line,
- },
- ticks: {
- color: colors.text,
- callback: renderYLabel,
- },
- },
- },
- };
- }, [animationDuration, renderTooltip, stacked, colors, locale]);
+ ];
+ }, [data]);
- const createChart = () => {
- Chart.defaults.font.family = 'Inter';
-
- const options = getOptions();
-
- chart.current = new Chart(canvas.current, {
- type: 'bar',
- data: { datasets },
- options,
- });
-
- onCreate(chart.current);
- };
-
- const updateChart = () => {
- setTooltip(null);
-
- chart.current.data.datasets[0].data = datasets[0].data;
- chart.current.data.datasets[0].label = datasets[0].label;
-
- chart.current.options = getOptions();
-
- onUpdate(chart.current);
-
- chart.current.update();
- };
-
- useEffect(() => {
- if (datasets) {
- if (!chart.current) {
- createChart();
- } else {
- updateChart();
- }
- }
- }, [datasets, theme, animationDuration, locale]);
+ if (loading) {
+ return ;
+ }
return (
- <>
-
- {loading && }
-
-
-
- {tooltip && }
- >
+
);
}
diff --git a/components/pages/reports/funnel/FunnelChart.module.css b/components/pages/reports/funnel/FunnelChart.module.css
index f071a29e..9e1690b3 100644
--- a/components/pages/reports/funnel/FunnelChart.module.css
+++ b/components/pages/reports/funnel/FunnelChart.module.css
@@ -1,23 +1,3 @@
-.chart {
- position: relative;
- height: 400px;
- overflow: hidden;
-}
-
-.tooltip {
- display: flex;
- flex-direction: column;
- gap: 10px;
-}
-
-.tooltip .value {
- display: flex;
- flex-direction: column;
- text-transform: lowercase;
-}
-
-@media only screen and (max-width: 992px) {
- .chart {
- /*height: 200px;*/
- }
+.loading {
+ height: 300px;
}
diff --git a/components/pages/reports/funnel/FunnelParameters.js b/components/pages/reports/funnel/FunnelParameters.js
index d387686e..3caf6a8e 100644
--- a/components/pages/reports/funnel/FunnelParameters.js
+++ b/components/pages/reports/funnel/FunnelParameters.js
@@ -1,36 +1,47 @@
import { useMessages } from 'hooks';
import {
+ Button,
Icon,
Form,
FormButtons,
FormInput,
FormRow,
- PopupTrigger,
- Popup,
+ ModalTrigger,
+ Modal,
SubmitButton,
+ Text,
TextField,
+ Tooltip,
} from 'react-basics';
import Icons from 'components/icons';
import { updateReport } from 'store/reports';
-import { useRef } from 'react';
+import { useRef, useState } from 'react';
import styles from './FunnelParameters.module.css';
export function FunnelParameters({ report }) {
const { formatMessage, labels } = useMessages();
const ref = useRef(null);
const { id, websiteId, parameters, isLoading } = report || {};
+ const queryDisabled = !websiteId || parameters?.urls?.length < 2;
const handleSubmit = values => {
- console.log({ values });
- updateReport(id, { parameters: values, isLoading: false });
+ updateReport(id, { parameters: values, isLoading: false, update: Date.now() });
};
- console.log('PARAMETERS', parameters);
+ const handleAdd = url => {
+ updateReport(id, { parameters: { ...parameters, urls: parameters.urls.concat(url) } });
+ };
+
+ const handleRemove = index => {
+ const urls = [...parameters.urls];
+ urls.splice(index, 1);
+ updateReport(id, { parameters: { ...parameters, urls } });
+ };
return (
<>