From 9f112c8cc96f8e693a75a2a9e666c221d9bcc879 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Thu, 30 Jul 2020 20:11:43 -0700 Subject: [PATCH] DropDown component. Renamed files. --- components/DateFilter.js | 28 +++++------ components/DropDown.js | 47 +++++++++++++++++++ components/Dropdown.module.css | 46 ++++++++++++++++++ .../{WebsiteSummary.js => MetricsBar.js} | 6 +-- ...mmary.module.css => MetricsBar.module.css} | 0 components/PageviewsChart.js | 3 +- components/PageviewsChart.module.css | 1 + components/QuickButtons.js | 9 ++-- components/QuickButtons.module.css | 8 ++++ .../{WebsiteStats.js => WebsiteChart.js} | 15 +++--- ...ats.module.css => WebsiteChart.module.css} | 0 components/WebsiteList.js | 4 +- lib/date.js | 14 +++--- lib/db.js | 2 +- .../website/[id]/{summary.js => metrics.js} | 8 ++-- styles/index.css | 6 +++ 16 files changed, 149 insertions(+), 48 deletions(-) create mode 100644 components/DropDown.js create mode 100644 components/Dropdown.module.css rename components/{WebsiteSummary.js => MetricsBar.js} (84%) rename components/{WebsiteSummary.module.css => MetricsBar.module.css} (100%) rename components/{WebsiteStats.js => WebsiteChart.js} (70%) rename components/{WebsiteStats.module.css => WebsiteChart.module.css} (100%) rename pages/api/website/[id]/{summary.js => metrics.js} (65%) diff --git a/components/DateFilter.js b/components/DateFilter.js index b2540c6a..be83e634 100644 --- a/components/DateFilter.js +++ b/components/DateFilter.js @@ -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 ( - - ); + return ; } diff --git a/components/DropDown.js b/components/DropDown.js new file mode 100644 index 00000000..b3b08fdb --- /dev/null +++ b/components/DropDown.js @@ -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 ( +
+
+ {options.find(e => e.value === value).label} +
+
+ {showMenu && ( +
+ {options.map(({ label, value }) => ( +
handleSelect(value, e)}> + {label} +
+ ))} +
+ )} +
+ ); +} diff --git a/components/Dropdown.module.css b/components/Dropdown.module.css new file mode 100644 index 00000000..7fe08fc1 --- /dev/null +++ b/components/Dropdown.module.css @@ -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; +} diff --git a/components/WebsiteSummary.js b/components/MetricsBar.js similarity index 84% rename from components/WebsiteSummary.js rename to components/MetricsBar.js index cee9f044..c6ca569e 100644 --- a/components/WebsiteSummary.js +++ b/components/MetricsBar.js @@ -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, }), diff --git a/components/WebsiteSummary.module.css b/components/MetricsBar.module.css similarity index 100% rename from components/WebsiteSummary.module.css rename to components/MetricsBar.module.css diff --git a/components/PageviewsChart.js b/components/PageviewsChart.js index 99fa529b..68d1cced 100644 --- a/components/PageviewsChart.js +++ b/components/PageviewsChart.js @@ -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 }) {
+ {children}
); } diff --git a/components/PageviewsChart.module.css b/components/PageviewsChart.module.css index e83e789a..491a1382 100644 --- a/components/PageviewsChart.module.css +++ b/components/PageviewsChart.module.css @@ -6,6 +6,7 @@ opacity: 0; position: absolute; pointer-events: none; + z-index: 2; } .content { diff --git a/components/QuickButtons.js b/components/QuickButtons.js index 7ac77b12..2ccac3e0 100644 --- a/components/QuickButtons.js +++ b/components/QuickButtons.js @@ -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 => (
handleClick(key)} > {options[key]} diff --git a/components/QuickButtons.module.css b/components/QuickButtons.module.css index f0398be2..d56f1876 100644 --- a/components/QuickButtons.module.css +++ b/components/QuickButtons.module.css @@ -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; } diff --git a/components/WebsiteStats.js b/components/WebsiteChart.js similarity index 70% rename from components/WebsiteStats.js rename to components/WebsiteChart.js index bf77a9bb..0aba3c9e 100644 --- a/components/WebsiteStats.js +++ b/components/WebsiteChart.js @@ -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 }) {
{title}
- - + +
- + + +
); } diff --git a/components/WebsiteStats.module.css b/components/WebsiteChart.module.css similarity index 100% rename from components/WebsiteStats.module.css rename to components/WebsiteChart.module.css diff --git a/components/WebsiteList.js b/components/WebsiteList.js index b6e3b5b8..916f3fe6 100644 --- a/components/WebsiteList.js +++ b/components/WebsiteList.js @@ -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() {
{data && data.websites.map(({ website_id, label }) => ( - + ))}
); diff --git a/lib/date.js b/lib/date.js index bd49a0f8..f89d33a1 100644 --- a/lib/date.js +++ b/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, }; } } diff --git a/lib/db.js b/lib/db.js index 102f100e..cd47cb92 100644 --- a/lib/db.js +++ b/lib/db.js @@ -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( ` diff --git a/pages/api/website/[id]/summary.js b/pages/api/website/[id]/metrics.js similarity index 65% rename from pages/api/website/[id]/summary.js rename to pages/api/website/[id]/metrics.js index c8b95599..234336b0 100644 --- a/pages/api/website/[id]/summary.js +++ b/pages/api/website/[id]/metrics.js @@ -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; }, {}); diff --git a/styles/index.css b/styles/index.css index 162cd9cf..28f7fcee 100644 --- a/styles/index.css +++ b/styles/index.css @@ -37,3 +37,9 @@ form label { form input { margin-right: 10px; } + +select { + padding: 4px 8px; + border: 1px solid #b3b3b3; + border-radius: 4px; +}