mirror of
https://github.com/kremalicious/umami.git
synced 2024-11-25 11:28:50 +01:00
Refactor components and styles.
This commit is contained in:
parent
c5599f1e20
commit
a2db27894f
@ -66,16 +66,16 @@ export default function PageviewsChart({
|
||||
label: 'unique visitors',
|
||||
data: data.uniques,
|
||||
lineTension: 0,
|
||||
backgroundColor: 'rgb(146, 86, 217, 0.4)',
|
||||
borderColor: 'rgb(122, 66, 191, 0.4)',
|
||||
backgroundColor: 'rgb(38, 128, 235, 0.4)',
|
||||
borderColor: 'rgb(13, 102, 208, 0.4)',
|
||||
borderWidth: 1,
|
||||
},
|
||||
{
|
||||
label: 'page views',
|
||||
data: data.pageviews,
|
||||
lineTension: 0,
|
||||
backgroundColor: 'rgb(38, 128, 235, 0.4)',
|
||||
borderColor: 'rgb(13, 102, 208, 0.4)',
|
||||
backgroundColor: 'rgb(38, 128, 235, 0.2)',
|
||||
borderColor: 'rgb(13, 102, 208, 0.2)',
|
||||
borderWidth: 1,
|
||||
},
|
||||
],
|
||||
@ -165,7 +165,9 @@ const Tooltip = ({ title, value, label, labelColor }) => (
|
||||
<div className={styles.content}>
|
||||
<div className={styles.title}>{title}</div>
|
||||
<div className={styles.metric}>
|
||||
<div className={styles.dot} style={{ backgroundColor: labelColor }} />
|
||||
<div className={styles.dot}>
|
||||
<div className={styles.color} style={{ backgroundColor: labelColor }} />
|
||||
</div>
|
||||
{value} {label}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -30,9 +30,14 @@
|
||||
}
|
||||
|
||||
.dot {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius: 100%;
|
||||
margin-right: 8px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.color {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 100%;
|
||||
border: 1px solid #b3b3b3;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import { useSpring, animated } from 'react-spring';
|
||||
import { useSpring, animated, config } from 'react-spring';
|
||||
import classNames from 'classnames';
|
||||
import CheckVisible from './CheckVisible';
|
||||
import { get } from 'lib/web';
|
||||
import { percentFilter } from 'lib/filters';
|
||||
import styles from './RankingsChart.module.css';
|
||||
@ -14,7 +15,6 @@ export default function RankingsChart({
|
||||
heading,
|
||||
className,
|
||||
dataFilter,
|
||||
animate = true,
|
||||
onDataLoad = () => {},
|
||||
}) {
|
||||
const [data, setData] = useState();
|
||||
@ -50,43 +50,42 @@ export default function RankingsChart({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classNames(styles.container, className)}>
|
||||
<div className={styles.header}>
|
||||
<div className={styles.title}>{title}</div>
|
||||
<div className={styles.heading}>{heading}</div>
|
||||
</div>
|
||||
{rankings.map(({ x, y, z }) =>
|
||||
animate ? (
|
||||
<AnimatedRow key={x} label={x} value={y} percent={z} />
|
||||
) : (
|
||||
<Row key={x} label={x} value={y} percent={z} />
|
||||
),
|
||||
<CheckVisible>
|
||||
{visible => (
|
||||
<div className={classNames(styles.container, className)}>
|
||||
<div className={styles.header}>
|
||||
<div className={styles.title}>{title}</div>
|
||||
<div className={styles.heading}>{heading}</div>
|
||||
</div>
|
||||
{rankings.map(({ x, y, z }) => (
|
||||
<Row key={x} label={x} value={y} percent={z} animate={visible} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CheckVisible>
|
||||
);
|
||||
}
|
||||
|
||||
const Row = ({ label, value, percent }) => (
|
||||
<div className={styles.row}>
|
||||
<div className={styles.label}>{label}</div>
|
||||
<div className={styles.value}>{value.toFixed(0)}</div>
|
||||
<div className={styles.percent}>
|
||||
<div>{`${percent.toFixed(0)}%`}</div>
|
||||
<div className={styles.bar} style={{ width: percent }} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const AnimatedRow = ({ label, value, percent }) => {
|
||||
const props = useSpring({ width: percent, y: value, from: { width: 0, y: 0 } });
|
||||
const Row = ({ label, value, percent, animate }) => {
|
||||
const props = useSpring({
|
||||
width: percent,
|
||||
y: value,
|
||||
from: { width: 0, y: 0 },
|
||||
config: animate ? config.default : { duration: 0 },
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={styles.row}>
|
||||
<div className={styles.label}>{label}</div>
|
||||
<animated.div className={styles.value}>{props.y.interpolate(n => n.toFixed(0))}</animated.div>
|
||||
<div className={styles.percent}>
|
||||
<animated.div>{props.width.interpolate(n => `${n.toFixed(0)}%`)}</animated.div>
|
||||
<animated.div className={styles.bar} style={{ width: props.width }} />
|
||||
<animated.div
|
||||
className={styles.bar}
|
||||
style={{ width: props.width.interpolate(n => `${n}%`) }}
|
||||
/>
|
||||
<animated.span className={styles.percentValue}>
|
||||
{props.width.interpolate(n => `${n.toFixed(0)}%`)}
|
||||
</animated.span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -56,11 +56,12 @@
|
||||
}
|
||||
|
||||
.percent {
|
||||
position: relative;
|
||||
width: 50px;
|
||||
color: #6e6e6e;
|
||||
position: relative;
|
||||
border-left: 1px solid #8e8e8e;
|
||||
padding-left: 10px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.bar {
|
||||
|
@ -1,26 +1,26 @@
|
||||
import React, { useState, useEffect, useMemo, useRef } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import PageviewsChart from './PageviewsChart';
|
||||
import { get } from 'lib/web';
|
||||
import { getDateArray, getDateRange, getTimezone } from 'lib/date';
|
||||
import CheckVisible from './CheckVisible';
|
||||
import MetricsBar from './MetricsBar';
|
||||
import QuickButtons from './QuickButtons';
|
||||
import styles from './WebsiteChart.module.css';
|
||||
import DateFilter from './DateFilter';
|
||||
import useSticky from './hooks/useSticky';
|
||||
import { get } from 'lib/web';
|
||||
import { getDateArray, getDateRange, getTimezone } from 'lib/date';
|
||||
import styles from './WebsiteChart.module.css';
|
||||
|
||||
export default function WebsiteChart({
|
||||
websiteId,
|
||||
defaultDateRange = '7day',
|
||||
stickHeader = false,
|
||||
animate = true,
|
||||
onDateChange = () => {},
|
||||
}) {
|
||||
const [data, setData] = useState();
|
||||
const [dateRange, setDateRange] = useState(getDateRange(defaultDateRange));
|
||||
const { startDate, endDate, unit, value } = dateRange;
|
||||
const [ref, sticky] = useSticky(stickHeader);
|
||||
const width = useRef();
|
||||
const container = useRef();
|
||||
|
||||
const [pageviews, uniques] = useMemo(() => {
|
||||
if (data) {
|
||||
@ -52,16 +52,12 @@ export default function WebsiteChart({
|
||||
loadData();
|
||||
}, [websiteId, startDate, endDate, unit]);
|
||||
|
||||
useEffect(() => {
|
||||
width.current = document.querySelector('main').offsetWidth;
|
||||
}, [sticky]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div ref={container}>
|
||||
<div
|
||||
ref={ref}
|
||||
className={classNames(styles.header, 'row', { [styles.sticky]: sticky })}
|
||||
style={{ width: sticky ? width.current : 'auto' }}
|
||||
style={{ width: sticky ? container.current.clientWidth : 'auto' }}
|
||||
>
|
||||
<MetricsBar
|
||||
className="col-12 col-md-9 col-lg-10"
|
||||
@ -76,16 +72,20 @@ export default function WebsiteChart({
|
||||
/>
|
||||
</div>
|
||||
<div className="row">
|
||||
<PageviewsChart
|
||||
className="col"
|
||||
websiteId={websiteId}
|
||||
data={{ pageviews, uniques }}
|
||||
unit={unit}
|
||||
animationDuration={animate ? 300 : 0}
|
||||
>
|
||||
<QuickButtons value={value} onChange={handleDateChange} />
|
||||
</PageviewsChart>
|
||||
<CheckVisible>
|
||||
{visible => (
|
||||
<PageviewsChart
|
||||
className="col"
|
||||
websiteId={websiteId}
|
||||
data={{ pageviews, uniques }}
|
||||
unit={unit}
|
||||
animationDuration={visible ? 300 : 0}
|
||||
>
|
||||
<QuickButtons value={value} onChange={handleDateChange} />
|
||||
</PageviewsChart>
|
||||
)}
|
||||
</CheckVisible>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -13,7 +13,7 @@
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.sticky {
|
||||
@ -21,7 +21,6 @@
|
||||
top: 0;
|
||||
margin: auto;
|
||||
background: #fff;
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid #e1e1e1;
|
||||
z-index: 1;
|
||||
z-index: 2;
|
||||
}
|
||||
|
@ -37,103 +37,69 @@ export default function WebsiteDetails({ websiteId, defaultDateRange = '7day' })
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.container}>
|
||||
<div className="row">
|
||||
<div className={classNames(styles.chart, 'col')}>
|
||||
<h1>{data.label}</h1>
|
||||
<CheckVisible>
|
||||
{visible => (
|
||||
<WebsiteChart
|
||||
websiteId={data.website_id}
|
||||
onDateChange={handleDateChange}
|
||||
animate={visible}
|
||||
stickHeader
|
||||
/>
|
||||
)}
|
||||
</CheckVisible>
|
||||
<h2>{data.label}</h2>
|
||||
<WebsiteChart websiteId={websiteId} onDateChange={handleDateChange} stickHeader />
|
||||
</div>
|
||||
</div>
|
||||
<div className={classNames(styles.row, 'row')}>
|
||||
<div className={pageviewClasses}>
|
||||
<CheckVisible>
|
||||
{visible => (
|
||||
<RankingsChart
|
||||
title="Pages"
|
||||
type="url"
|
||||
heading="Views"
|
||||
websiteId={data.website_id}
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
dataFilter={urlFilter}
|
||||
animate={visible}
|
||||
/>
|
||||
)}
|
||||
</CheckVisible>
|
||||
<RankingsChart
|
||||
title="Pages"
|
||||
type="url"
|
||||
heading="Views"
|
||||
websiteId={websiteId}
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
dataFilter={urlFilter}
|
||||
/>
|
||||
</div>
|
||||
<div className={pageviewClasses}>
|
||||
<CheckVisible>
|
||||
{visible => (
|
||||
<RankingsChart
|
||||
title="Referrers"
|
||||
type="referrer"
|
||||
heading="Views"
|
||||
websiteId={data.website_id}
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
dataFilter={refFilter}
|
||||
animate={visible}
|
||||
/>
|
||||
)}
|
||||
</CheckVisible>
|
||||
<RankingsChart
|
||||
title="Referrers"
|
||||
type="referrer"
|
||||
heading="Views"
|
||||
websiteId={websiteId}
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
dataFilter={refFilter}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classNames(styles.row, 'row')}>
|
||||
<div className={sessionClasses}>
|
||||
<CheckVisible>
|
||||
{visible => (
|
||||
<RankingsChart
|
||||
title="Browsers"
|
||||
type="browser"
|
||||
heading="Visitors"
|
||||
websiteId={data.website_id}
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
dataFilter={browserFilter}
|
||||
animate={visible}
|
||||
/>
|
||||
)}
|
||||
</CheckVisible>
|
||||
<RankingsChart
|
||||
title="Browsers"
|
||||
type="browser"
|
||||
heading="Visitors"
|
||||
websiteId={websiteId}
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
dataFilter={browserFilter}
|
||||
/>
|
||||
</div>
|
||||
<div className={sessionClasses}>
|
||||
<CheckVisible>
|
||||
{visible => (
|
||||
<RankingsChart
|
||||
title="Operating system"
|
||||
type="os"
|
||||
heading="Visitors"
|
||||
websiteId={data.website_id}
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
animate={visible}
|
||||
/>
|
||||
)}
|
||||
</CheckVisible>
|
||||
<RankingsChart
|
||||
title="Operating system"
|
||||
type="os"
|
||||
heading="Visitors"
|
||||
websiteId={websiteId}
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
/>
|
||||
</div>
|
||||
<div className={sessionClasses}>
|
||||
<CheckVisible>
|
||||
{visible => (
|
||||
<RankingsChart
|
||||
title="Devices"
|
||||
type="screen"
|
||||
heading="Visitors"
|
||||
websiteId={data.website_id}
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
dataFilter={deviceFilter}
|
||||
animate={visible}
|
||||
/>
|
||||
)}
|
||||
</CheckVisible>
|
||||
<RankingsChart
|
||||
title="Devices"
|
||||
type="screen"
|
||||
heading="Visitors"
|
||||
websiteId={websiteId}
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
dataFilter={deviceFilter}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classNames(styles.row, 'row')}>
|
||||
@ -141,23 +107,18 @@ export default function WebsiteDetails({ websiteId, defaultDateRange = '7day' })
|
||||
<WorldMap data={countryData} />
|
||||
</div>
|
||||
<div className="col-12 col-md-12 col-lg-4">
|
||||
<CheckVisible>
|
||||
{visible => (
|
||||
<RankingsChart
|
||||
title="Countries"
|
||||
type="country"
|
||||
heading="Visitors"
|
||||
websiteId={data.website_id}
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
dataFilter={countryFilter}
|
||||
onDataLoad={data => setCountryData(data)}
|
||||
animate={visible}
|
||||
/>
|
||||
)}
|
||||
</CheckVisible>
|
||||
<RankingsChart
|
||||
title="Countries"
|
||||
type="country"
|
||||
heading="Visitors"
|
||||
websiteId={websiteId}
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
dataFilter={countryFilter}
|
||||
onDataLoad={data => setCountryData(data)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -18,10 +18,10 @@ export default function WebsiteList() {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
{data &&
|
||||
data.websites.map(({ website_id, website_uuid, label }) => (
|
||||
data.websites.map(({ website_id, label }) => (
|
||||
<div key={website_id}>
|
||||
<h2>
|
||||
<Link href={`/${website_uuid}`}>
|
||||
<Link href={`/website/${website_id}/${label}`}>
|
||||
<a>{label}</a>
|
||||
</Link>
|
||||
</h2>
|
||||
|
@ -1,3 +1,32 @@
|
||||
.container > div {
|
||||
padding-bottom: 30px;
|
||||
border-bottom: 1px solid #e1e1e1;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.container > div:last-child {
|
||||
border-bottom: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.container a {
|
||||
position: relative;
|
||||
color: #2c2c2c;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.container a:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -2px;
|
||||
width: 0;
|
||||
height: 2px;
|
||||
background: #2680eb;
|
||||
opacity: 0.5;
|
||||
transition: width 100ms;
|
||||
}
|
||||
|
||||
.container a:hover:before {
|
||||
width: 100%;
|
||||
transition: width 100ms;
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
.container {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background: #fff;
|
||||
}
|
||||
|
10
lib/db.js
10
lib/db.js
@ -39,11 +39,12 @@ export async function runQuery(query) {
|
||||
});
|
||||
}
|
||||
|
||||
export async function getWebsite(website_uuid) {
|
||||
export async function getWebsite({ website_id, website_uuid }) {
|
||||
return runQuery(
|
||||
prisma.website.findOne({
|
||||
where: {
|
||||
website_uuid,
|
||||
...(website_id && { website_id }),
|
||||
...(website_uuid && { website_uuid }),
|
||||
},
|
||||
}),
|
||||
);
|
||||
@ -77,11 +78,12 @@ export async function createSession(website_id, data) {
|
||||
);
|
||||
}
|
||||
|
||||
export async function getSession(session_uuid) {
|
||||
export async function getSession({ session_id, session_uuid }) {
|
||||
return runQuery(
|
||||
prisma.session.findOne({
|
||||
where: {
|
||||
session_uuid,
|
||||
...(session_id && { session_id }),
|
||||
...(session_uuid && { session_uuid }),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
@ -18,13 +18,13 @@ export default async req => {
|
||||
const country = await getCountry(req, ip);
|
||||
|
||||
if (website_uuid) {
|
||||
const website = await getWebsite(website_uuid);
|
||||
const website = await getWebsite({ website_uuid });
|
||||
|
||||
if (website) {
|
||||
const { website_id } = website;
|
||||
const session_uuid = uuid(website_id, hostname, ip, userAgent, os);
|
||||
|
||||
let session = await getSession(session_uuid);
|
||||
let session = await getSession({ session_uuid });
|
||||
|
||||
if (!session) {
|
||||
session = await createSession(website_id, {
|
||||
|
@ -6,7 +6,7 @@ export default async (req, res) => {
|
||||
|
||||
const { id } = req.query;
|
||||
|
||||
const website = await getWebsite(id);
|
||||
const website = await getWebsite({ website_id: +id });
|
||||
|
||||
return res.status(200).json(website);
|
||||
};
|
||||
|
@ -1,26 +1,13 @@
|
||||
import React from 'react';
|
||||
import Link from 'next/link';
|
||||
import { parse } from 'cookie';
|
||||
import Layout from 'components/Layout';
|
||||
import PageviewsChart from 'components/PageviewsChart';
|
||||
import { verifySecureToken } from 'lib/crypto';
|
||||
import { subDays, endOfDay } from 'date-fns';
|
||||
import WebsiteList from '../components/WebsiteList';
|
||||
|
||||
export default function HomePage({ username }) {
|
||||
return (
|
||||
<Layout>
|
||||
<WebsiteList />
|
||||
<div>
|
||||
<PageviewsChart
|
||||
websiteId={3}
|
||||
startDate={subDays(endOfDay(new Date()), 6)}
|
||||
endDate={endOfDay(new Date())}
|
||||
/>
|
||||
</div>
|
||||
<Link href="/logout">
|
||||
<a>Logout 🡒</a>
|
||||
</Link>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
@ -1,15 +1,19 @@
|
||||
import React from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import Layout from 'components/Layout';
|
||||
import WebsiteDetails from '../components/WebsiteDetails';
|
||||
import WebsiteDetails from '../../components/WebsiteDetails';
|
||||
|
||||
export default function DetailsPage() {
|
||||
const router = useRouter();
|
||||
const { id } = router.query;
|
||||
|
||||
if (!id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<WebsiteDetails websiteId={id} />
|
||||
<WebsiteDetails websiteId={+id[0]} />
|
||||
</Layout>
|
||||
);
|
||||
}
|
@ -9,6 +9,7 @@ body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
*,
|
||||
@ -24,8 +25,14 @@ body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
a,
|
||||
a:active,
|
||||
a:visited {
|
||||
color: #2680eb;
|
||||
}
|
||||
|
||||
header a {
|
||||
color: #000;
|
||||
color: #000 !important;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@ -44,6 +51,14 @@ select {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
main {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.row {
|
||||
margin-right: 0;
|
||||
margin-left: 0;
|
||||
|
Loading…
Reference in New Issue
Block a user