mirror of
https://github.com/kremalicious/umami.git
synced 2024-11-22 09:57:00 +01:00
CSS updates.
This commit is contained in:
parent
9bb89c7e8b
commit
e4c5f42189
@ -9,7 +9,7 @@ import Head from 'next/head';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import Script from 'next/script';
|
||||
import { Button, Column, Row } from 'react-basics';
|
||||
import { Button } from 'react-basics';
|
||||
import styles from './TestConsole.module.css';
|
||||
|
||||
export function TestConsole() {
|
||||
@ -91,8 +91,8 @@ export function TestConsole() {
|
||||
src={`${basePath}/script.js`}
|
||||
data-cache="true"
|
||||
/>
|
||||
<Row className={styles.test}>
|
||||
<Column xs="4">
|
||||
<div className={styles.test}>
|
||||
<div>
|
||||
<div className={styles.header}>Page links</div>
|
||||
<div>
|
||||
<Link href={`/console/${websiteId}/page/1/?q=abc`}>page one</Link>
|
||||
@ -115,8 +115,8 @@ export function TestConsole() {
|
||||
external link (tab)
|
||||
</a>
|
||||
</div>
|
||||
</Column>
|
||||
<Column xs="4">
|
||||
</div>
|
||||
<div>
|
||||
<div className={styles.header}>Click events</div>
|
||||
<Button id="send-event-button" data-umami-event="button-click" variant="action">
|
||||
Send event
|
||||
@ -131,8 +131,8 @@ export function TestConsole() {
|
||||
>
|
||||
Send event with data
|
||||
</Button>
|
||||
</Column>
|
||||
<Column xs="4">
|
||||
</div>
|
||||
<div>
|
||||
<div className={styles.header}>Javascript events</div>
|
||||
<Button id="manual-button" variant="action" onClick={handleClick}>
|
||||
Run script
|
||||
@ -141,14 +141,12 @@ export function TestConsole() {
|
||||
<Button id="manual-button" variant="action" onClick={handleIdentifyClick}>
|
||||
Run identify
|
||||
</Button>
|
||||
</Column>
|
||||
</Row>
|
||||
<Row>
|
||||
<Column>
|
||||
<WebsiteChart websiteId={website.id} />
|
||||
<EventsChart websiteId={website.id} />
|
||||
</Column>
|
||||
</Row>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<WebsiteChart websiteId={website.id} />
|
||||
<EventsChart websiteId={website.id} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Page>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { GridRow, GridColumn } from 'components/layout/Grid';
|
||||
import { Grid, GridRow } from 'components/layout/Grid';
|
||||
import PagesTable from 'components/metrics/PagesTable';
|
||||
import ReferrersTable from 'components/metrics/ReferrersTable';
|
||||
import BrowsersTable from 'components/metrics/BrowsersTable';
|
||||
@ -18,42 +18,24 @@ export default function WebsiteTableView({ websiteId }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<GridRow>
|
||||
<GridColumn variant="two">
|
||||
<PagesTable {...tableProps} />
|
||||
</GridColumn>
|
||||
<GridColumn variant="two">
|
||||
<ReferrersTable {...tableProps} />
|
||||
</GridColumn>
|
||||
<Grid>
|
||||
<GridRow columns="two">
|
||||
<PagesTable {...tableProps} />
|
||||
<ReferrersTable {...tableProps} />
|
||||
</GridRow>
|
||||
<GridRow>
|
||||
<GridColumn variant="three">
|
||||
<BrowsersTable {...tableProps} />
|
||||
</GridColumn>
|
||||
<GridColumn variant="three">
|
||||
<OSTable {...tableProps} />
|
||||
</GridColumn>
|
||||
<GridColumn variant="three">
|
||||
<DevicesTable {...tableProps} />
|
||||
</GridColumn>
|
||||
<GridRow columns="three">
|
||||
<BrowsersTable {...tableProps} />
|
||||
<OSTable {...tableProps} />
|
||||
<DevicesTable {...tableProps} />
|
||||
</GridRow>
|
||||
<GridRow>
|
||||
<GridColumn xs={12} sm={12} md={12} defaultSize={8}>
|
||||
<WorldMap data={countryData} />
|
||||
</GridColumn>
|
||||
<GridColumn xs={12} sm={12} md={12} defaultSize={4}>
|
||||
<CountriesTable {...tableProps} onDataLoad={setCountryData} />
|
||||
</GridColumn>
|
||||
<GridRow columns="two-one">
|
||||
<WorldMap data={countryData} />
|
||||
<CountriesTable {...tableProps} onDataLoad={setCountryData} />
|
||||
</GridRow>
|
||||
<GridRow>
|
||||
<GridColumn xs={12} sm={12} md={12} lg={4} defaultSize={4}>
|
||||
<EventsTable {...tableProps} />
|
||||
</GridColumn>
|
||||
<GridColumn xs={12} sm={12} md={12} lg={8} defaultSize={8}>
|
||||
<EventsChart websiteId={websiteId} />
|
||||
</GridColumn>
|
||||
<GridRow columns="one-two">
|
||||
<EventsTable {...tableProps} />
|
||||
<EventsChart websiteId={websiteId} />
|
||||
</GridRow>
|
||||
</>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
@ -1,35 +0,0 @@
|
||||
.col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.row {
|
||||
border-top: 1px solid var(--base300);
|
||||
min-height: 430px;
|
||||
}
|
||||
|
||||
.row > .col {
|
||||
border-left: 1px solid var(--base300);
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.row > .col:first-child {
|
||||
border-left: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.row > .col:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 992px) {
|
||||
.row {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.row > .col {
|
||||
border-top: 1px solid var(--base300);
|
||||
border-left: 0;
|
||||
padding: 20px 0;
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
import classNames from 'classnames';
|
||||
import { Row, Column, Text, Button, Icon } from 'react-basics';
|
||||
import { Text, Button, Icon } from 'react-basics';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import Favicon from 'components/common/Favicon';
|
||||
@ -39,17 +39,19 @@ export function WebsiteHeader({ websiteId, showLinks = true, children }) {
|
||||
];
|
||||
|
||||
return (
|
||||
<Row className={styles.header} justifyContent="center">
|
||||
<Column className={styles.title} variant="two">
|
||||
<div className={styles.header}>
|
||||
<div className={styles.title}>
|
||||
<Favicon domain={domain} />
|
||||
<Text>{name}</Text>
|
||||
<ActiveUsers websiteId={websiteId} />
|
||||
</Column>
|
||||
<Column className={styles.actions} variant="two">
|
||||
</div>
|
||||
<div className={styles.actions}>
|
||||
{showLinks && (
|
||||
<div className={styles.links}>
|
||||
{links.map(({ label, icon, path }) => {
|
||||
const selected = path ? pathname.endsWith(path) : pathname === '/websites/[id]';
|
||||
const selected = path
|
||||
? pathname.endsWith(path)
|
||||
: pathname.match(/^\/websites\/[\w-]+$/);
|
||||
|
||||
return (
|
||||
<Link key={label} href={`/websites/${websiteId}${path}`} shallow={true}>
|
||||
@ -68,8 +70,8 @@ export function WebsiteHeader({ websiteId, showLinks = true, children }) {
|
||||
</div>
|
||||
)}
|
||||
{children}
|
||||
</Column>
|
||||
</Row>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr max-content;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@ -35,6 +35,10 @@
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.header {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.links {
|
||||
justify-content: space-evenly;
|
||||
flex: 1;
|
||||
@ -49,7 +53,7 @@
|
||||
|
||||
.icon,
|
||||
.icon svg {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import MetricsBar from 'components/metrics/MetricsBar';
|
||||
import FilterSelectForm from '../../reports/FilterSelectForm';
|
||||
import PopupForm from '../../reports/PopupForm';
|
||||
import { formatShortTime } from 'lib/format';
|
||||
import { Button, Column, Icon, Icons, Popup, PopupTrigger, Row } from 'react-basics';
|
||||
import { Button, Icon, Icons, Popup, PopupTrigger } from 'react-basics';
|
||||
import styles from './WebsiteMetricsBar.module.css';
|
||||
|
||||
export function WebsiteMetricsBar({ websiteId, showFilter = true, sticky }) {
|
||||
@ -98,72 +98,64 @@ export function WebsiteMetricsBar({ websiteId, showFilter = true, sticky }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<Row
|
||||
<div
|
||||
ref={ref}
|
||||
className={classNames(styles.container, {
|
||||
[styles.sticky]: sticky,
|
||||
[styles.isSticky]: isSticky,
|
||||
})}
|
||||
>
|
||||
<Column defaultSize={12} xl={8}>
|
||||
<MetricsBar isLoading={isLoading} isFetched={isFetched} error={error}>
|
||||
{!error && isFetched && (
|
||||
<>
|
||||
<MetricCard
|
||||
className={styles.card}
|
||||
label={formatMessage(labels.views)}
|
||||
value={pageviews.value}
|
||||
change={pageviews.change}
|
||||
/>
|
||||
<MetricCard
|
||||
className={styles.card}
|
||||
label={formatMessage(labels.visitors)}
|
||||
value={uniques.value}
|
||||
change={uniques.change}
|
||||
/>
|
||||
<MetricCard
|
||||
className={styles.card}
|
||||
label={formatMessage(labels.bounceRate)}
|
||||
value={uniques.value ? (num / uniques.value) * 100 : 0}
|
||||
change={
|
||||
uniques.value && uniques.change
|
||||
? (num / uniques.value) * 100 -
|
||||
(Math.min(diffs.uniques, diffs.bounces) / diffs.uniques) * 100 || 0
|
||||
: 0
|
||||
}
|
||||
format={n => Number(n).toFixed(0) + '%'}
|
||||
reverseColors
|
||||
/>
|
||||
<MetricCard
|
||||
className={styles.card}
|
||||
label={formatMessage(labels.averageVisitTime)}
|
||||
value={
|
||||
totaltime.value && pageviews.value
|
||||
? totaltime.value / (pageviews.value - bounces.value)
|
||||
: 0
|
||||
}
|
||||
change={
|
||||
totaltime.value && pageviews.value
|
||||
? (diffs.totaltime / (diffs.pageviews - diffs.bounces) -
|
||||
totaltime.value / (pageviews.value - bounces.value)) *
|
||||
-1 || 0
|
||||
: 0
|
||||
}
|
||||
format={n =>
|
||||
`${n < 0 ? '-' : ''}${formatShortTime(Math.abs(~~n), ['m', 's'], ' ')}`
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</MetricsBar>
|
||||
</Column>
|
||||
<Column defaultSize={12} xl={4}>
|
||||
<div className={styles.actions}>
|
||||
{showFilter && <WebsiteFilterButton />}
|
||||
<WebsiteDateFilter websiteId={websiteId} />
|
||||
</div>
|
||||
</Column>
|
||||
</Row>
|
||||
<MetricsBar isLoading={isLoading} isFetched={isFetched} error={error}>
|
||||
{pageviews && uniques && (
|
||||
<>
|
||||
<MetricCard
|
||||
label={formatMessage(labels.views)}
|
||||
value={pageviews.value}
|
||||
change={pageviews.change}
|
||||
/>
|
||||
<MetricCard
|
||||
label={formatMessage(labels.visitors)}
|
||||
value={uniques.value}
|
||||
change={uniques.change}
|
||||
/>
|
||||
<MetricCard
|
||||
className={styles.card}
|
||||
label={formatMessage(labels.bounceRate)}
|
||||
value={uniques.value ? (num / uniques.value) * 100 : 0}
|
||||
change={
|
||||
uniques.value && uniques.change
|
||||
? (num / uniques.value) * 100 -
|
||||
(Math.min(diffs.uniques, diffs.bounces) / diffs.uniques) * 100 || 0
|
||||
: 0
|
||||
}
|
||||
format={n => Number(n).toFixed(0) + '%'}
|
||||
reverseColors
|
||||
/>
|
||||
<MetricCard
|
||||
className={styles.card}
|
||||
label={formatMessage(labels.averageVisitTime)}
|
||||
value={
|
||||
totaltime.value && pageviews.value
|
||||
? totaltime.value / (pageviews.value - bounces.value)
|
||||
: 0
|
||||
}
|
||||
change={
|
||||
totaltime.value && pageviews.value
|
||||
? (diffs.totaltime / (diffs.pageviews - diffs.bounces) -
|
||||
totaltime.value / (pageviews.value - bounces.value)) *
|
||||
-1 || 0
|
||||
: 0
|
||||
}
|
||||
format={n => `${n < 0 ? '-' : ''}${formatShortTime(Math.abs(~~n), ['m', 's'], ' ')}`}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</MetricsBar>
|
||||
<div className={styles.actions}>
|
||||
{showFilter && <WebsiteFilterButton />}
|
||||
<WebsiteDateFilter websiteId={websiteId} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
.container {
|
||||
display: flex;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr max-content;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 0;
|
||||
min-height: 90px;
|
||||
margin-bottom: 20px;
|
||||
background: var(--base50);
|
||||
z-index: var(--z-index-above);
|
||||
min-height: 120px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
@ -18,8 +18,12 @@
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1200px) {
|
||||
.container {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin-top: 40px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,6 +34,7 @@
|
||||
}
|
||||
|
||||
.isSticky {
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid var(--base300);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { Column, Row } from 'react-basics';
|
||||
import { useApi, useDateRange } from 'components/hooks';
|
||||
import MetricCard from 'components/metrics/MetricCard';
|
||||
import useMessages from 'components/hooks/useMessages';
|
||||
@ -23,36 +22,16 @@ export function EventDataMetricsBar({ websiteId }) {
|
||||
);
|
||||
|
||||
return (
|
||||
<Row className={styles.row}>
|
||||
<Column defaultSize={12} xl={8}>
|
||||
<MetricsBar isLoading={isLoading} isFetched={isFetched} error={error}>
|
||||
{!error && isFetched && (
|
||||
<>
|
||||
<MetricCard
|
||||
className={styles.card}
|
||||
label={formatMessage(labels.events)}
|
||||
value={data?.events}
|
||||
/>
|
||||
<MetricCard
|
||||
className={styles.card}
|
||||
label={formatMessage(labels.fields)}
|
||||
value={data?.fields}
|
||||
/>
|
||||
<MetricCard
|
||||
className={styles.card}
|
||||
label={formatMessage(labels.totalRecords)}
|
||||
value={data?.records}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</MetricsBar>
|
||||
</Column>
|
||||
<Column defaultSize={12} xl={4}>
|
||||
<div className={styles.actions}>
|
||||
<WebsiteDateFilter websiteId={websiteId} />
|
||||
</div>
|
||||
</Column>
|
||||
</Row>
|
||||
<div className={styles.container}>
|
||||
<MetricsBar isLoading={isLoading} isFetched={isFetched} error={error}>
|
||||
<MetricCard label={formatMessage(labels.events)} value={data?.events} />
|
||||
<MetricCard label={formatMessage(labels.fields)} value={data?.fields} />
|
||||
<MetricCard label={formatMessage(labels.totalRecords)} value={data?.records} />
|
||||
</MetricsBar>
|
||||
<div className={styles.actions}>
|
||||
<WebsiteDateFilter websiteId={websiteId} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
.container {
|
||||
display: flex;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 0;
|
||||
@ -9,12 +10,6 @@
|
||||
z-index: var(--z-index-above);
|
||||
}
|
||||
|
||||
.metrics {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@ -23,24 +18,9 @@
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.bar {
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
min-height: 110px;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.card {
|
||||
justify-self: flex-start;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 992px) {
|
||||
.card {
|
||||
flex-basis: calc(50% - 20px);
|
||||
.container {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.row {
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
import { useMemo, useState, useEffect } from 'react';
|
||||
import { subMinutes, startOfMinute } from 'date-fns';
|
||||
import firstBy from 'thenby';
|
||||
import { GridRow, GridColumn } from 'components/layout/Grid';
|
||||
import { Grid, GridRow } from 'components/layout/Grid';
|
||||
import Page from 'components/layout/Page';
|
||||
import RealtimeChart from 'components/metrics/RealtimeChart';
|
||||
import WorldMap from 'components/common/WorldMap';
|
||||
@ -99,22 +99,16 @@ export function Realtime({ websiteId }) {
|
||||
<WebsiteHeader websiteId={websiteId} />
|
||||
<RealtimeHeader websiteId={websiteId} data={currentData} />
|
||||
<RealtimeChart className={styles.chart} data={realtimeData} unit="minute" />
|
||||
<GridRow>
|
||||
<GridColumn xs={12} sm={12} md={12} lg={4} xl={4}>
|
||||
<Grid>
|
||||
<GridRow columns="one-two">
|
||||
<RealtimeUrls websiteId={websiteId} websiteDomain={website?.domain} data={realtimeData} />
|
||||
</GridColumn>
|
||||
<GridColumn xs={12} sm={12} md={12} lg={8} xl={8}>
|
||||
<RealtimeLog websiteId={websiteId} websiteDomain={website?.domain} data={realtimeData} />
|
||||
</GridColumn>
|
||||
</GridRow>
|
||||
<GridRow>
|
||||
<GridColumn xs={12} lg={4}>
|
||||
</GridRow>
|
||||
<GridRow columns="one-two">
|
||||
<RealtimeCountries data={realtimeData?.countries} />
|
||||
</GridColumn>
|
||||
<GridColumn xs={12} lg={8}>
|
||||
<WorldMap data={realtimeData?.countries} />
|
||||
</GridColumn>
|
||||
</GridRow>
|
||||
</GridRow>
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,117 +0,0 @@
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import { subMinutes, startOfMinute } from 'date-fns';
|
||||
import firstBy from 'thenby';
|
||||
import { GridRow, GridColumn } from 'components/layout/Grid';
|
||||
import Page from 'components/layout/Page';
|
||||
import RealtimeChart from 'components/metrics/RealtimeChart';
|
||||
import WorldMap from 'components/common/WorldMap';
|
||||
import RealtimeLog from './RealtimeLog';
|
||||
import RealtimeHeader from './RealtimeHeader';
|
||||
import RealtimeUrls from './RealtimeUrls';
|
||||
import RealtimeCountries from './RealtimeCountries';
|
||||
import WebsiteHeader from '../WebsiteHeader';
|
||||
import useApi from 'components/hooks/useApi';
|
||||
import { percentFilter } from 'lib/filters';
|
||||
import { REALTIME_RANGE, REALTIME_INTERVAL } from 'lib/constants';
|
||||
import { useWebsite } from 'components/hooks';
|
||||
import styles from './Realtime.module.css';
|
||||
|
||||
function mergeData(state = [], data = [], time) {
|
||||
const ids = state.map(({ __id }) => __id);
|
||||
return state
|
||||
.concat(data.filter(({ __id }) => !ids.includes(__id)))
|
||||
.filter(({ timestamp }) => timestamp >= time);
|
||||
}
|
||||
|
||||
export function RealtimePage({ websiteId }) {
|
||||
const [currentData, setCurrentData] = useState();
|
||||
const { get, useQuery } = useApi();
|
||||
const { data: website } = useWebsite(websiteId);
|
||||
const { data, isLoading, error } = useQuery(
|
||||
['realtime', websiteId],
|
||||
() => get(`/realtime/${websiteId}`, { startAt: currentData?.timestamp || 0 }),
|
||||
{
|
||||
enabled: !!(websiteId && website),
|
||||
refetchInterval: REALTIME_INTERVAL,
|
||||
cache: false,
|
||||
},
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
const date = subMinutes(startOfMinute(new Date()), REALTIME_RANGE);
|
||||
const time = date.getTime();
|
||||
|
||||
setCurrentData(state => ({
|
||||
pageviews: mergeData(state?.pageviews, data.pageviews, time),
|
||||
sessions: mergeData(state?.sessions, data.sessions, time),
|
||||
events: mergeData(state?.events, data.events, time),
|
||||
timestamp: data.timestamp,
|
||||
}));
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
const realtimeData = useMemo(() => {
|
||||
if (!currentData) {
|
||||
return { pageviews: [], sessions: [], events: [], countries: [], visitors: [] };
|
||||
}
|
||||
|
||||
currentData.countries = percentFilter(
|
||||
currentData.sessions
|
||||
.reduce((arr, data) => {
|
||||
if (!arr.find(({ id }) => id === data.id)) {
|
||||
return arr.concat(data);
|
||||
}
|
||||
return arr;
|
||||
}, [])
|
||||
.reduce((arr, { country }) => {
|
||||
if (country) {
|
||||
const row = arr.find(({ x }) => x === country);
|
||||
|
||||
if (!row) {
|
||||
arr.push({ x: country, y: 1 });
|
||||
} else {
|
||||
row.y += 1;
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}, [])
|
||||
.sort(firstBy('y', -1)),
|
||||
);
|
||||
|
||||
currentData.visitors = currentData.sessions.reduce((arr, val) => {
|
||||
if (!arr.find(({ id }) => id === val.id)) {
|
||||
return arr.concat(val);
|
||||
}
|
||||
return arr;
|
||||
}, []);
|
||||
|
||||
return currentData;
|
||||
}, [currentData]);
|
||||
|
||||
return (
|
||||
<Page loading={isLoading} error={error}>
|
||||
<WebsiteHeader websiteId={websiteId} />
|
||||
<RealtimeHeader websiteId={websiteId} data={currentData} />
|
||||
<RealtimeChart className={styles.chart} data={realtimeData} unit="minute" />
|
||||
<GridRow>
|
||||
<GridColumn xs={12} sm={12} md={12} lg={4} xl={4}>
|
||||
<RealtimeUrls websiteId={websiteId} websiteDomain={website?.domain} data={realtimeData} />
|
||||
</GridColumn>
|
||||
<GridColumn xs={12} sm={12} md={12} lg={8} xl={8}>
|
||||
<RealtimeLog websiteId={websiteId} websiteDomain={website?.domain} data={realtimeData} />
|
||||
</GridColumn>
|
||||
</GridRow>
|
||||
<GridRow>
|
||||
<GridColumn xs={12} lg={4}>
|
||||
<RealtimeCountries data={realtimeData?.countries} />
|
||||
</GridColumn>
|
||||
<GridColumn xs={12} lg={8}>
|
||||
<WorldMap data={realtimeData?.countries} />
|
||||
</GridColumn>
|
||||
</GridRow>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
||||
export default RealtimePage;
|
@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
import { useEffect, useCallback, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { Button, Row, Column } from 'react-basics';
|
||||
import { Button } from 'react-basics';
|
||||
import { setItem } from 'next-basics';
|
||||
import useStore, { checkVersion } from 'store/version';
|
||||
import { REPO_URL, VERSION_CHECK } from 'lib/constants';
|
||||
@ -47,17 +47,17 @@ export function UpdateNotice({ user, config }) {
|
||||
}
|
||||
|
||||
return createPortal(
|
||||
<Row className={styles.notice}>
|
||||
<Column variant="two" className={styles.message}>
|
||||
<div className={styles.notice}>
|
||||
<div className={styles.message}>
|
||||
{formatMessage(messages.newVersionAvailable, { version: `v${latest}` })}
|
||||
</Column>
|
||||
<Column className={styles.buttons}>
|
||||
</div>
|
||||
<div className={styles.buttons}>
|
||||
<Button variant="primary" onClick={handleViewClick}>
|
||||
{formatMessage(labels.viewDetails)}
|
||||
</Button>
|
||||
<Button onClick={handleDismissClick}>{formatMessage(labels.dismiss)}</Button>
|
||||
</Column>
|
||||
</Row>,
|
||||
</div>
|
||||
</div>,
|
||||
document.body,
|
||||
);
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import useDateRange from 'components/hooks/useDateRange';
|
||||
import { isAfter } from 'date-fns';
|
||||
import { incrementDateRange } from 'lib/date';
|
||||
import { Button, Flexbox, Icon, Icons } from 'react-basics';
|
||||
import { Button, Icon, Icons } from 'react-basics';
|
||||
import DateFilter from './DateFilter';
|
||||
import styles from './WebsiteDateFilter.module.css';
|
||||
|
||||
@ -22,9 +22,9 @@ export function WebsiteDateFilter({ websiteId }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<Flexbox justifyContent="center" gap={10}>
|
||||
<div className={styles.container}>
|
||||
{value !== 'all' && selectedUnit && (
|
||||
<Flexbox justifyContent="center" className={styles.buttons}>
|
||||
<div className={styles.buttons}>
|
||||
<Button onClick={() => handleIncrement(1)}>
|
||||
<Icon rotate={90}>
|
||||
<Icons.ChevronDown />
|
||||
@ -35,7 +35,7 @@ export function WebsiteDateFilter({ websiteId }) {
|
||||
<Icons.ChevronDown />
|
||||
</Icon>
|
||||
</Button>
|
||||
</Flexbox>
|
||||
</div>
|
||||
)}
|
||||
<DateFilter
|
||||
className={styles.dropdown}
|
||||
@ -46,7 +46,7 @@ export function WebsiteDateFilter({ websiteId }) {
|
||||
onChange={handleChange}
|
||||
showAllTime={true}
|
||||
/>
|
||||
</Flexbox>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,17 @@
|
||||
.container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.buttons button:first-child {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
|
@ -1,13 +1,18 @@
|
||||
import { Row, Column } from 'react-basics';
|
||||
import classNames from 'classnames';
|
||||
import styles from './Grid.module.css';
|
||||
import { mapChildren } from 'react-basics';
|
||||
|
||||
export function Grid({ className, ...otherProps }) {
|
||||
return <div {...otherProps} className={classNames(styles.grid, className)} />;
|
||||
}
|
||||
|
||||
export function GridRow(props) {
|
||||
const { className, ...otherProps } = props;
|
||||
return <Row {...otherProps} className={classNames(styles.row, className)} />;
|
||||
}
|
||||
|
||||
export function GridColumn(props) {
|
||||
const { className, ...otherProps } = props;
|
||||
return <Column {...otherProps} className={classNames(styles.col, className)} />;
|
||||
const { columns = 'two', className, children, ...otherProps } = props;
|
||||
return (
|
||||
<div {...otherProps} className={classNames(styles.row, className)}>
|
||||
{mapChildren(children, child => {
|
||||
return <div className={classNames(styles.col, { [styles[columns]]: true })}>{child}</div>;
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,27 +1,52 @@
|
||||
.col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20px;
|
||||
.grid {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
border-top: 1px solid var(--base300);
|
||||
min-height: 430px;
|
||||
}
|
||||
|
||||
.row > .col {
|
||||
.col {
|
||||
padding: 20px;
|
||||
min-height: 430px;
|
||||
border-inline-start: 1px solid var(--base300);
|
||||
}
|
||||
|
||||
.row > .col:first-child {
|
||||
.col:first-child {
|
||||
border-inline-start: 0;
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
|
||||
.row > .col:last-child {
|
||||
.col:last-child {
|
||||
padding-inline-end: 0;
|
||||
}
|
||||
|
||||
.col.two {
|
||||
grid-column: span 3;
|
||||
}
|
||||
|
||||
.col.three {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.col.two-one:first-child {
|
||||
grid-column: span 4;
|
||||
}
|
||||
|
||||
.col.two-one:last-child {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.col.one-two:first-child {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.col.one-two:last-child {
|
||||
grid-column: span 4;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 992px) {
|
||||
.row {
|
||||
border: 0;
|
||||
@ -33,4 +58,11 @@
|
||||
border-inline-end: 0;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.col.two,
|
||||
.col.three,
|
||||
.col.one-two,
|
||||
.col.two-one {
|
||||
grid-column: span 6 !important;
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@
|
||||
flex-direction: column;
|
||||
background: var(--base50);
|
||||
position: relative;
|
||||
height: 100%;
|
||||
max-width: 1320px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
|
@ -145,7 +145,7 @@ export function BarChart({
|
||||
}, [datasets, unit, theme, animationDuration, locale]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.container}>
|
||||
<div className={classNames(styles.chart, className)}>
|
||||
{loading && <Loading position="page" icon="dots" />}
|
||||
<canvas ref={canvas} />
|
||||
@ -156,7 +156,7 @@ export function BarChart({
|
||||
<div className={styles.tooltip}>{tooltip}</div>
|
||||
</HoverTooltip>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,7 @@
|
||||
.container {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.chart {
|
||||
position: relative;
|
||||
height: 400px;
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { useState } from 'react';
|
||||
import { Loading, cloneChildren } from 'react-basics';
|
||||
import ErrorMessage from 'components/common/ErrorMessage';
|
||||
import styles from './MetricsBar.module.css';
|
||||
import { formatLongNumber, formatNumber } from 'lib/format';
|
||||
import styles from './MetricsBar.module.css';
|
||||
|
||||
export function MetricsBar({ children, isLoading, isFetched, error }) {
|
||||
const [format, setFormat] = useState(true);
|
||||
@ -19,9 +19,12 @@ export function MetricsBar({ children, isLoading, isFetched, error }) {
|
||||
<div className={styles.bar} onClick={handleSetFormat}>
|
||||
{isLoading && !isFetched && <Loading icon="dots" />}
|
||||
{error && <ErrorMessage />}
|
||||
{cloneChildren(children, child => {
|
||||
return { format: child.props.format || formatFunc };
|
||||
})}
|
||||
{!isLoading &&
|
||||
!error &&
|
||||
isFetched &&
|
||||
cloneChildren(children, child => {
|
||||
return { format: child.props.format || formatFunc };
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,18 +1,11 @@
|
||||
.bar {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
cursor: pointer;
|
||||
min-height: 110px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(140px, max-content));
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.card {
|
||||
justify-self: flex-start;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 992px) {
|
||||
.card {
|
||||
flex-basis: calc(50% - 20px);
|
||||
@media screen and (max-width: 768px) {
|
||||
.bar {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
@ -52,6 +52,8 @@ main {
|
||||
|
||||
svg {
|
||||
shape-rendering: geometricPrecision;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
#__next {
|
||||
|
Loading…
Reference in New Issue
Block a user