DropDown component. Renamed files.

This commit is contained in:
Mike Cao 2020-07-30 20:11:43 -07:00
parent ff4492ffb5
commit 9f112c8cc9
16 changed files with 149 additions and 48 deletions

View File

@ -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
View 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>
);
}

View 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;
}

View File

@ -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,
}),

View File

@ -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>
);
}

View File

@ -6,6 +6,7 @@
opacity: 0;
position: absolute;
pointer-events: none;
z-index: 2;
}
.content {

View File

@ -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]}

View File

@ -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;
}

View File

@ -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>
);
}

View File

@ -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>
);

View File

@ -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,
};
}
}

View File

@ -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(
`

View File

@ -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;
}, {});

View File

@ -37,3 +37,9 @@ form label {
form input {
margin-right: 10px;
}
select {
padding: 4px 8px;
border: 1px solid #b3b3b3;
border-radius: 4px;
}