mirror of
https://github.com/kremalicious/umami.git
synced 2024-11-22 18:00:17 +01:00
Calendar updates. Responsive CSS updates.
This commit is contained in:
parent
a0cb278463
commit
f59594e4cd
1
assets/calendar-alt.svg
Normal file
1
assets/calendar-alt.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M400 64h-48V12c0-6.6-5.4-12-12-12h-8c-6.6 0-12 5.4-12 12v52H128V12c0-6.6-5.4-12-12-12h-8c-6.6 0-12 5.4-12 12v52H48C21.5 64 0 85.5 0 112v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V112c0-26.5-21.5-48-48-48zM48 96h352c8.8 0 16 7.2 16 16v48H32v-48c0-8.8 7.2-16 16-16zm352 384H48c-8.8 0-16-7.2-16-16V192h384v272c0 8.8-7.2 16-16 16zM148 320h-40c-6.6 0-12-5.4-12-12v-40c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v40c0 6.6-5.4 12-12 12zm96 0h-40c-6.6 0-12-5.4-12-12v-40c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v40c0 6.6-5.4 12-12 12zm96 0h-40c-6.6 0-12-5.4-12-12v-40c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v40c0 6.6-5.4 12-12 12zm-96 96h-40c-6.6 0-12-5.4-12-12v-40c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v40c0 6.6-5.4 12-12 12zm-96 0h-40c-6.6 0-12-5.4-12-12v-40c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v40c0 6.6-5.4 12-12 12zm192 0h-40c-6.6 0-12-5.4-12-12v-40c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v40c0 6.6-5.4 12-12 12z"/></svg>
|
After Width: | Height: | Size: 1002 B |
@ -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}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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) => (
|
||||
<tr key={i}>
|
||||
{row.map((month, j) => {
|
||||
const disabled = isBefore(month, minDate) || isAfter(month, maxDate);
|
||||
const disabled =
|
||||
isBefore(endOfMonth(month), minDate) || isAfter(startOfMonth(month), maxDate);
|
||||
return (
|
||||
<td
|
||||
key={j}
|
||||
@ -221,10 +222,11 @@ const YearSelector = ({ date, minDate, maxDate, onSelect }) => {
|
||||
<div className={styles.pager}>
|
||||
<Button
|
||||
icon={<Chevron />}
|
||||
size="xsmall"
|
||||
size="small"
|
||||
className={styles.left}
|
||||
onClick={handlePrevClick}
|
||||
disabled={years[0] <= minYear}
|
||||
variant="light"
|
||||
/>
|
||||
<table>
|
||||
<tbody>
|
||||
@ -248,10 +250,11 @@ const YearSelector = ({ date, minDate, maxDate, onSelect }) => {
|
||||
</table>
|
||||
<Button
|
||||
icon={<Chevron />}
|
||||
size="xsmall"
|
||||
size="small"
|
||||
className={styles.right}
|
||||
onClick={handleNextClick}
|
||||
disabled={years[years.length - 1] > maxYear}
|
||||
variant="light"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -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 {
|
||||
|
@ -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: <FormattedMessage id="label.today" defaultMessage="Today" />, value: '1day' },
|
||||
{
|
||||
label: (
|
||||
<FormattedMessage id="label.last-hours" defaultMessage="Last {x} hours" values={{ x: 24 }} />
|
||||
),
|
||||
value: '24hour',
|
||||
},
|
||||
{
|
||||
label: <FormattedMessage id="label.this-week" defaultMessage="This week" />,
|
||||
value: '1week',
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<FormattedMessage id="label.last-days" defaultMessage="Last {x} days" values={{ x: 7 }} />
|
||||
),
|
||||
value: '7day',
|
||||
},
|
||||
{
|
||||
label: <FormattedMessage id="label.this-month" defaultMessage="This month" />,
|
||||
value: '1month',
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<FormattedMessage id="label.last-days" defaultMessage="Last {x} days" values={{ x: 30 }} />
|
||||
@ -33,26 +46,22 @@ const filterOptions = [
|
||||
),
|
||||
value: '90day',
|
||||
},
|
||||
{ label: <FormattedMessage id="label.today" defaultMessage="Today" />, value: '1day' },
|
||||
{ label: <FormattedMessage id="label.this-week" defaultMessage="This week" />, value: '1week' },
|
||||
{
|
||||
label: <FormattedMessage id="label.this-month" defaultMessage="This month" />,
|
||||
value: '1month',
|
||||
},
|
||||
{ label: <FormattedMessage id="label.this-year" defaultMessage="This year" />, value: '1year' },
|
||||
{
|
||||
label: <FormattedMessage id="label.custom-range" defaultMessage="Custom range" />,
|
||||
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' ? (
|
||||
<CustomRange startDate={startDate} endDate={endDate} onClick={() => 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 (
|
||||
<>
|
||||
<Icon icon={<Calendar />} className="mr-2" onClick={handleClick} />
|
||||
{`${dateFormat(startDate, 'd LLL y', locale)} — ${dateFormat(endDate, 'd LLL y', locale)}`}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -36,8 +36,8 @@ export default function DropDown({
|
||||
return (
|
||||
<div ref={ref} className={classNames(styles.dropdown, className)} onClick={handleShowMenu}>
|
||||
<div className={styles.value}>
|
||||
<div>{options.find(e => e.value === value)?.label || value}</div>
|
||||
<Icon icon={<Chevron />} size="small" />
|
||||
{options.find(e => e.value === value)?.label || value}
|
||||
<Icon icon={<Chevron />} className={styles.icon} size="small" />
|
||||
</div>
|
||||
{showMenu && (
|
||||
<Menu className={menuClassName} options={options} onSelect={handleSelect} float="bottom" />
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
)}
|
||||
</Head>
|
||||
<div ref={ref} className={styles.container}>
|
||||
<Button icon={<Globe />} onClick={() => setShowMenu(true)} size="small">
|
||||
<Button icon={<Globe />} onClick={toggleMenu} size="small">
|
||||
<div className={locale}>{selectedLocale}</div>
|
||||
</Button>
|
||||
{showMenu && (
|
||||
|
@ -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)}
|
||||
>
|
||||
|
@ -40,3 +40,7 @@
|
||||
.right {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.divider {
|
||||
border-top: 1px solid var(--gray300);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -4,6 +4,9 @@
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 992px) {
|
||||
.bar {
|
||||
justify-content: space-between;
|
||||
}
|
||||
.bar > div:last-child {
|
||||
display: none;
|
||||
}
|
||||
|
@ -57,14 +57,17 @@ export default function WebsiteChart({
|
||||
stickyClassName={styles.sticky}
|
||||
enabled={stickyHeader}
|
||||
>
|
||||
<MetricsBar className="col-12 col-md-9" websiteId={websiteId} />
|
||||
<DateFilter
|
||||
className="col-12 col-md-3"
|
||||
value={value}
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
onChange={handleDateChange}
|
||||
/>
|
||||
<div className="col-12 col-lg-9">
|
||||
<MetricsBar websiteId={websiteId} />
|
||||
</div>
|
||||
<div className={classNames(styles.filter, 'col-12 col-lg-3')}>
|
||||
<DateFilter
|
||||
value={value}
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
onChange={handleDateChange}
|
||||
/>
|
||||
</div>
|
||||
</StickyHeader>
|
||||
</div>
|
||||
<div className="row">
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
21
lib/date.js
21
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;
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user