Merge branch 'dev' into analytics

This commit is contained in:
Mike Cao 2023-08-25 17:07:00 -07:00
commit ac43f55636
14 changed files with 65 additions and 58 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "umami", "name": "umami",
"version": "2.5.0", "version": "2.6.0",
"description": "A simple, fast, privacy-focused alternative to Google Analytics.", "description": "A simple, fast, privacy-focused alternative to Google Analytics.",
"author": "Mike Cao <mike@mikecao.com>", "author": "Mike Cao <mike@mikecao.com>",
"license": "MIT", "license": "MIT",

View File

@ -29,16 +29,6 @@ const aliasConfig = {
customResolver, customResolver,
}; };
const external = [
'react',
'react-dom',
'react/jsx-runtime',
'react-intl',
'react-basics',
'classnames',
'next',
];
const jsBundle = { const jsBundle = {
input: 'src/index.ts', input: 'src/index.ts',
output: [ output: [

View File

@ -4,7 +4,7 @@ const path = require('path');
const https = require('https'); const https = require('https');
const chalk = require('chalk'); const chalk = require('chalk');
const src = path.resolve(__dirname, '../lang'); const src = path.resolve(__dirname, '../src/lang');
const dest = path.resolve(__dirname, '../public/intl/country'); const dest = path.resolve(__dirname, '../public/intl/country');
const files = fs.readdirSync(src); const files = fs.readdirSync(src);

View File

@ -4,7 +4,7 @@ const path = require('path');
const https = require('https'); const https = require('https');
const chalk = require('chalk'); const chalk = require('chalk');
const src = path.resolve(__dirname, '../lang'); const src = path.resolve(__dirname, '../src/lang');
const dest = path.resolve(__dirname, '../public/intl/language'); const dest = path.resolve(__dirname, '../public/intl/language');
const files = fs.readdirSync(src); const files = fs.readdirSync(src);

View File

@ -3,7 +3,7 @@ const path = require('path');
const del = require('del'); const del = require('del');
const prettier = require('prettier'); const prettier = require('prettier');
const src = path.resolve(__dirname, '../lang'); const src = path.resolve(__dirname, '../src/lang');
const dest = path.resolve(__dirname, '../build/messages'); const dest = path.resolve(__dirname, '../build/messages');
const files = fs.readdirSync(src); const files = fs.readdirSync(src);
@ -17,7 +17,7 @@ async function run() {
await fs.ensureDir(dest); await fs.ensureDir(dest);
files.forEach(file => { files.forEach(file => {
const lang = require(`../lang/${file}`); const lang = require(`../src/lang/${file}`);
const keys = Object.keys(lang).sort(); const keys = Object.keys(lang).sort();
const formatted = keys.reduce((obj, key) => { const formatted = keys.reduce((obj, key) => {

View File

@ -1,3 +1,4 @@
import { createPortal } from 'react-dom';
import classNames from 'classnames'; import classNames from 'classnames';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import Link from 'next/link'; import Link from 'next/link';
@ -28,10 +29,11 @@ export function MobileMenu({ items = [], onClose }) {
</div> </div>
); );
return ( return createPortal(
<div className={classNames(styles.menu)}> <div className={classNames(styles.menu)}>
<Items items={items} /> <Items items={items} />
</div> </div>,
document.body,
); );
} }

View File

@ -8,9 +8,10 @@ import styles from './WebsiteDateFilter.module.css';
export function WebsiteDateFilter({ websiteId }) { export function WebsiteDateFilter({ websiteId }) {
const [dateRange, setDateRange] = useDateRange(websiteId); const [dateRange, setDateRange] = useDateRange(websiteId);
const { value, startDate, endDate, selectedUnit } = dateRange; const { value, startDate, endDate, selectedUnit } = dateRange;
const isFutureDate = const isFutureDate =
value !== 'all' && isAfter(incrementDateRange(dateRange, -1).startDate, new Date()); value !== 'all' &&
selectedUnit &&
isAfter(incrementDateRange(dateRange, -1).startDate, new Date());
const handleChange = value => { const handleChange = value => {
setDateRange(value); setDateRange(value);
@ -21,7 +22,21 @@ export function WebsiteDateFilter({ websiteId }) {
}; };
return ( return (
<> <Flexbox justifyContent="center" gap={10}>
{value !== 'all' && selectedUnit && (
<Flexbox justifyContent="center" className={styles.buttons}>
<Button onClick={() => handleIncrement(1)}>
<Icon rotate={90}>
<Icons.ChevronDown />
</Icon>
</Button>
<Button onClick={() => handleIncrement(-1)} disabled={isFutureDate}>
<Icon rotate={270}>
<Icons.ChevronDown />
</Icon>
</Button>
</Flexbox>
)}
<DateFilter <DateFilter
className={styles.dropdown} className={styles.dropdown}
value={value} value={value}
@ -31,22 +46,7 @@ export function WebsiteDateFilter({ websiteId }) {
onChange={handleChange} onChange={handleChange}
showAllTime={true} showAllTime={true}
/> />
{value !== 'all' && ( </Flexbox>
<Flexbox justifyContent="center" gap={10} className={styles.container}>
<Button onClick={() => handleIncrement(1)}>
<Icon rotate={90}>
<Icons.ChevronDown />
</Icon>
</Button>
<Button onClick={() => handleIncrement(-1)} disabled={isFutureDate}>
<Icon rotate={270}>
<Icons.ChevronDown />
</Icon>
</Button>
</Flexbox>
)}
</>
); );
} }

View File

@ -1,3 +1,14 @@
.dropdown { .dropdown {
min-width: 200px; min-width: 200px;
} }
.buttons button:first-child {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.buttons button:last-child {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border-left: 1px solid var(--base400) !important;
}

View File

@ -10,7 +10,7 @@
width: 100vw; width: 100vw;
grid-column: 1; grid-column: 1;
grid-row: 1 / 2; grid-row: 1 / 2;
z-index: 1; z-index: var(--z-index-popup);
} }
.body { .body {

View File

@ -1,8 +1,8 @@
import { safeDecodeURI } from 'next-basics'; import { safeDecodeURI } from 'next-basics';
import { Button, Icon, Icons, Text } from 'react-basics'; import { Button, Icon, Icons, Text } from 'react-basics';
import usePageQuery from 'components/hooks/usePageQuery'; import usePageQuery from 'components/hooks/usePageQuery';
import styles from './FilterTags.module.css';
import useMessages from 'components/hooks/useMessages'; import useMessages from 'components/hooks/useMessages';
import styles from './FilterTags.module.css';
export function FilterTags({ params }) { export function FilterTags({ params }) {
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
@ -26,6 +26,7 @@ export function FilterTags({ params }) {
return ( return (
<div className={styles.filters}> <div className={styles.filters}>
<div className={styles.label}>{formatMessage(labels.filters)}</div>
{Object.keys(params).map(key => { {Object.keys(params).map(key => {
if (!params[key]) { if (!params[key]) {
return null; return null;

View File

@ -4,19 +4,23 @@
gap: 10px; gap: 10px;
} }
.label {
font-weight: 700;
}
.tag { .tag {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
gap: 10px; gap: 10px;
font-size: var(--font-size-sm); font-size: var(--font-size-sm);
border: 1px solid var(--base600); border: 1px solid var(--blue400);
border-radius: var(--border-radius); border-radius: var(--border-radius);
line-height: 30px; padding: 8px 16px;
padding: 0 8px;
cursor: pointer; cursor: pointer;
background: var(--blue100);
} }
.tag:hover { .tag:hover {
background: var(--base75); background: var(--blue200);
} }

View File

@ -22,12 +22,12 @@ export default function WebsiteDetailsPage({ websiteId }) {
return ( return (
<Page loading={isLoading} error={error}> <Page loading={isLoading} error={error}>
<WebsiteHeader websiteId={websiteId} showLinks={showLinks} /> <WebsiteHeader websiteId={websiteId} showLinks={showLinks} />
<WebsiteMetricsBar websiteId={websiteId} sticky={true} />
<WebsiteChart websiteId={websiteId} />
<FilterTags <FilterTags
websiteId={websiteId} websiteId={websiteId}
params={{ url, referrer, os, browser, device, country, region, city, title }} params={{ url, referrer, os, browser, device, country, region, city, title }}
/> />
<WebsiteMetricsBar websiteId={websiteId} sticky={true} />
<WebsiteChart websiteId={websiteId} />
{!website && <Loading icon="dots" style={{ minHeight: 300 }} />} {!website && <Loading icon="dots" style={{ minHeight: 300 }} />}
{website && ( {website && (
<> <>

View File

@ -1,4 +1,4 @@
import { Flexbox } from 'react-basics'; import { Flexbox, Loading } from 'react-basics';
import EventDataTable from 'components/pages/event-data/EventDataTable'; import EventDataTable from 'components/pages/event-data/EventDataTable';
import EventDataValueTable from 'components/pages/event-data/EventDataValueTable'; import EventDataValueTable from 'components/pages/event-data/EventDataValueTable';
import { EventDataMetricsBar } from 'components/pages/event-data/EventDataMetricsBar'; import { EventDataMetricsBar } from 'components/pages/event-data/EventDataMetricsBar';
@ -28,13 +28,14 @@ export default function WebsiteEventData({ websiteId }) {
const { const {
query: { event }, query: { event },
} = usePageQuery(); } = usePageQuery();
const { data } = useData(websiteId, event); const { data, isLoading } = useData(websiteId, event);
return ( return (
<Flexbox className={styles.container} direction="column" gap={20}> <Flexbox className={styles.container} direction="column" gap={20}>
<EventDataMetricsBar websiteId={websiteId} /> <EventDataMetricsBar websiteId={websiteId} />
{!event && <EventDataTable data={data} />} {!event && <EventDataTable data={data} />}
{event && <EventDataValueTable event={event} data={data} />} {isLoading && <Loading position="page" />}
{event && data && <EventDataValueTable event={event} data={data} />}
</Flexbox> </Flexbox>
); );
} }

View File

@ -7,10 +7,10 @@ import MetricsBar from 'components/metrics/MetricsBar';
import FilterSelectForm from 'components/pages/reports/FilterSelectForm'; import FilterSelectForm from 'components/pages/reports/FilterSelectForm';
import PopupForm from 'components/pages/reports/PopupForm'; import PopupForm from 'components/pages/reports/PopupForm';
import { formatShortTime } from 'lib/format'; import { formatShortTime } from 'lib/format';
import { Button, Column, Icon, Icons, Popup, PopupTrigger, Row, TooltipPopup } from 'react-basics'; import { Button, Column, Icon, Icons, Popup, PopupTrigger, Row } from 'react-basics';
import styles from './WebsiteMetricsBar.module.css'; import styles from './WebsiteMetricsBar.module.css';
export function WebsiteMetricsBar({ websiteId, showFilter = true, sticky }) { export function WebsiteMetricsBar({ websiteId, showFilter = true, showRefresh = true, sticky }) {
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
const { get, useQuery } = useApi(); const { get, useQuery } = useApi();
@ -71,14 +71,12 @@ export function WebsiteMetricsBar({ websiteId, showFilter = true, sticky }) {
const WebsiteFilterButton = () => { const WebsiteFilterButton = () => {
return ( return (
<PopupTrigger> <PopupTrigger>
<TooltipPopup label={formatMessage(labels.addFilter)} position="top"> <Button>
<Button> <Icon>
<Icon> <Icons.Plus />
<Icons.Plus /> </Icon>
</Icon> {formatMessage(labels.filter)}
{formatMessage(labels.filter)} </Button>
</Button>
</TooltipPopup>
<Popup position="bottom" alignment="start" className={styles.popup}> <Popup position="bottom" alignment="start" className={styles.popup}>
{close => { {close => {
return ( return (
@ -163,8 +161,8 @@ export function WebsiteMetricsBar({ websiteId, showFilter = true, sticky }) {
<Column defaultSize={12} xl={4}> <Column defaultSize={12} xl={4}>
<div className={styles.actions}> <div className={styles.actions}>
{showFilter && <WebsiteFilterButton />} {showFilter && <WebsiteFilterButton />}
{showRefresh && <RefreshButton websiteId={websiteId} />}
<WebsiteDateFilter websiteId={websiteId} /> <WebsiteDateFilter websiteId={websiteId} />
<RefreshButton websiteId={websiteId} />
</div> </div>
</Column> </Column>
</Row> </Row>