{mapChildren(children, child => {
return
{child}
;
})}
diff --git a/src/components/layout/Page.module.css b/src/components/layout/Page.module.css
index 52893157..3b9a4581 100644
--- a/src/components/layout/Page.module.css
+++ b/src/components/layout/Page.module.css
@@ -8,4 +8,5 @@
margin: 0 auto;
padding: 0 20px;
min-height: calc(100vh - 60px);
+ min-height: calc(100dvh - 60px);
}
diff --git a/src/components/messages.ts b/src/components/messages.ts
index 79a6b92c..688dd11d 100644
--- a/src/components/messages.ts
+++ b/src/components/messages.ts
@@ -29,6 +29,7 @@ export const labels = defineMessages({
createdBy: { id: 'label.created-by', defaultMessage: 'Created By' },
edit: { id: 'label.edit', defaultMessage: 'Edit' },
name: { id: 'label.name', defaultMessage: 'Name' },
+ manager: { id: 'label.manager', defaultMessage: 'Manager' },
member: { id: 'label.member', defaultMessage: 'Member' },
members: { id: 'label.members', defaultMessage: 'Members' },
accessCode: { id: 'label.access-code', defaultMessage: 'Access code' },
@@ -43,6 +44,7 @@ export const labels = defineMessages({
settings: { id: 'label.settings', defaultMessage: 'Settings' },
owner: { id: 'label.owner', defaultMessage: 'Owner' },
teamOwner: { id: 'label.team-owner', defaultMessage: 'Team owner' },
+ teamManager: { id: 'label.team-manager', defaultMessage: 'Team manager' },
teamMember: { id: 'label.team-member', defaultMessage: 'Team member' },
teamViewOnly: { id: 'label.team-view-only', defaultMessage: 'Team view only' },
enableShareUrl: { id: 'label.enable-share-url', defaultMessage: 'Enable share URL' },
@@ -86,13 +88,20 @@ export const labels = defineMessages({
leaveTeam: { id: 'label.leave-team', defaultMessage: 'Leave team' },
refresh: { id: 'label.refresh', defaultMessage: 'Refresh' },
pages: { id: 'label.pages', defaultMessage: 'Pages' },
+ entry: { id: 'label.entry', defaultMessage: 'Entry path' },
+ exit: { id: 'label.exit', defaultMessage: 'Exit path' },
referrers: { id: 'label.referrers', defaultMessage: 'Referrers' },
+ hosts: { id: 'label.hosts', defaultMessage: 'Hosts' },
screens: { id: 'label.screens', defaultMessage: 'Screens' },
browsers: { id: 'label.browsers', defaultMessage: 'Browsers' },
os: { id: 'label.os', defaultMessage: 'OS' },
devices: { id: 'label.devices', defaultMessage: 'Devices' },
countries: { id: 'label.countries', defaultMessage: 'Countries' },
languages: { id: 'label.languages', defaultMessage: 'Languages' },
+ tags: { id: 'label.tags', defaultMessage: 'Tags' },
+ count: { id: 'label.count', defaultMessage: 'Count' },
+ average: { id: 'label.average', defaultMessage: 'Average' },
+ sum: { id: 'label.sum', defaultMessage: 'Sum' },
event: { id: 'label.event', defaultMessage: 'Event' },
events: { id: 'label.events', defaultMessage: 'Events' },
query: { id: 'label.query', defaultMessage: 'Query' },
@@ -105,6 +114,7 @@ export const labels = defineMessages({
views: { id: 'label.views', defaultMessage: 'Views' },
none: { id: 'label.none', defaultMessage: 'None' },
clearAll: { id: 'label.clear-all', defaultMessage: 'Clear all' },
+ property: { id: 'label.property', defaultMessage: 'Property' },
today: { id: 'label.today', defaultMessage: 'Today' },
lastHours: { id: 'label.last-hours', defaultMessage: 'Last {x} hours' },
yesterday: { id: 'label.yesterday', defaultMessage: 'Yesterday' },
@@ -119,16 +129,17 @@ export const labels = defineMessages({
selectRole: { id: 'label.select-role', defaultMessage: 'Select role' },
selectDate: { id: 'label.select-date', defaultMessage: 'Select date' },
all: { id: 'label.all', defaultMessage: 'All' },
+ session: { id: 'label.session', defaultMessage: 'Session' },
sessions: { id: 'label.sessions', defaultMessage: 'Sessions' },
pageNotFound: { id: 'message.page-not-found', defaultMessage: 'Page not found' },
- activityLog: { id: 'label.activity-log', defaultMessage: 'Activity log' },
+ activity: { id: 'label.activity', defaultMessage: 'Activity' },
dismiss: { id: 'label.dismiss', defaultMessage: 'Dismiss' },
poweredBy: { id: 'label.powered-by', defaultMessage: 'Powered by {name}' },
pageViews: { id: 'label.page-views', defaultMessage: 'Page views' },
uniqueVisitors: { id: 'label.unique-visitors', defaultMessage: 'Unique visitors' },
bounceRate: { id: 'label.bounce-rate', defaultMessage: 'Bounce rate' },
viewsPerVisit: { id: 'label.views-per-visit', defaultMessage: 'Views per visit' },
- averageVisitTime: { id: 'label.average-visit-time', defaultMessage: 'Average visit time' },
+ visitDuration: { id: 'label.visit-duration', defaultMessage: 'Visit duration' },
desktop: { id: 'label.desktop', defaultMessage: 'Desktop' },
laptop: { id: 'label.laptop', defaultMessage: 'Laptop' },
tablet: { id: 'label.tablet', defaultMessage: 'Tablet' },
@@ -141,13 +152,22 @@ export const labels = defineMessages({
regions: { id: 'label.regions', defaultMessage: 'Regions' },
reports: { id: 'label.reports', defaultMessage: 'Reports' },
eventData: { id: 'label.event-data', defaultMessage: 'Event data' },
+ sessionData: { id: 'label.session-data', defaultMessage: 'Session data' },
funnel: { id: 'label.funnel', defaultMessage: 'Funnel' },
funnelDescription: {
id: 'label.funnel-description',
defaultMessage: 'Understand the conversion and drop-off rate of users.',
},
+ revenue: { id: 'label.revenue', defaultMessage: 'Revenue' },
+ revenueDescription: {
+ id: 'label.revenue-description',
+ defaultMessage: 'Look into your revenue data and how users are spending.',
+ },
+ currency: { id: 'label.currency', defaultMessage: 'Currency' },
url: { id: 'label.url', defaultMessage: 'URL' },
urls: { id: 'label.urls', defaultMessage: 'URLs' },
+ path: { id: 'label.path', defaultMessage: 'Path' },
+ paths: { id: 'label.paths', defaultMessage: 'Paths' },
add: { id: 'label.add', defaultMessage: 'Add' },
update: { id: 'label.update', defaultMessage: 'Update' },
window: { id: 'label.window', defaultMessage: 'Window' },
@@ -176,8 +196,6 @@ export const labels = defineMessages({
before: { id: 'label.before', defaultMessage: 'Before' },
after: { id: 'label.after', defaultMessage: 'After' },
total: { id: 'label.total', defaultMessage: 'Total' },
- sum: { id: 'label.sum', defaultMessage: 'Sum' },
- average: { id: 'label.average', defaultMessage: 'Average' },
min: { id: 'label.min', defaultMessage: 'Min' },
max: { id: 'label.max', defaultMessage: 'Max' },
unique: { id: 'label.unique', defaultMessage: 'Unique' },
@@ -196,12 +214,14 @@ export const labels = defineMessages({
},
dropoff: { id: 'label.dropoff', defaultMessage: 'Dropoff' },
referrer: { id: 'label.referrer', defaultMessage: 'Referrer' },
+ host: { id: 'label.host', defaultMessage: 'Host' },
country: { id: 'label.country', defaultMessage: 'Country' },
region: { id: 'label.region', defaultMessage: 'Region' },
city: { id: 'label.city', defaultMessage: 'City' },
browser: { id: 'label.browser', defaultMessage: 'Browser' },
device: { id: 'label.device', defaultMessage: 'Device' },
pageTitle: { id: 'label.pageTitle', defaultMessage: 'Page title' },
+ tag: { id: 'label.tag', defaultMessage: 'Tag' },
day: { id: 'label.day', defaultMessage: 'Day' },
date: { id: 'label.date', defaultMessage: 'Date' },
pageOf: { id: 'label.page-of', defaultMessage: 'Page {current} of {total}' },
@@ -214,10 +234,16 @@ export const labels = defineMessages({
select: { id: 'label.select', defaultMessage: 'Select' },
myAccount: { id: 'label.my-account', defaultMessage: 'My account' },
transfer: { id: 'label.transfer', defaultMessage: 'Transfer' },
+ transactions: { id: 'label.transactions', defaultMessage: 'Transactions' },
+ uniqueCustomers: { id: 'label.uniqueCustomers', defaultMessage: 'Unique Customers' },
viewedPage: {
id: 'message.viewed-page',
defaultMessage: 'Viewed page',
},
+ collectedData: {
+ id: 'message.collected-data',
+ defaultMessage: 'Collected data',
+ },
triggeredEvent: {
id: 'message.triggered-event',
defaultMessage: 'Triggered event',
@@ -232,7 +258,28 @@ export const labels = defineMessages({
defaultMessage: 'Track your campaigns through UTM parameters.',
},
steps: { id: 'label.steps', defaultMessage: 'Steps' },
+ startStep: { id: 'label.start-step', defaultMessage: 'Start Step' },
+ endStep: { id: 'label.end-step', defaultMessage: 'End Step' },
addStep: { id: 'label.add-step', defaultMessage: 'Add step' },
+ goal: { id: 'label.goal', defaultMessage: 'Goal' },
+ goals: { id: 'label.goals', defaultMessage: 'Goals' },
+ goalsDescription: {
+ id: 'label.goals-description',
+ defaultMessage: 'Track your goals for pageviews and events.',
+ },
+ journey: { id: 'label.journey', defaultMessage: 'Journey' },
+ journeyDescription: {
+ id: 'label.journey-description',
+ defaultMessage: 'Understand how users navigate through your website.',
+ },
+ compare: { id: 'label.compare', defaultMessage: 'Compare' },
+ current: { id: 'label.current', defaultMessage: 'Current' },
+ previous: { id: 'label.previous', defaultMessage: 'Previous' },
+ previousPeriod: { id: 'label.previous-period', defaultMessage: 'Previous period' },
+ previousYear: { id: 'label.previous-year', defaultMessage: 'Previous year' },
+ lastSeen: { id: 'label.last-seen', defaultMessage: 'Last seen' },
+ firstSeen: { id: 'label.first-seen', defaultMessage: 'First seen' },
+ properties: { id: 'label.properties', defaultMessage: 'Properties' },
});
export const messages = defineMessages({
diff --git a/src/components/metrics/BrowsersTable.tsx b/src/components/metrics/BrowsersTable.tsx
index c0c741cf..d0cec124 100644
--- a/src/components/metrics/BrowsersTable.tsx
+++ b/src/components/metrics/BrowsersTable.tsx
@@ -2,6 +2,7 @@ import FilterLink from 'components/common/FilterLink';
import MetricsTable, { MetricsTableProps } from 'components/metrics/MetricsTable';
import { useMessages } from 'components/hooks';
import { useFormat } from 'components/hooks';
+import TypeIcon from 'components/common/TypeIcon';
export function BrowsersTable(props: MetricsTableProps) {
const { formatMessage, labels } = useMessages();
@@ -10,12 +11,7 @@ export function BrowsersTable(props: MetricsTableProps) {
function renderLink({ x: browser }) {
return (
-
+
);
}
diff --git a/src/components/metrics/ChangeLabel.module.css b/src/components/metrics/ChangeLabel.module.css
new file mode 100644
index 00000000..802a9931
--- /dev/null
+++ b/src/components/metrics/ChangeLabel.module.css
@@ -0,0 +1,26 @@
+.label {
+ display: flex;
+ align-items: center;
+ gap: 5px;
+ font-size: 13px;
+ font-weight: 700;
+ padding: 0.1em 0.5em;
+ border-radius: 5px;
+ color: var(--base500);
+ align-self: flex-start;
+}
+
+.positive {
+ color: var(--green700);
+ background: var(--green100);
+}
+
+.negative {
+ color: var(--red700);
+ background: var(--red100);
+}
+
+.neutral {
+ color: var(--base700);
+ background: var(--base100);
+}
diff --git a/src/components/metrics/ChangeLabel.tsx b/src/components/metrics/ChangeLabel.tsx
new file mode 100644
index 00000000..7e7cb77b
--- /dev/null
+++ b/src/components/metrics/ChangeLabel.tsx
@@ -0,0 +1,46 @@
+import classNames from 'classnames';
+import { Icon, Icons } from 'react-basics';
+import { ReactNode } from 'react';
+import styles from './ChangeLabel.module.css';
+
+export function ChangeLabel({
+ value,
+ size,
+ title,
+ reverseColors,
+ className,
+ children,
+}: {
+ value: number;
+ size?: 'xs' | 'sm' | 'md' | 'lg';
+ title?: string;
+ reverseColors?: boolean;
+ showPercentage?: boolean;
+ className?: string;
+ children?: ReactNode;
+}) {
+ const positive = value >= 0;
+ const negative = value < 0;
+ const neutral = value === 0 || isNaN(value);
+ const good = reverseColors ? negative : positive;
+
+ return (
+
+ {!neutral && (
+
+
+
+ )}
+ {children || value}
+
+ );
+}
+
+export default ChangeLabel;
diff --git a/src/components/metrics/CitiesTable.tsx b/src/components/metrics/CitiesTable.tsx
index fbbcafa6..61624f6a 100644
--- a/src/components/metrics/CitiesTable.tsx
+++ b/src/components/metrics/CitiesTable.tsx
@@ -1,6 +1,9 @@
import MetricsTable, { MetricsTableProps } from './MetricsTable';
import { emptyFilter } from 'lib/filters';
import FilterLink from 'components/common/FilterLink';
+
+import TypeIcon from 'components/common/TypeIcon';
+import { useLocale } from 'components/hooks';
import { useMessages } from 'components/hooks';
import { useFormat } from 'components/hooks';
diff --git a/src/components/metrics/CountriesTable.tsx b/src/components/metrics/CountriesTable.tsx
index 4a5db7fd..3354c73c 100644
--- a/src/components/metrics/CountriesTable.tsx
+++ b/src/components/metrics/CountriesTable.tsx
@@ -2,34 +2,22 @@ import FilterLink from 'components/common/FilterLink';
import { useCountryNames } from 'components/hooks';
import { useLocale, useMessages, useFormat } from 'components/hooks';
import MetricsTable, { MetricsTableProps } from './MetricsTable';
+import TypeIcon from 'components/common/TypeIcon';
-export function CountriesTable({
- onDataLoad,
- ...props
-}: {
- onDataLoad: (data: any) => void;
-} & MetricsTableProps) {
+export function CountriesTable({ ...props }: MetricsTableProps) {
const { locale } = useLocale();
- const countryNames = useCountryNames(locale);
+ const { countryNames } = useCountryNames(locale);
const { formatMessage, labels } = useMessages();
const { formatCountry } = useFormat();
- const handleDataLoad = (data: any) => {
- onDataLoad?.(data);
- };
-
const renderLink = ({ x: code }) => {
return (
-
+
);
};
diff --git a/src/components/metrics/DevicesTable.tsx b/src/components/metrics/DevicesTable.tsx
index 7e078986..c25afe4f 100644
--- a/src/components/metrics/DevicesTable.tsx
+++ b/src/components/metrics/DevicesTable.tsx
@@ -2,6 +2,7 @@ import MetricsTable, { MetricsTableProps } from './MetricsTable';
import FilterLink from 'components/common/FilterLink';
import { useMessages } from 'components/hooks';
import { useFormat } from 'components/hooks';
+import TypeIcon from 'components/common/TypeIcon';
export function DevicesTable(props: MetricsTableProps) {
const { formatMessage, labels } = useMessages();
@@ -10,12 +11,7 @@ export function DevicesTable(props: MetricsTableProps) {
function renderLink({ x: device }) {
return (
-
+
);
}
diff --git a/src/components/metrics/EventsChart.tsx b/src/components/metrics/EventsChart.tsx
index eb2fb703..2ba2caee 100644
--- a/src/components/metrics/EventsChart.tsx
+++ b/src/components/metrics/EventsChart.tsx
@@ -1,11 +1,9 @@
-import { useMemo } from 'react';
-import { Loading } from 'react-basics';
import { colord } from 'colord';
import BarChart from 'components/charts/BarChart';
-import { getDateArray } from 'lib/date';
-import { useLocale, useDateRange, useWebsiteEvents } from 'components/hooks';
-import { CHART_COLORS } from 'lib/constants';
+import { useDateRange, useLocale, useWebsiteEventsSeries } from 'components/hooks';
import { renderDateLabels } from 'lib/charts';
+import { CHART_COLORS } from 'lib/constants';
+import { useMemo } from 'react';
export interface EventsChartProps {
websiteId: string;
@@ -13,9 +11,11 @@ export interface EventsChartProps {
}
export function EventsChart({ websiteId, className }: EventsChartProps) {
- const [{ startDate, endDate, unit }] = useDateRange(websiteId);
+ const {
+ dateRange: { startDate, endDate, unit, value },
+ } = useDateRange(websiteId);
const { locale } = useLocale();
- const { data, isLoading } = useWebsiteEvents(websiteId);
+ const { data, isLoading } = useWebsiteEventsSeries(websiteId);
const chartData = useMemo(() => {
if (!data) return [];
@@ -30,10 +30,6 @@ export function EventsChart({ websiteId, className }: EventsChartProps) {
return obj;
}, {});
- Object.keys(map).forEach(key => {
- map[key] = getDateArray(map[key], startDate, endDate, unit);
- });
-
return {
datasets: Object.keys(map).map((key, index) => {
const color = colord(CHART_COLORS[index % CHART_COLORS.length]);
@@ -49,18 +45,17 @@ export function EventsChart({ websiteId, className }: EventsChartProps) {
};
}, [data, startDate, endDate, unit]);
- if (isLoading) {
- return
;
- }
-
return (
);
}
diff --git a/src/components/metrics/FilterTags.module.css b/src/components/metrics/FilterTags.module.css
index fe5c345c..ea7714f4 100644
--- a/src/components/metrics/FilterTags.module.css
+++ b/src/components/metrics/FilterTags.module.css
@@ -2,6 +2,12 @@
display: flex;
align-items: center;
gap: 10px;
+ background: var(--base75);
+ padding: 10px 20px;
+ border: 1px solid var(--base400);
+ border-radius: 8px;
+ margin-bottom: 20px;
+ flex-wrap: wrap;
}
.label {
@@ -12,12 +18,13 @@
display: flex;
flex-direction: row;
align-items: center;
- gap: 10px;
- background: var(--base75);
+ gap: 4px;
+ font-size: 12px;
+ background: var(--base50);
border: 1px solid var(--base400);
border-radius: var(--border-radius);
box-shadow: 1px 1px 1px var(--base500);
- padding: 8px 16px;
+ padding: 6px 14px;
cursor: pointer;
}
@@ -27,6 +34,8 @@
.close {
font-weight: 700;
+ align-self: center;
+ margin-left: auto;
}
.name,
diff --git a/src/components/metrics/FilterTags.tsx b/src/components/metrics/FilterTags.tsx
index 35d12556..60cf90c1 100644
--- a/src/components/metrics/FilterTags.tsx
+++ b/src/components/metrics/FilterTags.tsx
@@ -13,6 +13,7 @@ import FieldFilterEditForm from 'app/(main)/reports/[reportId]/FieldFilterEditFo
import { OPERATOR_PREFIXES } from 'lib/constants';
import { isSearchOperator, parseParameterValue } from 'lib/params';
import styles from './FilterTags.module.css';
+import WebsiteFilterButton from 'app/(main)/websites/[websiteId]/WebsiteFilterButton';
export function FilterTags({
websiteId,
@@ -23,7 +24,7 @@ export function FilterTags({
}) {
const { formatMessage, labels } = useMessages();
const { formatValue } = useFormat();
- const [dateRange] = useDateRange(websiteId);
+ const { dateRange } = useDateRange(websiteId);
const {
router,
renderUrl,
@@ -100,6 +101,7 @@ export function FilterTags({
);
})}
+
diff --git a/src/components/metrics/HostsTable.tsx b/src/components/metrics/HostsTable.tsx
new file mode 100644
index 00000000..45147eac
--- /dev/null
+++ b/src/components/metrics/HostsTable.tsx
@@ -0,0 +1,35 @@
+import MetricsTable, { MetricsTableProps } from './MetricsTable';
+import FilterLink from 'components/common/FilterLink';
+import { useMessages } from 'components/hooks';
+import { Flexbox } from 'react-basics';
+
+export function HostsTable(props: MetricsTableProps) {
+ const { formatMessage, labels } = useMessages();
+
+ const renderLink = ({ x: host }) => {
+ return (
+
+
+
+ );
+ };
+
+ return (
+ <>
+
+ >
+ );
+}
+
+export default HostsTable;
diff --git a/src/components/metrics/Legend.tsx b/src/components/metrics/Legend.tsx
index c7ef1022..4ebcf4b4 100644
--- a/src/components/metrics/Legend.tsx
+++ b/src/components/metrics/Legend.tsx
@@ -3,7 +3,6 @@ import { safeDecodeURIComponent } from 'next-basics';
import { colord } from 'colord';
import classNames from 'classnames';
import { LegendItem } from 'chart.js/auto';
-import { useLocale } from 'components/hooks';
import styles from './Legend.module.css';
export function Legend({
@@ -13,8 +12,6 @@ export function Legend({
items: any[];
onClick: (index: LegendItem) => void;
}) {
- const { locale } = useLocale();
-
if (!items.find(({ text }) => text)) {
return null;
}
@@ -32,7 +29,7 @@ export function Legend({
onClick={() => onClick(item)}
>
- {safeDecodeURIComponent(text)}
+ {safeDecodeURIComponent(text)}
);
diff --git a/src/components/metrics/ListTable.module.css b/src/components/metrics/ListTable.module.css
index 9c3d5cff..405819b1 100644
--- a/src/components/metrics/ListTable.module.css
+++ b/src/components/metrics/ListTable.module.css
@@ -72,9 +72,11 @@
}
.value {
- width: 50px;
+ display: flex;
+ align-items: center;
+ gap: 10px;
text-align: end;
- margin-inline-end: 10px;
+ margin-inline-end: 5px;
font-weight: 600;
}
diff --git a/src/components/metrics/ListTable.tsx b/src/components/metrics/ListTable.tsx
index 133905e1..59ded491 100644
--- a/src/components/metrics/ListTable.tsx
+++ b/src/components/metrics/ListTable.tsx
@@ -14,7 +14,8 @@ export interface ListTableProps {
title?: string;
metric?: string;
className?: string;
- renderLabel?: (row: any) => ReactNode;
+ renderLabel?: (row: any, index: number) => ReactNode;
+ renderChange?: (row: any, index: number) => ReactNode;
animate?: boolean;
virtualize?: boolean;
showPercentage?: boolean;
@@ -27,6 +28,7 @@ export function ListTable({
metric,
className,
renderLabel,
+ renderChange,
animate = true,
virtualize = false,
showPercentage = true,
@@ -34,23 +36,24 @@ export function ListTable({
}: ListTableProps) {
const { formatMessage, labels } = useMessages();
- const getRow = row => {
+ const getRow = (row: { x: any; y: any; z: any }, index: number) => {
const { x: label, y: value, z: percent } = row;
return (
;
};
return (
@@ -71,14 +74,14 @@ export function ListTable({
{Row}
) : (
- data.map(row => getRow(row))
+ data.map(getRow)
)}