mirror of
https://github.com/umami-software/umami.git
synced 2026-02-21 04:55:36 +01:00
Merge branch 'dev' into feat/um-202-event-data-new
This commit is contained in:
commit
da7f02bb73
38 changed files with 437 additions and 414 deletions
25
components/common/HoverTooltip.js
Normal file
25
components/common/HoverTooltip.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import { Tooltip } from 'react-basics';
|
||||
import styles from './HoverTooltip.module.css';
|
||||
|
||||
export default function HoverTooltip({ tooltip }) {
|
||||
const [position, setPosition] = useState({ x: -1000, y: -1000 });
|
||||
|
||||
useEffect(() => {
|
||||
const handler = e => {
|
||||
setPosition({ x: e.clientX, y: e.clientY });
|
||||
};
|
||||
|
||||
document.addEventListener('mousemove', handler);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('mousemove', handler);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={styles.tooltip} style={{ left: position.x, top: position.y }}>
|
||||
<Tooltip position="top" action="none" label={tooltip} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
}
|
||||
|
||||
.tooltip {
|
||||
color: var(--msgColor);
|
||||
position: fixed;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
import { useState, useMemo } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import ReactTooltip from 'react-tooltip';
|
||||
import { ComposableMap, Geographies, Geography, ZoomableGroup } from 'react-simple-maps';
|
||||
import classNames from 'classnames';
|
||||
import { colord } from 'colord';
|
||||
|
|
@ -9,6 +8,8 @@ import { ISO_COUNTRIES, THEME_COLORS, MAP_FILE } from 'lib/constants';
|
|||
import styles from './WorldMap.module.css';
|
||||
import useCountryNames from 'hooks/useCountryNames';
|
||||
import useLocale from 'hooks/useLocale';
|
||||
import HoverTooltip from './HoverTooltip';
|
||||
import { formatLongNumber } from '../../lib/format';
|
||||
|
||||
function WorldMap({ data, className }) {
|
||||
const { basePath } = useRouter();
|
||||
|
|
@ -46,7 +47,7 @@ function WorldMap({ data, className }) {
|
|||
function handleHover(code) {
|
||||
if (code === 'AQ') return;
|
||||
const country = data?.find(({ x }) => x === code);
|
||||
setTooltip(`${countryNames[code]}: ${country?.y || 0} visitors`);
|
||||
setTooltip(`${countryNames[code]}: ${formatLongNumber(country?.y || 0)} visitors`);
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -83,7 +84,7 @@ function WorldMap({ data, className }) {
|
|||
</Geographies>
|
||||
</ZoomableGroup>
|
||||
</ComposableMap>
|
||||
<ReactTooltip id="world-map-tooltip">{tooltip}</ReactTooltip>
|
||||
{tooltip && <HoverTooltip tooltip={tooltip} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
gap: 10px;
|
||||
font-size: var(--font-size-lg);
|
||||
font-weight: 700;
|
||||
color: var(--font-color100);
|
||||
color: var(--font-color100) !important;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
|
|
|
|||
|
|
@ -1,224 +1,185 @@
|
|||
import { useState, useRef, useEffect } from 'react';
|
||||
import { useState, useRef, useEffect, useMemo, useCallback } from 'react';
|
||||
import { StatusLight } from 'react-basics';
|
||||
import classNames from 'classnames';
|
||||
import ChartJS from 'chart.js';
|
||||
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 useForceUpdate from 'hooks/useForceUpdate';
|
||||
import { DEFAULT_ANIMATION_DURATION, THEME_COLORS } from 'lib/constants';
|
||||
import styles from './BarChart.module.css';
|
||||
import ChartTooltip from './ChartTooltip';
|
||||
|
||||
export default function BarChart({
|
||||
chartId,
|
||||
datasets,
|
||||
unit,
|
||||
records,
|
||||
animationDuration = DEFAULT_ANIMATION_DURATION,
|
||||
className,
|
||||
stacked = false,
|
||||
loading = false,
|
||||
onCreate = () => {},
|
||||
onUpdate = () => {},
|
||||
className,
|
||||
}) {
|
||||
const canvas = useRef();
|
||||
const chart = useRef();
|
||||
const chart = useRef(null);
|
||||
const [tooltip, setTooltip] = useState(null);
|
||||
const { locale } = useLocale();
|
||||
const [theme] = useTheme();
|
||||
const forceUpdate = useForceUpdate();
|
||||
|
||||
const colors = {
|
||||
text: THEME_COLORS[theme].gray700,
|
||||
line: THEME_COLORS[theme].gray200,
|
||||
zeroLine: THEME_COLORS[theme].gray500,
|
||||
const colors = useMemo(
|
||||
() => ({
|
||||
text: THEME_COLORS[theme].gray700,
|
||||
line: THEME_COLORS[theme].gray200,
|
||||
}),
|
||||
[theme],
|
||||
);
|
||||
|
||||
const renderYLabel = label => {
|
||||
return +label > 1000 ? formatLongNumber(label) : label;
|
||||
};
|
||||
|
||||
function renderXLabel(label, index, values) {
|
||||
if (loading) return '';
|
||||
const d = new Date(values[index].value);
|
||||
const sw = canvas.current.width / window.devicePixelRatio;
|
||||
const renderTooltip = useCallback(
|
||||
model => {
|
||||
const { opacity, labelColors, dataPoints } = model.tooltip;
|
||||
|
||||
switch (unit) {
|
||||
case 'minute':
|
||||
return index % 2 === 0 ? dateFormat(d, 'H:mm', locale) : '';
|
||||
case 'hour':
|
||||
return dateFormat(d, 'p', locale);
|
||||
case 'day':
|
||||
if (records > 25) {
|
||||
if (sw <= 275) {
|
||||
return index % 10 === 0 ? dateFormat(d, 'M/d', locale) : '';
|
||||
}
|
||||
if (sw <= 550) {
|
||||
return index % 5 === 0 ? dateFormat(d, 'M/d', locale) : '';
|
||||
}
|
||||
if (sw <= 700) {
|
||||
return index % 2 === 0 ? dateFormat(d, 'M/d', locale) : '';
|
||||
}
|
||||
return dateFormat(d, 'MMM d', locale);
|
||||
}
|
||||
if (sw <= 375) {
|
||||
return index % 2 === 0 ? dateFormat(d, 'MMM d', locale) : '';
|
||||
}
|
||||
if (sw <= 425) {
|
||||
return dateFormat(d, 'MMM d', locale);
|
||||
}
|
||||
return dateFormat(d, 'EEE M/d', locale);
|
||||
case 'month':
|
||||
if (sw <= 330) {
|
||||
return index % 2 === 0 ? dateFormat(d, 'MMM', locale) : '';
|
||||
}
|
||||
return dateFormat(d, 'MMM', locale);
|
||||
default:
|
||||
return label;
|
||||
}
|
||||
}
|
||||
if (!dataPoints?.length || !opacity) {
|
||||
setTooltip(null);
|
||||
return;
|
||||
}
|
||||
|
||||
function renderYLabel(label) {
|
||||
return +label > 1000 ? formatLongNumber(label) : label;
|
||||
}
|
||||
const formats = {
|
||||
millisecond: 'T',
|
||||
second: 'pp',
|
||||
minute: 'p',
|
||||
hour: 'h aaa',
|
||||
day: 'PPPP',
|
||||
week: 'PPPP',
|
||||
month: 'LLLL yyyy',
|
||||
quarter: 'qqq',
|
||||
year: 'yyyy',
|
||||
};
|
||||
|
||||
function renderTooltip(model) {
|
||||
const { opacity, title, body, labelColors } = model;
|
||||
setTooltip(
|
||||
<div className={styles.tooltip}>
|
||||
<div>{dateFormat(new Date(dataPoints[0].raw.x), formats[unit], locale)}</div>
|
||||
<div>
|
||||
<StatusLight color={labelColors?.[0]?.backgroundColor}>
|
||||
<div className={styles.value}>
|
||||
{formatLongNumber(dataPoints[0].raw.y)} {dataPoints[0].dataset.label}
|
||||
</div>
|
||||
</StatusLight>
|
||||
</div>
|
||||
</div>,
|
||||
);
|
||||
},
|
||||
[unit],
|
||||
);
|
||||
|
||||
if (!opacity || !title) {
|
||||
setTooltip(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const [label, value] = body[0].lines[0].split(':');
|
||||
|
||||
setTooltip({
|
||||
title: dateFormat(new Date(+title[0]), getTooltipFormat(unit), locale),
|
||||
value,
|
||||
label,
|
||||
labelColor: labelColors[0].backgroundColor,
|
||||
});
|
||||
}
|
||||
|
||||
function getTooltipFormat(unit) {
|
||||
switch (unit) {
|
||||
case 'hour':
|
||||
return 'EEE p — PPP';
|
||||
default:
|
||||
return 'PPPP';
|
||||
}
|
||||
}
|
||||
|
||||
function createChart() {
|
||||
const options = {
|
||||
const getOptions = useCallback(() => {
|
||||
return {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
animation: {
|
||||
duration: animationDuration,
|
||||
resize: {
|
||||
duration: 0,
|
||||
},
|
||||
active: {
|
||||
duration: 0,
|
||||
},
|
||||
},
|
||||
tooltips: {
|
||||
enabled: false,
|
||||
custom: renderTooltip,
|
||||
},
|
||||
hover: {
|
||||
animationDuration: 0,
|
||||
},
|
||||
responsive: true,
|
||||
responsiveAnimationDuration: 0,
|
||||
maintainAspectRatio: false,
|
||||
legend: {
|
||||
display: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
enabled: false,
|
||||
external: renderTooltip,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
xAxes: [
|
||||
{
|
||||
type: 'time',
|
||||
distribution: 'series',
|
||||
time: {
|
||||
unit,
|
||||
tooltipFormat: 'x',
|
||||
},
|
||||
ticks: {
|
||||
callback: renderXLabel,
|
||||
minRotation: 0,
|
||||
maxRotation: 0,
|
||||
fontColor: colors.text,
|
||||
autoSkipPadding: 1,
|
||||
},
|
||||
gridLines: {
|
||||
display: false,
|
||||
},
|
||||
offset: true,
|
||||
stacked: true,
|
||||
x: {
|
||||
type: 'time',
|
||||
stacked: true,
|
||||
time: {
|
||||
unit,
|
||||
},
|
||||
],
|
||||
yAxes: [
|
||||
{
|
||||
ticks: {
|
||||
callback: renderYLabel,
|
||||
beginAtZero: true,
|
||||
fontColor: colors.text,
|
||||
},
|
||||
gridLines: {
|
||||
color: colors.line,
|
||||
zeroLineColor: colors.zeroLine,
|
||||
},
|
||||
stacked,
|
||||
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]);
|
||||
|
||||
const createChart = () => {
|
||||
Chart.defaults.font.family = 'Inter';
|
||||
|
||||
const options = getOptions();
|
||||
|
||||
onCreate(options);
|
||||
|
||||
chart.current = new ChartJS(canvas.current, {
|
||||
chart.current = new Chart(canvas.current, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
datasets,
|
||||
},
|
||||
options,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function updateChart() {
|
||||
const { options } = chart.current;
|
||||
const updateChart = () => {
|
||||
setTooltip(null);
|
||||
|
||||
options.legend.labels.fontColor = colors.text;
|
||||
options.scales.xAxes[0].time.unit = unit;
|
||||
options.scales.xAxes[0].ticks.callback = renderXLabel;
|
||||
options.scales.xAxes[0].ticks.fontColor = colors.text;
|
||||
options.scales.yAxes[0].ticks.fontColor = colors.text;
|
||||
options.scales.yAxes[0].ticks.precision = 0;
|
||||
options.scales.yAxes[0].gridLines.color = colors.line;
|
||||
options.scales.yAxes[0].gridLines.zeroLineColor = colors.zeroLine;
|
||||
options.animation.duration = animationDuration;
|
||||
options.tooltips.custom = renderTooltip;
|
||||
chart.current.options = getOptions();
|
||||
|
||||
onUpdate(chart.current);
|
||||
|
||||
chart.current.update();
|
||||
|
||||
forceUpdate();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (datasets) {
|
||||
if (!chart.current) {
|
||||
createChart();
|
||||
} else {
|
||||
setTooltip(null);
|
||||
updateChart();
|
||||
}
|
||||
}
|
||||
}, [datasets, unit, animationDuration, locale, theme]);
|
||||
}, [datasets, unit, theme, animationDuration, locale, loading]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
data-tip=""
|
||||
data-for={`${chartId}-tooltip`}
|
||||
className={classNames(styles.chart, className)}
|
||||
>
|
||||
<div className={classNames(styles.chart, className)}>
|
||||
<canvas ref={canvas} />
|
||||
</div>
|
||||
<Legend chart={chart.current} />
|
||||
<ChartTooltip chartId={chartId} tooltip={tooltip} />
|
||||
{tooltip && <HoverTooltip tooltip={tooltip} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,21 @@
|
|||
.chart {
|
||||
position: relative;
|
||||
height: 400px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.tooltip .value {
|
||||
text-transform: lowercase;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 992px) {
|
||||
.chart {
|
||||
height: 200px;
|
||||
/*height: 200px;*/
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
import { StatusLight } from 'react-basics';
|
||||
import styles from './ChartTooltip.module.css';
|
||||
import ReactTooltip from 'react-tooltip';
|
||||
|
||||
export default function ChartTooltip({ chartId, tooltip }) {
|
||||
if (!tooltip) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { title, value, label, labelColor } = tooltip;
|
||||
|
||||
return (
|
||||
<ReactTooltip id={`${chartId}-tooltip`}>
|
||||
<div className={styles.tooltip}>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.title}>{title}</div>
|
||||
<div className={styles.metric}>
|
||||
<StatusLight color={labelColor}>
|
||||
{value} {label}
|
||||
</StatusLight>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ReactTooltip>
|
||||
);
|
||||
}
|
||||
|
|
@ -33,12 +33,12 @@ export default function EventsChart({ websiteId, className, token }) {
|
|||
if (!data) return [];
|
||||
if (isLoading) return data;
|
||||
|
||||
const map = data.reduce((obj, { x, t, y }) => {
|
||||
const map = data.reduce((obj, { x, y }) => {
|
||||
if (!obj[x]) {
|
||||
obj[x] = [];
|
||||
}
|
||||
|
||||
obj[x].push({ t, y });
|
||||
obj[x].push({ x, y });
|
||||
|
||||
return obj;
|
||||
}, {});
|
||||
|
|
@ -76,7 +76,6 @@ export default function EventsChart({ websiteId, className, token }) {
|
|||
|
||||
return (
|
||||
<BarChart
|
||||
chartId={`events-${websiteId}`}
|
||||
className={className}
|
||||
datasets={datasets}
|
||||
unit={unit}
|
||||
|
|
|
|||
|
|
@ -32,15 +32,13 @@ export default function FilterTags({ websiteId, params, onClick }) {
|
|||
return null;
|
||||
}
|
||||
return (
|
||||
<div key={key} className={styles.tag}>
|
||||
<Button onClick={() => handleCloseFilter(key)} variant="primary" size="sm">
|
||||
<Text>
|
||||
<b>{`${key}`}</b> — {`${safeDecodeURI(params[key])}`}
|
||||
</Text>
|
||||
<Icon>
|
||||
<Icons.Close />
|
||||
</Icon>
|
||||
</Button>
|
||||
<div key={key} className={styles.tag} onClick={() => handleCloseFilter(key)}>
|
||||
<Text>
|
||||
<b>{`${key}`}</b> = {`${safeDecodeURI(params[key])}`}
|
||||
</Text>
|
||||
<Icon>
|
||||
<Icons.Close />
|
||||
</Icon>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,22 @@
|
|||
.filters {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.tag {
|
||||
text-align: center;
|
||||
margin-bottom: 10px;
|
||||
margin-right: 20px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-size: var(--font-size-sm);
|
||||
border: 1px solid var(--base600);
|
||||
border-radius: var(--border-radius);
|
||||
line-height: 30px;
|
||||
padding: 0 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tag:hover {
|
||||
background: var(--base75);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 10px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: var(--font-size-xs);
|
||||
font-size: var(--font-size-sm);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ export default function MetricsTable({
|
|||
|
||||
const { data, isLoading, isFetched, error } = useQuery(
|
||||
[
|
||||
'websites:mnetrics',
|
||||
'websites:metrics',
|
||||
{ websiteId, type, modified, url, referrer, os, browser, device, country },
|
||||
],
|
||||
() =>
|
||||
|
|
|
|||
|
|
@ -25,12 +25,16 @@ export default function PageviewsChart({
|
|||
const primaryColor = colord(THEME_COLORS[theme].primary);
|
||||
return {
|
||||
views: {
|
||||
background: primaryColor.alpha(0.4).toRgbString(),
|
||||
border: primaryColor.alpha(0.5).toRgbString(),
|
||||
hoverBackgroundColor: primaryColor.alpha(0.7).toRgbString(),
|
||||
backgroundColor: primaryColor.alpha(0.4).toRgbString(),
|
||||
borderColor: primaryColor.alpha(0.7).toRgbString(),
|
||||
hoverBorderColor: primaryColor.toRgbString(),
|
||||
},
|
||||
visitors: {
|
||||
background: primaryColor.alpha(0.6).toRgbString(),
|
||||
border: primaryColor.alpha(0.7).toRgbString(),
|
||||
hoverBackgroundColor: primaryColor.alpha(0.9).toRgbString(),
|
||||
backgroundColor: primaryColor.alpha(0.6).toRgbString(),
|
||||
borderColor: primaryColor.alpha(0.9).toRgbString(),
|
||||
hoverBorderColor: primaryColor.toRgbString(),
|
||||
},
|
||||
};
|
||||
}, [theme]);
|
||||
|
|
@ -50,30 +54,28 @@ export default function PageviewsChart({
|
|||
return null;
|
||||
}
|
||||
|
||||
const datasets = [
|
||||
{
|
||||
label: formatMessage(labels.uniqueVisitors),
|
||||
data: data.sessions,
|
||||
borderWidth: 1,
|
||||
...colors.visitors,
|
||||
},
|
||||
{
|
||||
label: formatMessage(labels.pageViews),
|
||||
data: data.pageviews,
|
||||
borderWidth: 1,
|
||||
...colors.views,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<BarChart
|
||||
{...props}
|
||||
key={websiteId}
|
||||
className={className}
|
||||
chartId={websiteId}
|
||||
datasets={[
|
||||
{
|
||||
label: formatMessage(labels.uniqueVisitors),
|
||||
data: data.sessions,
|
||||
lineTension: 0,
|
||||
backgroundColor: colors.visitors.background,
|
||||
borderColor: colors.visitors.border,
|
||||
borderWidth: 1,
|
||||
},
|
||||
{
|
||||
label: formatMessage(labels.pageViews),
|
||||
data: data.pageviews,
|
||||
lineTension: 0,
|
||||
backgroundColor: colors.views.background,
|
||||
borderColor: colors.views.border,
|
||||
borderWidth: 1,
|
||||
},
|
||||
]}
|
||||
datasets={datasets}
|
||||
unit={unit}
|
||||
records={records}
|
||||
animationDuration={visible ? animationDuration : 0}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,11 @@
|
|||
import { useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import MetricsTable from './MetricsTable';
|
||||
import FilterButtons from 'components/common/FilterButtons';
|
||||
import FilterLink from 'components/common/FilterLink';
|
||||
import { refFilter } from 'lib/filters';
|
||||
import { labels } from 'components/messages';
|
||||
import { FILTER_COMBINED, FILTER_RAW } from 'lib/constants';
|
||||
|
||||
const filters = {
|
||||
[FILTER_RAW]: null,
|
||||
[FILTER_COMBINED]: refFilter,
|
||||
};
|
||||
|
||||
export default function ReferrersTable({ websiteId, showFilters, ...props }) {
|
||||
const [filter, setFilter] = useState(FILTER_COMBINED);
|
||||
export default function ReferrersTable({ websiteId, ...props }) {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
const items = [
|
||||
{
|
||||
label: formatMessage(labels.filterCombined),
|
||||
key: FILTER_COMBINED,
|
||||
},
|
||||
{ label: formatMessage(labels.filterRaw), key: FILTER_RAW },
|
||||
];
|
||||
|
||||
const renderLink = ({ w: link, x: referrer }) => {
|
||||
return referrer ? (
|
||||
<FilterLink id="referrer" value={referrer} externalUrl={link} />
|
||||
|
|
@ -34,14 +16,12 @@ export default function ReferrersTable({ websiteId, showFilters, ...props }) {
|
|||
|
||||
return (
|
||||
<>
|
||||
{showFilters && <FilterButtons items={items} selectedKey={filter} onSelect={setFilter} />}
|
||||
<MetricsTable
|
||||
{...props}
|
||||
title={formatMessage(labels.referrers)}
|
||||
type="referrer"
|
||||
metric={formatMessage(labels.views)}
|
||||
websiteId={websiteId}
|
||||
dataFilter={filters[filter]}
|
||||
renderLabel={renderLink}
|
||||
/>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
.chart {
|
||||
position: relative;
|
||||
padding-bottom: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.title {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { FixedSizeList } from 'react-window';
|
|||
import firstBy from 'thenby';
|
||||
import FilterButtons from 'components/common/FilterButtons';
|
||||
import NoData from 'components/common/NoData';
|
||||
import { getDeviceMessage, labels, messages } from 'components/messages';
|
||||
import { labels, messages } from 'components/messages';
|
||||
import useLocale from 'hooks/useLocale';
|
||||
import useCountryNames from 'hooks/useCountryNames';
|
||||
import { BROWSERS } from 'lib/constants';
|
||||
|
|
@ -102,7 +102,7 @@ export default function RealtimeLog({ data, websiteDomain }) {
|
|||
country: <b>{countryNames[country] || formatMessage(labels.unknown)}</b>,
|
||||
browser: <b>{BROWSERS[browser]}</b>,
|
||||
os: <b>{os}</b>,
|
||||
device: <b>{formatMessage(getDeviceMessage(device))}</b>,
|
||||
device: <b>{formatMessage(labels[device] || labels.unknown)}</b>,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -17,14 +17,14 @@ import { labels } from 'components/messages';
|
|||
import useUser from 'hooks/useUser';
|
||||
import useApi from 'hooks/useApi';
|
||||
|
||||
export default function TeamWebsitesTable({ teamId, data = [], onSave }) {
|
||||
export default function TeamWebsitesTable({ data = [], onSave }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { user } = useUser();
|
||||
const { del, useMutation } = useApi();
|
||||
const { mutate } = useMutation(data => del(`/teamWebsites/${data.teamWebsiteId}`));
|
||||
const { mutate } = useMutation(({ teamWebsiteId }) => del(`/teamWebsites/${teamWebsiteId}`));
|
||||
|
||||
const columns = [
|
||||
{ name: 'name', label: formatMessage(labels.name), style: { flex: 2 } },
|
||||
{ name: 'name', label: formatMessage(labels.name) },
|
||||
{ name: 'domain', label: formatMessage(labels.domain) },
|
||||
{ name: 'action', label: ' ' },
|
||||
];
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue