Added reports section.

This commit is contained in:
Mike Cao 2023-05-17 23:20:06 -07:00
parent ad918c5bba
commit a5700d4a25
36 changed files with 422 additions and 43 deletions

View file

@ -3,31 +3,22 @@ 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, getDateRangeValues } from 'lib/date';
import { dateFormat } from 'lib/date';
import Icons from 'components/icons';
import useApi from 'hooks/useApi';
import useDateRange from 'hooks/useDateRange';
import useMessages from 'hooks/useMessages';
export function DateFilter({ websiteId, value, className }) {
export function DateFilter({
value,
startDate,
endDate,
className,
onChange,
showAllTime = false,
alignment = 'end',
}) {
const { formatMessage, labels } = useMessages();
const { get } = useApi();
const [dateRange, setDateRange] = useDateRange(websiteId);
const { startDate, endDate } = dateRange;
const [showPicker, setShowPicker] = useState(false);
async function handleDateChange(value) {
if (value === 'all' && websiteId) {
const data = await get(`/websites/${websiteId}`);
if (data) {
setDateRange({ value, ...getDateRangeValues(new Date(data.createdAt), Date.now()) });
}
} else if (value !== 'all') {
setDateRange(value);
}
}
const options = [
{ label: formatMessage(labels.today), value: '1day' },
{
@ -61,7 +52,7 @@ export function DateFilter({ websiteId, value, className }) {
value: '90day',
},
{ label: formatMessage(labels.thisYear), value: '1year' },
websiteId && {
showAllTime && {
label: formatMessage(labels.allTime),
value: 'all',
divider: true,
@ -86,12 +77,12 @@ export function DateFilter({ websiteId, value, className }) {
setShowPicker(true);
return;
}
handleDateChange(value);
onChange(value);
};
const handlePickerChange = value => {
setShowPicker(false);
handleDateChange(value);
onChange(value);
};
const handleClose = () => setShowPicker(false);
@ -103,7 +94,8 @@ export function DateFilter({ websiteId, value, className }) {
items={options}
renderValue={renderValue}
value={value}
alignment="end"
alignment={alignment}
placeholder={formatMessage(labels.selectDate)}
onChange={handleChange}
>
{({ label, value, divider }) => (

View file

@ -0,0 +1,26 @@
import { getDateRangeValues } from 'lib/date';
import useApi from 'hooks/useApi';
import useDateRange from 'hooks/useDateRange';
import DateFilter from './DateFilter';
export default function WebsiteDateFilter({ websiteId, value }) {
const { get } = useApi();
const [dateRange, setDateRange] = useDateRange(websiteId);
const { startDate, endDate } = dateRange;
const handleChange = async value => {
if (value === 'all' && websiteId) {
const data = await get(`/websites/${websiteId}`);
if (data) {
setDateRange({ value, ...getDateRangeValues(new Date(data.createdAt), Date.now()) });
}
} else if (value !== 'all') {
setDateRange(value);
}
};
return (
<DateFilter value={value} startDate={startDate} endDate={endDate} onChange={handleChange} />
);
}

View file

@ -18,6 +18,7 @@ export function NavBar() {
const links = [
{ label: formatMessage(labels.dashboard), url: '/dashboard' },
{ label: formatMessage(labels.reports), url: '/reports' },
{ label: formatMessage(labels.realtime), url: '/realtime' },
!cloudMode && { label: formatMessage(labels.settings), url: '/settings' },
].filter(n => n);

View file

@ -97,6 +97,7 @@ export const labels = defineMessages({
allTime: { id: 'label.all-time', defaultMessage: 'All time' },
customRange: { id: 'label.custom-range', defaultMessage: 'Custom range' },
selectWebsite: { id: 'label.select-website', defaultMessage: 'Select website' },
selectDate: { id: 'label.select-date', defaultMessage: 'Select date' },
all: { id: 'label.all', defaultMessage: 'All' },
sessions: { id: 'label.sessions', defaultMessage: 'Sessions' },
pageNotFound: { id: 'message.page-not-found', defaultMessage: 'Page not found' },
@ -117,6 +118,8 @@ export const labels = defineMessages({
view: { id: 'label.view', defaultMessage: 'View' },
cities: { id: 'label.cities', defaultMessage: 'Cities' },
regions: { id: 'label.regions', defaultMessage: 'Regions' },
reports: { id: 'label.reports', defaultMessage: 'Reports' },
eventData: { id: 'label.event-data', defaultMessage: 'Event data' },
});
export const messages = defineMessages({

View file

@ -5,7 +5,7 @@ import classNames from 'classnames';
import PageviewsChart from './PageviewsChart';
import MetricsBar from './MetricsBar';
import WebsiteHeader from './WebsiteHeader';
import DateFilter from 'components/input/DateFilter';
import WebsiteDateFilter from 'components/input/WebsiteDateFilter';
import ErrorMessage from 'components/common/ErrorMessage';
import FilterTags from 'components/metrics/FilterTags';
import RefreshButton from 'components/input/RefreshButton';
@ -107,7 +107,7 @@ export function WebsiteChart({
<Column defaultSize={12} xl={4}>
<div className={styles.actions}>
<RefreshButton websiteId={websiteId} isLoading={isLoading} />
<DateFilter websiteId={websiteId} value={value} className={styles.dropdown} />
<WebsiteDateFilter websiteId={websiteId} value={value} className={styles.dropdown} />
</div>
</Column>
</Row>

View file

@ -0,0 +1,33 @@
import { useState } from 'react';
import { Form, FormRow, FormInput, TextField } from 'react-basics';
import AppLayout from 'components/layout/AppLayout';
import Report from './Report';
import ReportHeader from './ReportHeader';
import useMessages from 'hooks/useMessages';
import Nodes from 'assets/nodes.svg';
import styles from './reports.module.css';
export default function EventDataReport({ websiteId, data }) {
const [values, setValues] = useState({ query: '' });
const { formatMessage, labels } = useMessages();
return (
<AppLayout>
<Report>
<ReportHeader title={formatMessage(labels.eventData)} icon={<Nodes />} />
<div className={styles.container}>
<div className={styles.menu}>
<Form>
<FormRow label="Properties">
<FormInput name="query">
<TextField value={values.query} />
</FormInput>
</FormRow>
</Form>
</div>
<div className={styles.content}></div>
</div>
</Report>
</AppLayout>
);
}

View file

@ -0,0 +1,5 @@
import Page from 'components/layout/Page';
export default function Report({ children, ...props }) {
return <Page {...props}>{children}</Page>;
}

View file

@ -0,0 +1,42 @@
import { useState } from 'react';
import { Flexbox, Icon, Text } from 'react-basics';
import WebsiteSelect from 'components/input/WebsiteSelect';
import PageHeader from 'components/layout/PageHeader';
import DateFilter from 'components/input/DateFilter';
import { parseDateRange } from 'lib/date';
export default function ReportHeader({ title, icon }) {
const [websiteId, setWebsiteId] = useState();
const [dateRange, setDateRange] = useState({});
const { value, startDate, endDate } = dateRange;
const handleSelect = id => {
setWebsiteId(id);
};
const handleDateChange = value => setDateRange(parseDateRange(value));
const Title = () => {
return (
<>
<Icon size="xl">{icon}</Icon>
<Text>{title}</Text>
</>
);
};
return (
<PageHeader title={<Title />}>
<Flexbox gap={20}>
<DateFilter
value={value}
startDate={startDate}
endDate={endDate}
onChange={handleDateChange}
showAllTime
/>
<WebsiteSelect websiteId={websiteId} onSelect={handleSelect} />
</Flexbox>
</PageHeader>
);
}

View file

@ -0,0 +1,66 @@
import Link from 'next/link';
import { Button, Icons, Text, Icon } from 'react-basics';
import Page from 'components/layout/Page';
import PageHeader from 'components/layout/PageHeader';
import Funnel from 'assets/funnel.svg';
import Nodes from 'assets/nodes.svg';
import Lightbulb from 'assets/lightbulb.svg';
import styles from './ReportsList.module.css';
const reports = [
{
title: 'Event data',
description: 'Query your event data.',
url: '/reports/event-data',
icon: <Nodes />,
},
{
title: 'Funnel',
description: 'Understand the conversion and drop-off rate of users.',
url: '/reports/funnel',
icon: <Funnel />,
},
{
title: 'Insights',
description: 'Explore your data by applying segments and filters.',
url: '/reports/insights',
icon: <Lightbulb />,
},
];
function Report({ title, description, url, icon }) {
return (
<div className={styles.report}>
<div className={styles.title}>
<Icon size="lg">{icon}</Icon>
<Text>{title}</Text>
</div>
<div className={styles.description}>{description}</div>
<div className={styles.buttons}>
<Link href={url}>
<Button variant="primary">
<Icon>
<Icons.Plus />
</Icon>
<Text>Create</Text>
</Button>
</Link>
</div>
</div>
);
}
export default function ReportsList() {
return (
<Page>
<PageHeader title="Reports" />
<div className={styles.reports}>
{reports.map(({ title, description, url, icon }) => {
return (
<Report key={title} icon={icon} title={title} description={description} url={url} />
);
})}
</div>
</Page>
);
}

View file

@ -0,0 +1,32 @@
.reports {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(360px, 1fr));
gap: 20px;
}
.report {
display: flex;
flex-direction: column;
gap: 20px;
padding: 20px;
border: 1px solid var(--base500);
border-radius: var(--border-radius);
}
.title {
display: flex;
gap: 10px;
align-items: center;
font-size: var(--font-size-lg);
font-weight: 700;
}
.description {
flex: 1;
}
.buttons {
display: flex;
align-items: center;
justify-content: center;
}

View file

@ -0,0 +1,14 @@
.container {
display: grid;
grid-template-rows: 1fr;
grid-template-columns: max-content 1fr;
}
.menu {
width: 300px;
grid-column: 1 / 2;
}
.content {
grid-column: 2 / 3;
}

View file

@ -9,11 +9,12 @@ export function DateRangeSetting() {
const [dateRange, setDateRange] = useDateRange();
const { startDate, endDate, value } = dateRange;
const handleChange = value => setDateRange(value);
const handleReset = () => setDateRange(DEFAULT_DATE_RANGE);
return (
<Flexbox gap={10}>
<DateFilter value={value} startDate={startDate} endDate={endDate} />
<DateFilter value={value} startDate={startDate} endDate={endDate} onChange={handleChange} />
<Button onClick={handleReset}>{formatMessage(labels.reset)}</Button>
</Flexbox>
);