mirror of
https://github.com/umami-software/umami.git
synced 2026-02-04 04:37:11 +01:00
Added month select component.
This commit is contained in:
parent
6ef1f2e9f9
commit
c548267d91
16 changed files with 169 additions and 71 deletions
|
|
@ -3,7 +3,7 @@ import { Icon, Modal, Dropdown, Item, Text, Flexbox } from 'react-basics';
|
|||
import { endOfYear, isSameDay } from 'date-fns';
|
||||
import DatePickerForm from 'components/metrics/DatePickerForm';
|
||||
import useLocale from 'hooks/useLocale';
|
||||
import { dateFormat } from 'lib/date';
|
||||
import { formatDate } from 'lib/date';
|
||||
import Icons from 'components/icons';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
|
|
@ -135,8 +135,8 @@ const CustomRange = ({ startDate, endDate, onClick }) => {
|
|||
<Icons.Calendar />
|
||||
</Icon>
|
||||
<Text>
|
||||
{dateFormat(startDate, 'd LLL y', locale)}
|
||||
{!isSameDay(startDate, endDate) && ` — ${dateFormat(endDate, 'd LLL y', locale)}`}
|
||||
{formatDate(startDate, 'd LLL y', locale)}
|
||||
{!isSameDay(startDate, endDate) && ` — ${formatDate(endDate, 'd LLL y', locale)}`}
|
||||
</Text>
|
||||
</Flexbox>
|
||||
);
|
||||
|
|
|
|||
51
components/input/MonthSelect.js
Normal file
51
components/input/MonthSelect.js
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import { useRef, useState } from 'react';
|
||||
import { Text, Icon, CalendarMonthSelect, CalendarYearSelect, Button } from 'react-basics';
|
||||
import { startOfMonth, endOfMonth } from 'date-fns';
|
||||
import Icons from 'components/icons';
|
||||
import { useLocale } from 'hooks';
|
||||
import { formatDate } from 'lib/date';
|
||||
import { getDateLocale } from 'lib/lang';
|
||||
import styles from './MonthSelect.module.css';
|
||||
|
||||
const MONTH = 'month';
|
||||
const YEAR = 'year';
|
||||
|
||||
export function MonthSelect({ date = new Date(), onChange }) {
|
||||
const { locale } = useLocale();
|
||||
const [select, setSelect] = useState(null);
|
||||
const month = formatDate(date, 'MMMM', locale);
|
||||
const year = date.getFullYear();
|
||||
const ref = useRef();
|
||||
|
||||
const handleSelect = value => {
|
||||
setSelect(state => (state !== value ? value : null));
|
||||
};
|
||||
|
||||
const handleChange = date => {
|
||||
onChange(`range:${startOfMonth(date).getTime()}:${endOfMonth(date).getTime()}`);
|
||||
setSelect(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div ref={ref} className={styles.container}>
|
||||
<Button className={styles.input} variant="quiet" onClick={() => handleSelect(MONTH)}>
|
||||
<Text>{month}</Text>
|
||||
<Icon size="sm">{select === MONTH ? <Icons.Close /> : <Icons.ChevronDown />}</Icon>
|
||||
</Button>
|
||||
<Button className={styles.input} variant="quiet" onClick={() => handleSelect(YEAR)}>
|
||||
<Text>{year}</Text>
|
||||
<Icon size="sm">{select === YEAR ? <Icons.Close /> : <Icons.ChevronDown />}</Icon>
|
||||
</Button>
|
||||
</div>
|
||||
{select === MONTH && (
|
||||
<CalendarMonthSelect date={date} locale={getDateLocale(locale)} onSelect={handleChange} />
|
||||
)}
|
||||
{select === YEAR && (
|
||||
<CalendarYearSelect date={date} locale={getDateLocale(locale)} onSelect={handleChange} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default MonthSelect;
|
||||
12
components/input/MonthSelect.module.css
Normal file
12
components/input/MonthSelect.module.css
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
.container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-top: 40px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.content {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import useLocale from 'hooks/useLocale';
|
|||
import useCountryNames from 'hooks/useCountryNames';
|
||||
import { BROWSERS } from 'lib/constants';
|
||||
import { stringToColor } from 'lib/format';
|
||||
import { dateFormat } from 'lib/date';
|
||||
import { formatDate } from 'lib/date';
|
||||
import { safeDecodeURI } from 'next-basics';
|
||||
import Icons from 'components/icons';
|
||||
import styles from './RealtimeLog.module.css';
|
||||
|
|
@ -50,7 +50,7 @@ export function RealtimeLog({ data, websiteDomain }) {
|
|||
},
|
||||
];
|
||||
|
||||
const getTime = ({ createdAt }) => dateFormat(new Date(createdAt), 'pp', locale);
|
||||
const getTime = ({ createdAt }) => formatDate(new Date(createdAt), 'pp', locale);
|
||||
|
||||
const getColor = ({ id, sessionId }) => stringToColor(sessionId || id);
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,12 @@ import { useContext } from 'react';
|
|||
import { ReportContext } from './Report';
|
||||
import { useMessages } from 'hooks';
|
||||
|
||||
export function BaseParameters() {
|
||||
export function BaseParameters({
|
||||
showWebsiteSelect = true,
|
||||
allowWebsiteSelect = true,
|
||||
showDateSelect = true,
|
||||
allowDateSelect = true,
|
||||
}) {
|
||||
const { report, updateReport } = useContext(ReportContext);
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
|
|
@ -24,17 +29,25 @@ export function BaseParameters() {
|
|||
|
||||
return (
|
||||
<>
|
||||
<FormRow label={formatMessage(labels.website)}>
|
||||
<WebsiteSelect websiteId={websiteId} onSelect={handleWebsiteSelect} />
|
||||
</FormRow>
|
||||
<FormRow label={formatMessage(labels.dateRange)}>
|
||||
<DateFilter
|
||||
value={value}
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
onChange={handleDateChange}
|
||||
/>
|
||||
</FormRow>
|
||||
{showWebsiteSelect && (
|
||||
<FormRow label={formatMessage(labels.website)}>
|
||||
{allowWebsiteSelect && (
|
||||
<WebsiteSelect websiteId={websiteId} onSelect={handleWebsiteSelect} />
|
||||
)}
|
||||
</FormRow>
|
||||
)}
|
||||
{showDateSelect && (
|
||||
<FormRow label={formatMessage(labels.dateRange)}>
|
||||
{allowDateSelect && (
|
||||
<DateFilter
|
||||
value={value}
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
onChange={handleDateChange}
|
||||
/>
|
||||
)}
|
||||
</FormRow>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,19 @@
|
|||
import { useContext, useRef } from 'react';
|
||||
import { useMessages } from 'hooks';
|
||||
import { Form, FormButtons, FormInput, FormRow, SubmitButton, TextField } from 'react-basics';
|
||||
import { Form, FormButtons, FormRow, SubmitButton } from 'react-basics';
|
||||
import { ReportContext } from 'components/pages/reports/Report';
|
||||
import { MonthSelect } from 'components/input/MonthSelect';
|
||||
import BaseParameters from '../BaseParameters';
|
||||
|
||||
const fieldOptions = [
|
||||
{ name: 'daily', type: 'string' },
|
||||
{ name: 'weekly', type: 'string' },
|
||||
];
|
||||
import { parseDateRange } from 'lib/date';
|
||||
|
||||
export function RetentionParameters() {
|
||||
const { report, runReport, isRunning } = useContext(ReportContext);
|
||||
const { report, runReport, isRunning, updateReport } = useContext(ReportContext);
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const ref = useRef(null);
|
||||
|
||||
const { parameters } = report || {};
|
||||
const { websiteId, dateRange } = parameters || {};
|
||||
const { startDate } = dateRange || {};
|
||||
const queryDisabled = !websiteId || !dateRange;
|
||||
|
||||
const handleSubmit = (data, e) => {
|
||||
|
|
@ -26,9 +24,16 @@ export function RetentionParameters() {
|
|||
}
|
||||
};
|
||||
|
||||
const handleDateChange = value => {
|
||||
updateReport({ parameters: { dateRange: { ...parseDateRange(value) } } });
|
||||
};
|
||||
|
||||
return (
|
||||
<Form ref={ref} values={parameters} onSubmit={handleSubmit} preventSubmit={true}>
|
||||
<BaseParameters />
|
||||
<BaseParameters showDateSelect={false} />
|
||||
<FormRow label={formatMessage(labels.dateRange)}>
|
||||
<MonthSelect date={startDate} onChange={handleDateChange} />
|
||||
</FormRow>
|
||||
<FormButtons>
|
||||
<SubmitButton variant="primary" disabled={queryDisabled} loading={isRunning}>
|
||||
{formatMessage(labels.runQuery)}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { useContext } from 'react';
|
||||
import { GridTable, GridColumn } from 'react-basics';
|
||||
import classNames from 'classnames';
|
||||
import { ReportContext } from '../Report';
|
||||
import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
|
||||
import { useMessages } from 'hooks';
|
||||
import { dateFormat } from 'lib/date';
|
||||
import { formatDate } from 'lib/date';
|
||||
import styles from './RetentionTable.module.css';
|
||||
|
||||
export function RetentionTable() {
|
||||
|
|
@ -15,34 +16,32 @@ export function RetentionTable() {
|
|||
return <EmptyPlaceholder />;
|
||||
}
|
||||
|
||||
const dates = data.reduce((arr, { date }) => {
|
||||
if (!arr.includes(date)) {
|
||||
return arr.concat(date);
|
||||
const rows = data.reduce((arr, { date, visitors }) => {
|
||||
if (!arr.find(a => a.date === date)) {
|
||||
return arr.concat({ date, visitors });
|
||||
}
|
||||
return arr;
|
||||
}, []);
|
||||
|
||||
const days = Array(32).fill(null);
|
||||
const days = [1, 2, 3, 4, 5, 6, 7, 14, 21, 30];
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.table}>
|
||||
<div className={styles.row}>
|
||||
<div className={classNames(styles.row, styles.header)}>
|
||||
<div className={styles.date}>{formatMessage(labels.date)}</div>
|
||||
{days.map((n, i) => (
|
||||
<div key={i} className={styles.header}>
|
||||
{formatMessage(labels.day)} {i}
|
||||
<div className={styles.visitors}>{formatMessage(labels.visitors)}</div>
|
||||
{days.map(n => (
|
||||
<div key={n} className={styles.day}>
|
||||
{formatMessage(labels.day)} {n}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{dates.map((date, i) => {
|
||||
{rows.map(({ date, visitors }, i) => {
|
||||
return (
|
||||
<div key={i} className={styles.row}>
|
||||
<div className={styles.date}>
|
||||
{dateFormat(date, 'P')}
|
||||
<br />
|
||||
{date}
|
||||
</div>
|
||||
<div className={styles.date}>{formatDate(`${date} 00:00:00`, 'PP')}</div>
|
||||
<div className={styles.visitors}>{visitors}</div>
|
||||
{days.map((n, day) => {
|
||||
return (
|
||||
<div key={day} className={styles.cell}>
|
||||
|
|
|
|||
|
|
@ -4,10 +4,7 @@
|
|||
}
|
||||
|
||||
.header {
|
||||
width: 60px;
|
||||
height: 40px;
|
||||
text-align: center;
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.row {
|
||||
|
|
@ -28,5 +25,24 @@
|
|||
}
|
||||
|
||||
.date {
|
||||
min-width: 200px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 160px;
|
||||
}
|
||||
|
||||
.visitors {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.day {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
text-align: center;
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue