diff --git a/assets/calendar-alt.svg b/assets/calendar-alt.svg new file mode 100644 index 00000000..230c4e66 --- /dev/null +++ b/assets/calendar-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/components/common/Button.js b/components/common/Button.js index d72fb662..b973b36e 100644 --- a/components/common/Button.js +++ b/components/common/Button.js @@ -29,6 +29,7 @@ export default function Button({ [styles.xsmall]: size === 'xsmall', [styles.action]: variant === 'action', [styles.danger]: variant === 'danger', + [styles.light]: variant === 'light', [styles.disabled]: disabled, })} disabled={disabled} diff --git a/components/common/Button.module.css b/components/common/Button.module.css index 99c7168f..faae656b 100644 --- a/components/common/Button.module.css +++ b/components/common/Button.module.css @@ -55,7 +55,16 @@ background: var(--red400); } +.light { + background: var(--gray50); +} + +.light:hover { + background: var(--gray75); +} + .button:disabled { + cursor: default; color: var(--gray500); background: var(--gray75); } @@ -67,3 +76,7 @@ .button:disabled:hover { background: var(--gray75); } + +.button.light:disabled { + background: var(--gray50); +} diff --git a/components/common/Calendar.js b/components/common/Calendar.js index 8ecf76a8..d9c281d3 100644 --- a/components/common/Calendar.js +++ b/components/common/Calendar.js @@ -160,7 +160,7 @@ const MonthSelector = ({ date, minDate, maxDate, locale, onSelect }) => { const start = startOfYear(date); const months = []; for (let i = 0; i < 12; i++) { - months.push(endOfMonth(addMonths(start, i))); + months.push(addMonths(start, i)); } function handleSelect(value) { @@ -173,7 +173,8 @@ const MonthSelector = ({ date, minDate, maxDate, locale, onSelect }) => { {chunk(months, 3).map((row, i) => ( {row.map((month, j) => { - const disabled = isBefore(month, minDate) || isAfter(month, maxDate); + const disabled = + isBefore(endOfMonth(month), minDate) || isAfter(startOfMonth(month), maxDate); return ( {
); diff --git a/components/common/Calendar.module.css b/components/common/Calendar.module.css index ccfde683..ee5581bd 100644 --- a/components/common/Calendar.module.css +++ b/components/common/Calendar.module.css @@ -3,6 +3,7 @@ flex-direction: column; font-size: var(--font-size-small); flex: 1; + min-height: 285px; } .calendar table { @@ -37,7 +38,7 @@ } .calendar td.disabled { - color: var(--gray300); + color: var(--gray400); background: var(--gray75); } @@ -47,7 +48,7 @@ } .calendar td.faded.disabled { - color: var(--gray200); + background: var(--gray100); } .header { diff --git a/components/common/DateFilter.js b/components/common/DateFilter.js index a7ac6372..c43cb860 100644 --- a/components/common/DateFilter.js +++ b/components/common/DateFilter.js @@ -7,20 +7,33 @@ import DatePickerForm from 'components/forms/DatePickerForm'; import useLocale from 'hooks/useLocale'; import { getDateRange } from 'lib/date'; import { dateFormat } from 'lib/lang'; +import Calendar from 'assets/calendar-alt.svg'; +import Icon from './Icon'; const filterOptions = [ + { label: , value: '1day' }, { label: ( ), value: '24hour', }, + { + label: , + value: '1week', + divider: true, + }, { label: ( ), value: '7day', }, + { + label: , + value: '1month', + divider: true, + }, { label: ( @@ -33,26 +46,22 @@ const filterOptions = [ ), value: '90day', }, - { label: , value: '1day' }, - { label: , value: '1week' }, - { - label: , - value: '1month', - }, { label: , value: '1year' }, { label: , value: 'custom', + divider: true, }, ]; export default function DateFilter({ value, startDate, endDate, onChange, className }) { - const [locale] = useLocale(); const [showPicker, setShowPicker] = useState(false); const displayValue = - value === 'custom' - ? `${dateFormat(startDate, 'd LLL y', locale)} — ${dateFormat(endDate, 'd LLL y', locale)}` - : value; + value === 'custom' ? ( + handleChange('custom')} /> + ) : ( + value + ); function handleChange(value) { if (value === 'custom') { @@ -90,3 +99,20 @@ export default function DateFilter({ value, startDate, endDate, onChange, classN ); } + +const CustomRange = ({ startDate, endDate, onClick }) => { + const [locale] = useLocale(); + + function handleClick(e) { + e.stopPropagation(); + + onClick(); + } + + return ( + <> + } className="mr-2" onClick={handleClick} /> + {`${dateFormat(startDate, 'd LLL y', locale)} — ${dateFormat(endDate, 'd LLL y', locale)}`} + + ); +}; diff --git a/components/common/DropDown.js b/components/common/DropDown.js index 03fa8bb6..df559ef9 100644 --- a/components/common/DropDown.js +++ b/components/common/DropDown.js @@ -36,8 +36,8 @@ export default function DropDown({ return (
-
{options.find(e => e.value === value)?.label || value}
- } size="small" /> + {options.find(e => e.value === value)?.label || value} + } className={styles.icon} size="small" />
{showMenu && ( diff --git a/components/common/Dropdown.module.css b/components/common/Dropdown.module.css index 958305e7..250e6c29 100644 --- a/components/common/Dropdown.module.css +++ b/components/common/Dropdown.module.css @@ -1,15 +1,24 @@ .dropdown { - flex: 1; position: relative; + display: flex; + justify-content: space-between; + align-items: center; border: 1px solid var(--gray500); border-radius: 4px; cursor: pointer; } .value { + flex: 1; display: flex; justify-content: space-between; font-size: var(--font-size-small); - min-width: 140px; + flex-wrap: nowrap; + white-space: nowrap; padding: 4px 16px; + min-width: 160px; +} + +.icon { + padding-left: 10px; } diff --git a/components/common/LanguageButton.js b/components/common/LanguageButton.js index 714ae7f6..0581a020 100644 --- a/components/common/LanguageButton.js +++ b/components/common/LanguageButton.js @@ -20,6 +20,10 @@ export default function LanguageButton({ menuPosition = 'bottom', menuAlign = 'l setShowMenu(false); } + function toggleMenu() { + setShowMenu(state => !state); + } + useDocumentClick(e => { if (!ref.current.contains(e.target)) { setShowMenu(false); @@ -43,7 +47,7 @@ export default function LanguageButton({ menuPosition = 'bottom', menuAlign = 'l )}
- {showMenu && ( diff --git a/components/common/Menu.js b/components/common/Menu.js index 4cb5a885..283ee1fb 100644 --- a/components/common/Menu.js +++ b/components/common/Menu.js @@ -25,7 +25,7 @@ export default function Menu({ {options .filter(({ hidden }) => !hidden) .map(option => { - const { label, value, className: customClassName, render } = option; + const { label, value, className: customClassName, render, divider } = option; return render ? ( render(option) @@ -34,6 +34,7 @@ export default function Menu({ key={value} className={classNames(styles.option, optionClassName, customClassName, { [selectedClassName]: selectedOption === value, + [styles.divider]: divider, })} onClick={e => onSelect(value, e)} > diff --git a/components/common/Menu.module.css b/components/common/Menu.module.css index be394b71..9bcd642f 100644 --- a/components/common/Menu.module.css +++ b/components/common/Menu.module.css @@ -40,3 +40,7 @@ .right { right: 0; } + +.divider { + border-top: 1px solid var(--gray300); +} diff --git a/components/metrics/MetricCard.module.css b/components/metrics/MetricCard.module.css index ed868f99..9c0d9e46 100644 --- a/components/metrics/MetricCard.module.css +++ b/components/metrics/MetricCard.module.css @@ -3,7 +3,7 @@ flex-direction: column; justify-content: center; min-width: 120px; - margin-right: 20px; + padding-right: 20px; } .value { @@ -16,4 +16,5 @@ .label { font-size: var(--font-size-normal); + white-space: nowrap; } diff --git a/components/metrics/MetricsBar.module.css b/components/metrics/MetricsBar.module.css index 4046634e..5f5cb1a7 100644 --- a/components/metrics/MetricsBar.module.css +++ b/components/metrics/MetricsBar.module.css @@ -4,6 +4,9 @@ } @media only screen and (max-width: 992px) { + .bar { + justify-content: space-between; + } .bar > div:last-child { display: none; } diff --git a/components/metrics/WebsiteChart.js b/components/metrics/WebsiteChart.js index 6b319a89..8cf01a5a 100644 --- a/components/metrics/WebsiteChart.js +++ b/components/metrics/WebsiteChart.js @@ -57,14 +57,17 @@ export default function WebsiteChart({ stickyClassName={styles.sticky} enabled={stickyHeader} > - - +
+ +
+
+ +
diff --git a/components/metrics/WebsiteChart.module.css b/components/metrics/WebsiteChart.module.css index 3bd9db19..ea0fcaee 100644 --- a/components/metrics/WebsiteChart.module.css +++ b/components/metrics/WebsiteChart.module.css @@ -29,3 +29,15 @@ border-bottom: 1px solid var(--gray300); z-index: 3; } + +.filter { + display: flex; + justify-content: flex-end; + align-items: center; +} + +@media only screen and (max-width: 992px) { + .filter { + display: block; + } +} diff --git a/lib/date.js b/lib/date.js index e7e28036..363e68dd 100644 --- a/lib/date.js +++ b/lib/date.js @@ -88,14 +88,16 @@ export function getDateRange(value) { } export function getDateRangeValues(startDate, endDate) { - if (differenceInHours(endDate, startDate) <= 48) { - return { startDate: startOfHour(startDate), endDate: endOfHour(endDate), unit: 'hour' }; + let unit = 'year'; + if (differenceInHours(endDate, startDate) <= 72) { + unit = 'hour'; } else if (differenceInCalendarDays(endDate, startDate) <= 90) { - return { startDate: startOfDay(startDate), endDate: endOfDay(endDate), unit: 'day' }; + unit = 'day'; } else if (differenceInCalendarMonths(endDate, startDate) <= 24) { - return { startDate: startOfMonth(startDate), endDate: endOfMonth(endDate), unit: 'month' }; + unit = 'month'; } - return { startDate: startOfYear(startDate), endDate: endOfYear(endDate), unit: 'year' }; + + return { startDate: startOfDay(startDate), endDate: endOfDay(endDate), unit }; } const dateFuncs = { @@ -112,11 +114,12 @@ export function getDateArray(data, startDate, endDate, unit) { function findData(t) { const x = data.find(e => { - if (unit === 'day') { - const [year, month, day] = e.t.split('-'); - return normalize(new Date(year, month - 1, day)).getTime() === t.getTime(); + if (unit === 'hour') { + return normalize(new Date(e.t)).getTime() === t.getTime(); } - return normalize(new Date(e.t)).getTime() === t.getTime(); + + const [year, month, day] = e.t.split('-'); + return normalize(new Date(year, month - 1, day)).getTime() === t.getTime(); }); return x?.y || 0; diff --git a/lib/lang.js b/lib/lang.js index 3a91f118..f66aa0ac 100644 --- a/lib/lang.js +++ b/lib/lang.js @@ -37,9 +37,9 @@ export const menuOptions = [ { label: 'Nederlands (Dutch)', value: 'nl-NL', display: 'NL' }, { label: 'Deutsch (German)', value: 'de-DE', display: 'DE' }, { label: '日本語 (Japanese)', value: 'ja-JP', display: '日本語' }, + { label: 'Español (Mexicano)', value: 'es-MX', display: 'ES' }, { label: 'Русский (Russian)', value: 'ru-RU', display: 'РУ' }, { label: 'Turkish', value: 'tr-TR', display: 'TR' }, - { label: 'Español (Mexicano)', value: 'es-MX', display: 'ES' }, ]; export function dateFormat(date, str, locale) { diff --git a/pages/api/website/[id]/events.js b/pages/api/website/[id]/events.js index c230acd1..143c745a 100644 --- a/pages/api/website/[id]/events.js +++ b/pages/api/website/[id]/events.js @@ -3,7 +3,7 @@ import { getEvents } from 'lib/queries'; import { ok, badRequest, methodNotAllowed } from 'lib/response'; import { useAuth } from 'lib/middleware'; -const unitTypes = ['month', 'hour', 'day']; +const unitTypes = ['year', 'month', 'hour', 'day']; export default async (req, res) => { await useAuth(req, res);