mirror of
https://github.com/kremalicious/umami.git
synced 2024-11-22 09:57:00 +01:00
DropDown component. Renamed files.
This commit is contained in:
parent
ff4492ffb5
commit
9f112c8cc9
@ -1,24 +1,18 @@
|
||||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
import { getDateRange } from 'lib/date';
|
||||
import DropDown from './DropDown';
|
||||
|
||||
const filterOptions = ['24hour', '7day', '30day', '60day', '90day'];
|
||||
const filterOptions = [
|
||||
{ label: 'Last 24 hours', value: '24hour' },
|
||||
{ label: 'Last 7 days', value: '7day' },
|
||||
{ label: 'Last 30 days', value: '30day' },
|
||||
{ label: 'Last 90 days', value: '90day' },
|
||||
];
|
||||
|
||||
export default function DateFilter({ onChange }) {
|
||||
const [selected, setSelected] = useState('7day');
|
||||
|
||||
function handleChange(e) {
|
||||
const value = e.target.value;
|
||||
setSelected(value);
|
||||
export default function DateFilter({ value, onChange }) {
|
||||
function handleChange(value) {
|
||||
onChange(getDateRange(value));
|
||||
}
|
||||
|
||||
return (
|
||||
<select value={selected} onChange={handleChange}>
|
||||
{filterOptions.map(option => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
);
|
||||
return <DropDown value={value} options={filterOptions} onChange={handleChange} />;
|
||||
}
|
||||
|
47
components/DropDown.js
Normal file
47
components/DropDown.js
Normal file
@ -0,0 +1,47 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import styles from './Dropdown.module.css';
|
||||
|
||||
export default function DropDown({ value, options = [], onChange }) {
|
||||
const [showMenu, setShowMenu] = useState(false);
|
||||
const ref = useRef();
|
||||
|
||||
function handleShowMenu() {
|
||||
setShowMenu(state => !state);
|
||||
}
|
||||
|
||||
function handleSelect(value) {
|
||||
onChange(value);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
function hideMenu(e) {
|
||||
if (ref.current && !ref.current.contains(e.target)) {
|
||||
setShowMenu(false);
|
||||
}
|
||||
}
|
||||
|
||||
document.body.addEventListener('click', hideMenu);
|
||||
|
||||
return () => {
|
||||
document.body.removeEventListener('click', hideMenu);
|
||||
};
|
||||
}, [ref]);
|
||||
|
||||
return (
|
||||
<div ref={ref} className={styles.dropdown} onClick={handleShowMenu}>
|
||||
<div className={styles.value}>
|
||||
{options.find(e => e.value === value).label}
|
||||
<div className={styles.caret} />
|
||||
</div>
|
||||
{showMenu && (
|
||||
<div className={styles.menu}>
|
||||
{options.map(({ label, value }) => (
|
||||
<div key={value} className={styles.option} onClick={e => handleSelect(value, e)}>
|
||||
{label}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
46
components/Dropdown.module.css
Normal file
46
components/Dropdown.module.css
Normal file
@ -0,0 +1,46 @@
|
||||
.dropdown {
|
||||
position: relative;
|
||||
font-size: 12px;
|
||||
min-width: 140px;
|
||||
}
|
||||
|
||||
.value {
|
||||
padding: 4px 24px 4px 8px;
|
||||
border: 1px solid #b3b3b3;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.menu {
|
||||
position: absolute;
|
||||
min-width: 100px;
|
||||
top: 100%;
|
||||
margin-top: 4px;
|
||||
border: 1px solid #b3b3b3;
|
||||
border-radius: 4px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.option {
|
||||
background: #fff;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.option:hover {
|
||||
background: #eaeaea;
|
||||
}
|
||||
|
||||
.caret {
|
||||
position: absolute;
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
border-right: 2px solid #8e8e8e;
|
||||
border-bottom: 2px solid #8e8e8e;
|
||||
transform: rotate(45deg);
|
||||
top: -4px;
|
||||
bottom: 0;
|
||||
right: 8px;
|
||||
margin: auto;
|
||||
}
|
@ -2,15 +2,15 @@ import React, { useState, useEffect } from 'react';
|
||||
import MetricCard from './MetricCard';
|
||||
import { get } from '../lib/web';
|
||||
import { formatShortTime } from 'lib/format';
|
||||
import styles from './WebsiteSummary.module.css';
|
||||
import styles from './MetricsBar.module.css';
|
||||
|
||||
export default function WebsiteSummary({ websiteId, startDate, endDate }) {
|
||||
export default function MetricsBar({ websiteId, startDate, endDate }) {
|
||||
const [data, setData] = useState({});
|
||||
const { pageviews, uniques, bounces, totaltime } = data;
|
||||
|
||||
async function loadData() {
|
||||
setData(
|
||||
await get(`/api/website/${websiteId}/summary`, {
|
||||
await get(`/api/website/${websiteId}/metrics`, {
|
||||
start_at: +startDate,
|
||||
end_at: +endDate,
|
||||
}),
|
@ -3,7 +3,7 @@ import ChartJS from 'chart.js';
|
||||
import { format } from 'date-fns';
|
||||
import styles from './PageviewsChart.module.css';
|
||||
|
||||
export default function PageviewsChart({ data, unit }) {
|
||||
export default function PageviewsChart({ data, unit, children }) {
|
||||
const canvas = useRef();
|
||||
const chart = useRef();
|
||||
const [tooltip, setTooltip] = useState({});
|
||||
@ -138,6 +138,7 @@ export default function PageviewsChart({ data, unit }) {
|
||||
<div className={styles.chart}>
|
||||
<canvas ref={canvas} width={960} height={400} />
|
||||
<Tootip {...tooltip} />
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.content {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { getDateRange } from 'lib/date';
|
||||
import styles from './QuickButtons.module.css';
|
||||
@ -9,11 +9,8 @@ const options = {
|
||||
'30day': '30d',
|
||||
};
|
||||
|
||||
export default function QuickButtons({ onChange }) {
|
||||
const [active, setActive] = useState('7day');
|
||||
|
||||
export default function QuickButtons({ value, onChange }) {
|
||||
function handleClick(value) {
|
||||
setActive(value);
|
||||
onChange(getDateRange(value));
|
||||
}
|
||||
|
||||
@ -22,7 +19,7 @@ export default function QuickButtons({ onChange }) {
|
||||
{Object.keys(options).map(key => (
|
||||
<div
|
||||
key={key}
|
||||
className={classNames(styles.button, { [styles.active]: active === key })}
|
||||
className={classNames(styles.button, { [styles.active]: value === key })}
|
||||
onClick={() => handleClick(key)}
|
||||
>
|
||||
{options[key]}
|
||||
|
@ -1,5 +1,9 @@
|
||||
.buttons {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.button {
|
||||
@ -11,6 +15,10 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.button:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background: #eaeaea;
|
||||
}
|
||||
|
@ -2,11 +2,12 @@ import React, { useState, useEffect, useMemo } from 'react';
|
||||
import PageviewsChart from './PageviewsChart';
|
||||
import { get } from 'lib/web';
|
||||
import { getDateArray, getDateRange, getTimezone } from 'lib/date';
|
||||
import WebsiteSummary from './WebsiteSummary';
|
||||
import MetricsBar from './MetricsBar';
|
||||
import QuickButtons from './QuickButtons';
|
||||
import styles from './WebsiteStats.module.css';
|
||||
import styles from './WebsiteChart.module.css';
|
||||
import DateFilter from './DateFilter';
|
||||
|
||||
export default function WebsiteStats({ title, websiteId }) {
|
||||
export default function WebsiteChart({ title, websiteId }) {
|
||||
const [data, setData] = useState();
|
||||
const [dateRange, setDateRange] = useState(getDateRange('7day'));
|
||||
const { startDate, endDate, unit } = dateRange;
|
||||
@ -44,10 +45,12 @@ export default function WebsiteStats({ title, websiteId }) {
|
||||
<div className={styles.container}>
|
||||
<div className={styles.title}>{title}</div>
|
||||
<div className={styles.header}>
|
||||
<WebsiteSummary websiteId={websiteId} startDate={startDate} endDate={endDate} />
|
||||
<QuickButtons onChange={handleDateChange} />
|
||||
<MetricsBar websiteId={websiteId} startDate={startDate} endDate={endDate} />
|
||||
<DateFilter value={dateRange.value} onChange={handleDateChange} />
|
||||
</div>
|
||||
<PageviewsChart data={{ pageviews, uniques }} unit={unit} />
|
||||
<PageviewsChart data={{ pageviews, uniques }} unit={unit}>
|
||||
<QuickButtons value={dateRange.value} onChange={handleDateChange} />
|
||||
</PageviewsChart>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { get } from 'lib/web';
|
||||
import WebsiteStats from './WebsiteStats';
|
||||
import WebsiteChart from './WebsiteChart';
|
||||
import DateFilter from './DateFilter';
|
||||
import styles from './WebsiteList.module.css';
|
||||
|
||||
@ -19,7 +19,7 @@ export default function WebsiteList() {
|
||||
<div className={styles.container}>
|
||||
{data &&
|
||||
data.websites.map(({ website_id, label }) => (
|
||||
<WebsiteStats key={website_id} title={label} websiteId={website_id} />
|
||||
<WebsiteChart key={website_id} title={label} websiteId={website_id} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
14
lib/date.js
14
lib/date.js
@ -2,13 +2,13 @@ import moment from 'moment-timezone';
|
||||
import {
|
||||
addMinutes,
|
||||
addHours,
|
||||
addDays,
|
||||
subHours,
|
||||
subDays,
|
||||
startOfHour,
|
||||
startOfDay,
|
||||
endOfHour,
|
||||
endOfDay,
|
||||
startOfHour,
|
||||
addDays,
|
||||
subDays,
|
||||
subHours,
|
||||
differenceInHours,
|
||||
differenceInDays,
|
||||
} from 'date-fns';
|
||||
@ -17,10 +17,6 @@ export function getTimezone() {
|
||||
return moment.tz.guess();
|
||||
}
|
||||
|
||||
export function getTimezonAbbr() {
|
||||
return moment.tz.zone(getTimezone()).abbr(new Date().getTimezoneOffset());
|
||||
}
|
||||
|
||||
export function getLocalTime(t) {
|
||||
return addMinutes(new Date(t), new Date().getTimezoneOffset());
|
||||
}
|
||||
@ -38,12 +34,14 @@ export function getDateRange(value) {
|
||||
startDate: subDays(day, num),
|
||||
endDate: day,
|
||||
unit,
|
||||
value,
|
||||
};
|
||||
case 'hour':
|
||||
return {
|
||||
startDate: subHours(hour, num),
|
||||
endDate: hour,
|
||||
unit,
|
||||
value,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -180,7 +180,7 @@ export async function getPageviewData(
|
||||
);
|
||||
}
|
||||
|
||||
export async function getSummary(website_id, start_at, end_at) {
|
||||
export async function getMetrics(website_id, start_at, end_at) {
|
||||
return runQuery(
|
||||
prisma.queryRaw(
|
||||
`
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { getSummary } from 'lib/db';
|
||||
import { getMetrics } from 'lib/db';
|
||||
import { useAuth } from 'lib/middleware';
|
||||
|
||||
export default async (req, res) => {
|
||||
@ -6,14 +6,14 @@ export default async (req, res) => {
|
||||
|
||||
const { id, start_at, end_at } = req.query;
|
||||
|
||||
const summary = await getSummary(
|
||||
const metrics = await getMetrics(
|
||||
+id,
|
||||
new Date(+start_at).toISOString(),
|
||||
new Date(+end_at).toISOString(),
|
||||
);
|
||||
|
||||
const stats = Object.keys(summary[0]).reduce((obj, key) => {
|
||||
obj[key] = +summary[0][key];
|
||||
const stats = Object.keys(metrics[0]).reduce((obj, key) => {
|
||||
obj[key] = +metrics[0][key];
|
||||
return obj;
|
||||
}, {});
|
||||
|
@ -37,3 +37,9 @@ form label {
|
||||
form input {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
select {
|
||||
padding: 4px 8px;
|
||||
border: 1px solid #b3b3b3;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user