CSS updates.

This commit is contained in:
Mike Cao 2023-10-02 23:51:26 -07:00
parent 9bb89c7e8b
commit e4c5f42189
22 changed files with 227 additions and 395 deletions

View File

@ -9,7 +9,7 @@ import Head from 'next/head';
import Link from 'next/link'; import Link from 'next/link';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import Script from 'next/script'; import Script from 'next/script';
import { Button, Column, Row } from 'react-basics'; import { Button } from 'react-basics';
import styles from './TestConsole.module.css'; import styles from './TestConsole.module.css';
export function TestConsole() { export function TestConsole() {
@ -91,8 +91,8 @@ export function TestConsole() {
src={`${basePath}/script.js`} src={`${basePath}/script.js`}
data-cache="true" data-cache="true"
/> />
<Row className={styles.test}> <div className={styles.test}>
<Column xs="4"> <div>
<div className={styles.header}>Page links</div> <div className={styles.header}>Page links</div>
<div> <div>
<Link href={`/console/${websiteId}/page/1/?q=abc`}>page one</Link> <Link href={`/console/${websiteId}/page/1/?q=abc`}>page one</Link>
@ -115,8 +115,8 @@ export function TestConsole() {
external link (tab) external link (tab)
</a> </a>
</div> </div>
</Column> </div>
<Column xs="4"> <div>
<div className={styles.header}>Click events</div> <div className={styles.header}>Click events</div>
<Button id="send-event-button" data-umami-event="button-click" variant="action"> <Button id="send-event-button" data-umami-event="button-click" variant="action">
Send event Send event
@ -131,8 +131,8 @@ export function TestConsole() {
> >
Send event with data Send event with data
</Button> </Button>
</Column> </div>
<Column xs="4"> <div>
<div className={styles.header}>Javascript events</div> <div className={styles.header}>Javascript events</div>
<Button id="manual-button" variant="action" onClick={handleClick}> <Button id="manual-button" variant="action" onClick={handleClick}>
Run script Run script
@ -141,14 +141,12 @@ export function TestConsole() {
<Button id="manual-button" variant="action" onClick={handleIdentifyClick}> <Button id="manual-button" variant="action" onClick={handleIdentifyClick}>
Run identify Run identify
</Button> </Button>
</Column> </div>
</Row> </div>
<Row> <div>
<Column> <WebsiteChart websiteId={website.id} />
<WebsiteChart websiteId={website.id} /> <EventsChart websiteId={website.id} />
<EventsChart websiteId={website.id} /> </div>
</Column>
</Row>
</> </>
)} )}
</Page> </Page>

View File

@ -1,5 +1,5 @@
import { useState } from 'react'; import { useState } from 'react';
import { GridRow, GridColumn } from 'components/layout/Grid'; import { Grid, GridRow } from 'components/layout/Grid';
import PagesTable from 'components/metrics/PagesTable'; import PagesTable from 'components/metrics/PagesTable';
import ReferrersTable from 'components/metrics/ReferrersTable'; import ReferrersTable from 'components/metrics/ReferrersTable';
import BrowsersTable from 'components/metrics/BrowsersTable'; import BrowsersTable from 'components/metrics/BrowsersTable';
@ -18,42 +18,24 @@ export default function WebsiteTableView({ websiteId }) {
}; };
return ( return (
<> <Grid>
<GridRow> <GridRow columns="two">
<GridColumn variant="two"> <PagesTable {...tableProps} />
<PagesTable {...tableProps} /> <ReferrersTable {...tableProps} />
</GridColumn>
<GridColumn variant="two">
<ReferrersTable {...tableProps} />
</GridColumn>
</GridRow> </GridRow>
<GridRow> <GridRow columns="three">
<GridColumn variant="three"> <BrowsersTable {...tableProps} />
<BrowsersTable {...tableProps} /> <OSTable {...tableProps} />
</GridColumn> <DevicesTable {...tableProps} />
<GridColumn variant="three">
<OSTable {...tableProps} />
</GridColumn>
<GridColumn variant="three">
<DevicesTable {...tableProps} />
</GridColumn>
</GridRow> </GridRow>
<GridRow> <GridRow columns="two-one">
<GridColumn xs={12} sm={12} md={12} defaultSize={8}> <WorldMap data={countryData} />
<WorldMap data={countryData} /> <CountriesTable {...tableProps} onDataLoad={setCountryData} />
</GridColumn>
<GridColumn xs={12} sm={12} md={12} defaultSize={4}>
<CountriesTable {...tableProps} onDataLoad={setCountryData} />
</GridColumn>
</GridRow> </GridRow>
<GridRow> <GridRow columns="one-two">
<GridColumn xs={12} sm={12} md={12} lg={4} defaultSize={4}> <EventsTable {...tableProps} />
<EventsTable {...tableProps} /> <EventsChart websiteId={websiteId} />
</GridColumn>
<GridColumn xs={12} sm={12} md={12} lg={8} defaultSize={8}>
<EventsChart websiteId={websiteId} />
</GridColumn>
</GridRow> </GridRow>
</> </Grid>
); );
} }

View File

@ -1,35 +0,0 @@
.col {
display: flex;
flex-direction: column;
}
.row {
border-top: 1px solid var(--base300);
min-height: 430px;
}
.row > .col {
border-left: 1px solid var(--base300);
padding: 20px;
}
.row > .col:first-child {
border-left: 0;
padding-left: 0;
}
.row > .col:last-child {
padding-right: 0;
}
@media only screen and (max-width: 992px) {
.row {
border: 0;
}
.row > .col {
border-top: 1px solid var(--base300);
border-left: 0;
padding: 20px 0;
}
}

View File

@ -1,6 +1,6 @@
'use client'; 'use client';
import classNames from 'classnames'; import classNames from 'classnames';
import { Row, Column, Text, Button, Icon } from 'react-basics'; import { Text, Button, Icon } from 'react-basics';
import Link from 'next/link'; import Link from 'next/link';
import { usePathname } from 'next/navigation'; import { usePathname } from 'next/navigation';
import Favicon from 'components/common/Favicon'; import Favicon from 'components/common/Favicon';
@ -39,17 +39,19 @@ export function WebsiteHeader({ websiteId, showLinks = true, children }) {
]; ];
return ( return (
<Row className={styles.header} justifyContent="center"> <div className={styles.header}>
<Column className={styles.title} variant="two"> <div className={styles.title}>
<Favicon domain={domain} /> <Favicon domain={domain} />
<Text>{name}</Text> <Text>{name}</Text>
<ActiveUsers websiteId={websiteId} /> <ActiveUsers websiteId={websiteId} />
</Column> </div>
<Column className={styles.actions} variant="two"> <div className={styles.actions}>
{showLinks && ( {showLinks && (
<div className={styles.links}> <div className={styles.links}>
{links.map(({ label, icon, path }) => { {links.map(({ label, icon, path }) => {
const selected = path ? pathname.endsWith(path) : pathname === '/websites/[id]'; const selected = path
? pathname.endsWith(path)
: pathname.match(/^\/websites\/[\w-]+$/);
return ( return (
<Link key={label} href={`/websites/${websiteId}${path}`} shallow={true}> <Link key={label} href={`/websites/${websiteId}${path}`} shallow={true}>
@ -68,8 +70,8 @@ export function WebsiteHeader({ websiteId, showLinks = true, children }) {
</div> </div>
)} )}
{children} {children}
</Column> </div>
</Row> </div>
); );
} }

View File

@ -1,6 +1,6 @@
.header { .header {
display: flex; display: grid;
flex-direction: row; grid-template-columns: 1fr max-content;
align-items: center; align-items: center;
} }
@ -35,6 +35,10 @@
} }
@media only screen and (max-width: 768px) { @media only screen and (max-width: 768px) {
.header {
grid-template-columns: 1fr;
}
.links { .links {
justify-content: space-evenly; justify-content: space-evenly;
flex: 1; flex: 1;
@ -49,7 +53,7 @@
.icon, .icon,
.icon svg { .icon svg {
width: 30px; width: 20px;
height: 30px; height: 20px;
} }
} }

View File

@ -6,7 +6,7 @@ import MetricsBar from 'components/metrics/MetricsBar';
import FilterSelectForm from '../../reports/FilterSelectForm'; import FilterSelectForm from '../../reports/FilterSelectForm';
import PopupForm from '../../reports/PopupForm'; import PopupForm from '../../reports/PopupForm';
import { formatShortTime } from 'lib/format'; import { formatShortTime } from 'lib/format';
import { Button, Column, Icon, Icons, Popup, PopupTrigger, Row } from 'react-basics'; import { Button, Icon, Icons, Popup, PopupTrigger } 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, sticky }) {
@ -98,72 +98,64 @@ export function WebsiteMetricsBar({ websiteId, showFilter = true, sticky }) {
}; };
return ( return (
<Row <div
ref={ref} ref={ref}
className={classNames(styles.container, { className={classNames(styles.container, {
[styles.sticky]: sticky, [styles.sticky]: sticky,
[styles.isSticky]: isSticky, [styles.isSticky]: isSticky,
})} })}
> >
<Column defaultSize={12} xl={8}> <MetricsBar isLoading={isLoading} isFetched={isFetched} error={error}>
<MetricsBar isLoading={isLoading} isFetched={isFetched} error={error}> {pageviews && uniques && (
{!error && isFetched && ( <>
<> <MetricCard
<MetricCard label={formatMessage(labels.views)}
className={styles.card} value={pageviews.value}
label={formatMessage(labels.views)} change={pageviews.change}
value={pageviews.value} />
change={pageviews.change} <MetricCard
/> label={formatMessage(labels.visitors)}
<MetricCard value={uniques.value}
className={styles.card} change={uniques.change}
label={formatMessage(labels.visitors)} />
value={uniques.value} <MetricCard
change={uniques.change} className={styles.card}
/> label={formatMessage(labels.bounceRate)}
<MetricCard value={uniques.value ? (num / uniques.value) * 100 : 0}
className={styles.card} change={
label={formatMessage(labels.bounceRate)} uniques.value && uniques.change
value={uniques.value ? (num / uniques.value) * 100 : 0} ? (num / uniques.value) * 100 -
change={ (Math.min(diffs.uniques, diffs.bounces) / diffs.uniques) * 100 || 0
uniques.value && uniques.change : 0
? (num / uniques.value) * 100 - }
(Math.min(diffs.uniques, diffs.bounces) / diffs.uniques) * 100 || 0 format={n => Number(n).toFixed(0) + '%'}
: 0 reverseColors
} />
format={n => Number(n).toFixed(0) + '%'} <MetricCard
reverseColors className={styles.card}
/> label={formatMessage(labels.averageVisitTime)}
<MetricCard value={
className={styles.card} totaltime.value && pageviews.value
label={formatMessage(labels.averageVisitTime)} ? totaltime.value / (pageviews.value - bounces.value)
value={ : 0
totaltime.value && pageviews.value }
? totaltime.value / (pageviews.value - bounces.value) change={
: 0 totaltime.value && pageviews.value
} ? (diffs.totaltime / (diffs.pageviews - diffs.bounces) -
change={ totaltime.value / (pageviews.value - bounces.value)) *
totaltime.value && pageviews.value -1 || 0
? (diffs.totaltime / (diffs.pageviews - diffs.bounces) - : 0
totaltime.value / (pageviews.value - bounces.value)) * }
-1 || 0 format={n => `${n < 0 ? '-' : ''}${formatShortTime(Math.abs(~~n), ['m', 's'], ' ')}`}
: 0 />
} </>
format={n => )}
`${n < 0 ? '-' : ''}${formatShortTime(Math.abs(~~n), ['m', 's'], ' ')}` </MetricsBar>
} <div className={styles.actions}>
/> {showFilter && <WebsiteFilterButton />}
</> <WebsiteDateFilter websiteId={websiteId} />
)} </div>
</MetricsBar> </div>
</Column>
<Column defaultSize={12} xl={4}>
<div className={styles.actions}>
{showFilter && <WebsiteFilterButton />}
<WebsiteDateFilter websiteId={websiteId} />
</div>
</Column>
</Row>
); );
} }

View File

@ -1,12 +1,12 @@
.container { .container {
display: flex; display: grid;
grid-template-columns: 1fr max-content;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 10px 0;
min-height: 90px;
margin-bottom: 20px;
background: var(--base50); background: var(--base50);
z-index: var(--z-index-above); z-index: var(--z-index-above);
min-height: 120px;
padding-bottom: 20px;
} }
.actions { .actions {
@ -18,8 +18,12 @@
} }
@media only screen and (max-width: 1200px) { @media only screen and (max-width: 1200px) {
.container {
grid-template-columns: 1fr;
}
.actions { .actions {
margin-top: 40px; margin: 20px 0;
} }
} }
@ -30,6 +34,7 @@
} }
.isSticky { .isSticky {
padding: 10px 0;
border-bottom: 1px solid var(--base300); border-bottom: 1px solid var(--base300);
} }
} }

View File

@ -1,4 +1,3 @@
import { Column, Row } from 'react-basics';
import { useApi, useDateRange } from 'components/hooks'; import { useApi, useDateRange } from 'components/hooks';
import MetricCard from 'components/metrics/MetricCard'; import MetricCard from 'components/metrics/MetricCard';
import useMessages from 'components/hooks/useMessages'; import useMessages from 'components/hooks/useMessages';
@ -23,36 +22,16 @@ export function EventDataMetricsBar({ websiteId }) {
); );
return ( return (
<Row className={styles.row}> <div className={styles.container}>
<Column defaultSize={12} xl={8}> <MetricsBar isLoading={isLoading} isFetched={isFetched} error={error}>
<MetricsBar isLoading={isLoading} isFetched={isFetched} error={error}> <MetricCard label={formatMessage(labels.events)} value={data?.events} />
{!error && isFetched && ( <MetricCard label={formatMessage(labels.fields)} value={data?.fields} />
<> <MetricCard label={formatMessage(labels.totalRecords)} value={data?.records} />
<MetricCard </MetricsBar>
className={styles.card} <div className={styles.actions}>
label={formatMessage(labels.events)} <WebsiteDateFilter websiteId={websiteId} />
value={data?.events} </div>
/> </div>
<MetricCard
className={styles.card}
label={formatMessage(labels.fields)}
value={data?.fields}
/>
<MetricCard
className={styles.card}
label={formatMessage(labels.totalRecords)}
value={data?.records}
/>
</>
)}
</MetricsBar>
</Column>
<Column defaultSize={12} xl={4}>
<div className={styles.actions}>
<WebsiteDateFilter websiteId={websiteId} />
</div>
</Column>
</Row>
); );
} }

View File

@ -1,5 +1,6 @@
.container { .container {
display: flex; display: grid;
grid-template-columns: 1fr 1fr;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 10px 0; padding: 10px 0;
@ -9,12 +10,6 @@
z-index: var(--z-index-above); z-index: var(--z-index-above);
} }
.metrics {
display: flex;
flex-direction: row;
align-items: center;
}
.actions { .actions {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@ -23,24 +18,9 @@
flex: 1; flex: 1;
} }
.bar {
display: flex;
cursor: pointer;
min-height: 110px;
gap: 20px;
flex-wrap: wrap;
}
.card {
justify-self: flex-start;
}
@media only screen and (max-width: 992px) { @media only screen and (max-width: 992px) {
.card { .container {
flex-basis: calc(50% - 20px); grid-template-columns: 1fr;
grid-template-rows: 1fr 1fr;
} }
} }
.row {
border-bottom: 1px solid var(--border-color);
}

View File

@ -2,7 +2,7 @@
import { useMemo, useState, useEffect } from 'react'; import { useMemo, useState, useEffect } from 'react';
import { subMinutes, startOfMinute } from 'date-fns'; import { subMinutes, startOfMinute } from 'date-fns';
import firstBy from 'thenby'; import firstBy from 'thenby';
import { GridRow, GridColumn } from 'components/layout/Grid'; import { Grid, GridRow } from 'components/layout/Grid';
import Page from 'components/layout/Page'; import Page from 'components/layout/Page';
import RealtimeChart from 'components/metrics/RealtimeChart'; import RealtimeChart from 'components/metrics/RealtimeChart';
import WorldMap from 'components/common/WorldMap'; import WorldMap from 'components/common/WorldMap';
@ -99,22 +99,16 @@ export function Realtime({ websiteId }) {
<WebsiteHeader websiteId={websiteId} /> <WebsiteHeader websiteId={websiteId} />
<RealtimeHeader websiteId={websiteId} data={currentData} /> <RealtimeHeader websiteId={websiteId} data={currentData} />
<RealtimeChart className={styles.chart} data={realtimeData} unit="minute" /> <RealtimeChart className={styles.chart} data={realtimeData} unit="minute" />
<GridRow> <Grid>
<GridColumn xs={12} sm={12} md={12} lg={4} xl={4}> <GridRow columns="one-two">
<RealtimeUrls websiteId={websiteId} websiteDomain={website?.domain} data={realtimeData} /> <RealtimeUrls websiteId={websiteId} websiteDomain={website?.domain} data={realtimeData} />
</GridColumn>
<GridColumn xs={12} sm={12} md={12} lg={8} xl={8}>
<RealtimeLog websiteId={websiteId} websiteDomain={website?.domain} data={realtimeData} /> <RealtimeLog websiteId={websiteId} websiteDomain={website?.domain} data={realtimeData} />
</GridColumn> </GridRow>
</GridRow> <GridRow columns="one-two">
<GridRow>
<GridColumn xs={12} lg={4}>
<RealtimeCountries data={realtimeData?.countries} /> <RealtimeCountries data={realtimeData?.countries} />
</GridColumn>
<GridColumn xs={12} lg={8}>
<WorldMap data={realtimeData?.countries} /> <WorldMap data={realtimeData?.countries} />
</GridColumn> </GridRow>
</GridRow> </Grid>
</> </>
); );
} }

View File

@ -1,117 +0,0 @@
import { useState, useEffect, useMemo } from 'react';
import { subMinutes, startOfMinute } from 'date-fns';
import firstBy from 'thenby';
import { GridRow, GridColumn } from 'components/layout/Grid';
import Page from 'components/layout/Page';
import RealtimeChart from 'components/metrics/RealtimeChart';
import WorldMap from 'components/common/WorldMap';
import RealtimeLog from './RealtimeLog';
import RealtimeHeader from './RealtimeHeader';
import RealtimeUrls from './RealtimeUrls';
import RealtimeCountries from './RealtimeCountries';
import WebsiteHeader from '../WebsiteHeader';
import useApi from 'components/hooks/useApi';
import { percentFilter } from 'lib/filters';
import { REALTIME_RANGE, REALTIME_INTERVAL } from 'lib/constants';
import { useWebsite } from 'components/hooks';
import styles from './Realtime.module.css';
function mergeData(state = [], data = [], time) {
const ids = state.map(({ __id }) => __id);
return state
.concat(data.filter(({ __id }) => !ids.includes(__id)))
.filter(({ timestamp }) => timestamp >= time);
}
export function RealtimePage({ websiteId }) {
const [currentData, setCurrentData] = useState();
const { get, useQuery } = useApi();
const { data: website } = useWebsite(websiteId);
const { data, isLoading, error } = useQuery(
['realtime', websiteId],
() => get(`/realtime/${websiteId}`, { startAt: currentData?.timestamp || 0 }),
{
enabled: !!(websiteId && website),
refetchInterval: REALTIME_INTERVAL,
cache: false,
},
);
useEffect(() => {
if (data) {
const date = subMinutes(startOfMinute(new Date()), REALTIME_RANGE);
const time = date.getTime();
setCurrentData(state => ({
pageviews: mergeData(state?.pageviews, data.pageviews, time),
sessions: mergeData(state?.sessions, data.sessions, time),
events: mergeData(state?.events, data.events, time),
timestamp: data.timestamp,
}));
}
}, [data]);
const realtimeData = useMemo(() => {
if (!currentData) {
return { pageviews: [], sessions: [], events: [], countries: [], visitors: [] };
}
currentData.countries = percentFilter(
currentData.sessions
.reduce((arr, data) => {
if (!arr.find(({ id }) => id === data.id)) {
return arr.concat(data);
}
return arr;
}, [])
.reduce((arr, { country }) => {
if (country) {
const row = arr.find(({ x }) => x === country);
if (!row) {
arr.push({ x: country, y: 1 });
} else {
row.y += 1;
}
}
return arr;
}, [])
.sort(firstBy('y', -1)),
);
currentData.visitors = currentData.sessions.reduce((arr, val) => {
if (!arr.find(({ id }) => id === val.id)) {
return arr.concat(val);
}
return arr;
}, []);
return currentData;
}, [currentData]);
return (
<Page loading={isLoading} error={error}>
<WebsiteHeader websiteId={websiteId} />
<RealtimeHeader websiteId={websiteId} data={currentData} />
<RealtimeChart className={styles.chart} data={realtimeData} unit="minute" />
<GridRow>
<GridColumn xs={12} sm={12} md={12} lg={4} xl={4}>
<RealtimeUrls websiteId={websiteId} websiteDomain={website?.domain} data={realtimeData} />
</GridColumn>
<GridColumn xs={12} sm={12} md={12} lg={8} xl={8}>
<RealtimeLog websiteId={websiteId} websiteDomain={website?.domain} data={realtimeData} />
</GridColumn>
</GridRow>
<GridRow>
<GridColumn xs={12} lg={4}>
<RealtimeCountries data={realtimeData?.countries} />
</GridColumn>
<GridColumn xs={12} lg={8}>
<WorldMap data={realtimeData?.countries} />
</GridColumn>
</GridRow>
</Page>
);
}
export default RealtimePage;

View File

@ -1,7 +1,7 @@
'use client'; 'use client';
import { useEffect, useCallback, useState } from 'react'; import { useEffect, useCallback, useState } from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { Button, Row, Column } from 'react-basics'; import { Button } from 'react-basics';
import { setItem } from 'next-basics'; import { setItem } from 'next-basics';
import useStore, { checkVersion } from 'store/version'; import useStore, { checkVersion } from 'store/version';
import { REPO_URL, VERSION_CHECK } from 'lib/constants'; import { REPO_URL, VERSION_CHECK } from 'lib/constants';
@ -47,17 +47,17 @@ export function UpdateNotice({ user, config }) {
} }
return createPortal( return createPortal(
<Row className={styles.notice}> <div className={styles.notice}>
<Column variant="two" className={styles.message}> <div className={styles.message}>
{formatMessage(messages.newVersionAvailable, { version: `v${latest}` })} {formatMessage(messages.newVersionAvailable, { version: `v${latest}` })}
</Column> </div>
<Column className={styles.buttons}> <div className={styles.buttons}>
<Button variant="primary" onClick={handleViewClick}> <Button variant="primary" onClick={handleViewClick}>
{formatMessage(labels.viewDetails)} {formatMessage(labels.viewDetails)}
</Button> </Button>
<Button onClick={handleDismissClick}>{formatMessage(labels.dismiss)}</Button> <Button onClick={handleDismissClick}>{formatMessage(labels.dismiss)}</Button>
</Column> </div>
</Row>, </div>,
document.body, document.body,
); );
} }

View File

@ -1,7 +1,7 @@
import useDateRange from 'components/hooks/useDateRange'; import useDateRange from 'components/hooks/useDateRange';
import { isAfter } from 'date-fns'; import { isAfter } from 'date-fns';
import { incrementDateRange } from 'lib/date'; import { incrementDateRange } from 'lib/date';
import { Button, Flexbox, Icon, Icons } from 'react-basics'; import { Button, Icon, Icons } from 'react-basics';
import DateFilter from './DateFilter'; import DateFilter from './DateFilter';
import styles from './WebsiteDateFilter.module.css'; import styles from './WebsiteDateFilter.module.css';
@ -22,9 +22,9 @@ export function WebsiteDateFilter({ websiteId }) {
}; };
return ( return (
<Flexbox justifyContent="center" gap={10}> <div className={styles.container}>
{value !== 'all' && selectedUnit && ( {value !== 'all' && selectedUnit && (
<Flexbox justifyContent="center" className={styles.buttons}> <div className={styles.buttons}>
<Button onClick={() => handleIncrement(1)}> <Button onClick={() => handleIncrement(1)}>
<Icon rotate={90}> <Icon rotate={90}>
<Icons.ChevronDown /> <Icons.ChevronDown />
@ -35,7 +35,7 @@ export function WebsiteDateFilter({ websiteId }) {
<Icons.ChevronDown /> <Icons.ChevronDown />
</Icon> </Icon>
</Button> </Button>
</Flexbox> </div>
)} )}
<DateFilter <DateFilter
className={styles.dropdown} className={styles.dropdown}
@ -46,7 +46,7 @@ export function WebsiteDateFilter({ websiteId }) {
onChange={handleChange} onChange={handleChange}
showAllTime={true} showAllTime={true}
/> />
</Flexbox> </div>
); );
} }

View File

@ -1,7 +1,17 @@
.container {
display: flex;
align-items: center;
gap: 10px;
}
.dropdown { .dropdown {
min-width: 200px; min-width: 200px;
} }
.buttons {
display: flex;
}
.buttons button:first-child { .buttons button:first-child {
border-top-right-radius: 0; border-top-right-radius: 0;
border-bottom-right-radius: 0; border-bottom-right-radius: 0;

View File

@ -1,13 +1,18 @@
import { Row, Column } from 'react-basics';
import classNames from 'classnames'; import classNames from 'classnames';
import styles from './Grid.module.css'; import styles from './Grid.module.css';
import { mapChildren } from 'react-basics';
export function Grid({ className, ...otherProps }) {
return <div {...otherProps} className={classNames(styles.grid, className)} />;
}
export function GridRow(props) { export function GridRow(props) {
const { className, ...otherProps } = props; const { columns = 'two', className, children, ...otherProps } = props;
return <Row {...otherProps} className={classNames(styles.row, className)} />; return (
} <div {...otherProps} className={classNames(styles.row, className)}>
{mapChildren(children, child => {
export function GridColumn(props) { return <div className={classNames(styles.col, { [styles[columns]]: true })}>{child}</div>;
const { className, ...otherProps } = props; })}
return <Column {...otherProps} className={classNames(styles.col, className)} />; </div>
);
} }

View File

@ -1,27 +1,52 @@
.col { .grid {
display: flex; display: grid;
flex-direction: column;
padding: 20px;
} }
.row { .row {
display: grid;
grid-template-columns: repeat(6, 1fr);
border-top: 1px solid var(--base300); border-top: 1px solid var(--base300);
min-height: 430px;
} }
.row > .col { .col {
padding: 20px;
min-height: 430px;
border-inline-start: 1px solid var(--base300); border-inline-start: 1px solid var(--base300);
} }
.row > .col:first-child { .col:first-child {
border-inline-start: 0; border-inline-start: 0;
padding-inline-start: 0; padding-inline-start: 0;
} }
.row > .col:last-child { .col:last-child {
padding-inline-end: 0; padding-inline-end: 0;
} }
.col.two {
grid-column: span 3;
}
.col.three {
grid-column: span 2;
}
.col.two-one:first-child {
grid-column: span 4;
}
.col.two-one:last-child {
grid-column: span 2;
}
.col.one-two:first-child {
grid-column: span 2;
}
.col.one-two:last-child {
grid-column: span 4;
}
@media only screen and (max-width: 992px) { @media only screen and (max-width: 992px) {
.row { .row {
border: 0; border: 0;
@ -33,4 +58,11 @@
border-inline-end: 0; border-inline-end: 0;
padding: 20px 0; padding: 20px 0;
} }
.col.two,
.col.three,
.col.one-two,
.col.two-one {
grid-column: span 6 !important;
}
} }

View File

@ -4,7 +4,6 @@
flex-direction: column; flex-direction: column;
background: var(--base50); background: var(--base50);
position: relative; position: relative;
height: 100%;
max-width: 1320px; max-width: 1320px;
margin: 0 auto; margin: 0 auto;
padding: 0 20px; padding: 0 20px;

View File

@ -145,7 +145,7 @@ export function BarChart({
}, [datasets, unit, theme, animationDuration, locale]); }, [datasets, unit, theme, animationDuration, locale]);
return ( return (
<> <div className={styles.container}>
<div className={classNames(styles.chart, className)}> <div className={classNames(styles.chart, className)}>
{loading && <Loading position="page" icon="dots" />} {loading && <Loading position="page" icon="dots" />}
<canvas ref={canvas} /> <canvas ref={canvas} />
@ -156,7 +156,7 @@ export function BarChart({
<div className={styles.tooltip}>{tooltip}</div> <div className={styles.tooltip}>{tooltip}</div>
</HoverTooltip> </HoverTooltip>
)} )}
</> </div>
); );
} }

View File

@ -1,3 +1,7 @@
.container {
display: grid;
}
.chart { .chart {
position: relative; position: relative;
height: 400px; height: 400px;

View File

@ -1,8 +1,8 @@
import { useState } from 'react'; import { useState } from 'react';
import { Loading, cloneChildren } from 'react-basics'; import { Loading, cloneChildren } from 'react-basics';
import ErrorMessage from 'components/common/ErrorMessage'; import ErrorMessage from 'components/common/ErrorMessage';
import styles from './MetricsBar.module.css';
import { formatLongNumber, formatNumber } from 'lib/format'; import { formatLongNumber, formatNumber } from 'lib/format';
import styles from './MetricsBar.module.css';
export function MetricsBar({ children, isLoading, isFetched, error }) { export function MetricsBar({ children, isLoading, isFetched, error }) {
const [format, setFormat] = useState(true); const [format, setFormat] = useState(true);
@ -19,9 +19,12 @@ export function MetricsBar({ children, isLoading, isFetched, error }) {
<div className={styles.bar} onClick={handleSetFormat}> <div className={styles.bar} onClick={handleSetFormat}>
{isLoading && !isFetched && <Loading icon="dots" />} {isLoading && !isFetched && <Loading icon="dots" />}
{error && <ErrorMessage />} {error && <ErrorMessage />}
{cloneChildren(children, child => { {!isLoading &&
return { format: child.props.format || formatFunc }; !error &&
})} isFetched &&
cloneChildren(children, child => {
return { format: child.props.format || formatFunc };
})}
</div> </div>
); );
} }

View File

@ -1,18 +1,11 @@
.bar { .bar {
display: flex; display: grid;
flex-direction: row; grid-template-columns: repeat(auto-fit, minmax(140px, max-content));
cursor: pointer;
min-height: 110px;
gap: 20px; gap: 20px;
flex-wrap: wrap;
} }
.card { @media screen and (max-width: 768px) {
justify-self: flex-start; .bar {
} grid-template-columns: 1fr 1fr;
@media only screen and (max-width: 992px) {
.card {
flex-basis: calc(50% - 20px);
} }
} }

View File

@ -52,6 +52,8 @@ main {
svg { svg {
shape-rendering: geometricPrecision; shape-rendering: geometricPrecision;
width: 10px;
height: 10px;
} }
#__next { #__next {