mirror of
https://github.com/kremalicious/umami.git
synced 2025-01-04 19:15:09 +01:00
Added grid layout components.
This commit is contained in:
parent
e30f2dfb44
commit
910481e629
31
components/layout/GridLayout.js
Normal file
31
components/layout/GridLayout.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import styles from './GridLayout.module.css';
|
||||||
|
|
||||||
|
export default function GridLayout({ className, children }) {
|
||||||
|
return <div className={classNames(styles.grid, className)}>{children}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GridRow = ({ className, children }) => {
|
||||||
|
return <div className={classNames(styles.row, className, 'row')}>{children}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GridColumn = ({ xs, sm, md, lg, xl, className, children }) => {
|
||||||
|
const classes = [];
|
||||||
|
|
||||||
|
classes.push(xs ? `col-${xs}` : 'col');
|
||||||
|
|
||||||
|
if (sm) {
|
||||||
|
classes.push(`col-sm-${sm}`);
|
||||||
|
}
|
||||||
|
if (md) {
|
||||||
|
classes.push(`col-md-${md}`);
|
||||||
|
}
|
||||||
|
if (lg) {
|
||||||
|
classes.push(`col-lg-${lg}`);
|
||||||
|
}
|
||||||
|
if (xl) {
|
||||||
|
classes.push(`col-lg-${xl}`);
|
||||||
|
}
|
||||||
|
return <div className={classNames(styles.col, classes, className)}>{children}</div>;
|
||||||
|
};
|
40
components/layout/GridLayout.module.css
Normal file
40
components/layout/GridLayout.module.css
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
.grid {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
border-top: 1px solid var(--gray300);
|
||||||
|
min-height: 430px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row > .col {
|
||||||
|
border-left: 1px solid var(--gray300);
|
||||||
|
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(--gray300);
|
||||||
|
border-left: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
@ -8,7 +8,7 @@ import useTimezone from 'hooks/useTimezone';
|
|||||||
import usePageQuery from 'hooks/usePageQuery';
|
import usePageQuery from 'hooks/usePageQuery';
|
||||||
import { EVENT_COLORS } from 'lib/constants';
|
import { EVENT_COLORS } from 'lib/constants';
|
||||||
|
|
||||||
export default function EventsChart({ websiteId, token }) {
|
export default function EventsChart({ websiteId, className, token }) {
|
||||||
const [dateRange] = useDateRange(websiteId);
|
const [dateRange] = useDateRange(websiteId);
|
||||||
const { startDate, endDate, unit, modified } = dateRange;
|
const { startDate, endDate, unit, modified } = dateRange;
|
||||||
const [timezone] = useTimezone();
|
const [timezone] = useTimezone();
|
||||||
@ -79,6 +79,7 @@ export default function EventsChart({ websiteId, token }) {
|
|||||||
return (
|
return (
|
||||||
<BarChart
|
<BarChart
|
||||||
chartId={`events-${websiteId}`}
|
chartId={`events-${websiteId}`}
|
||||||
|
className={className}
|
||||||
datasets={datasets}
|
datasets={datasets}
|
||||||
unit={unit}
|
unit={unit}
|
||||||
records={getDateLength(startDate, endDate, unit)}
|
records={getDateLength(startDate, endDate, unit)}
|
||||||
|
@ -119,6 +119,9 @@ export default function RealtimeLog({ data, websites }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.log}>
|
<div className={styles.log}>
|
||||||
|
<div className={styles.header}>
|
||||||
|
<FormattedMessage id="label.realtime-logs" defaultMessage="Realtime logs" />
|
||||||
|
</div>
|
||||||
<Table
|
<Table
|
||||||
className={styles.table}
|
className={styles.table}
|
||||||
bodyClassName={styles.body}
|
bodyClassName={styles.body}
|
||||||
@ -126,7 +129,7 @@ export default function RealtimeLog({ data, websites }) {
|
|||||||
rows={logs}
|
rows={logs}
|
||||||
showHeader={false}
|
showHeader={false}
|
||||||
>
|
>
|
||||||
<FixedSizeList height={300} itemCount={logs.length} itemSize={46}>
|
<FixedSizeList height={400} itemCount={logs.length} itemSize={46}>
|
||||||
{Row}
|
{Row}
|
||||||
</FixedSizeList>
|
</FixedSizeList>
|
||||||
</Table>
|
</Table>
|
||||||
|
@ -2,13 +2,20 @@
|
|||||||
font-size: var(--font-size-xsmall);
|
font-size: var(--font-size-xsmall);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
line-height: 40px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
.row {
|
.row {
|
||||||
display: flex;
|
display: flex;
|
||||||
border-bottom: 1px solid var(--gray300);
|
border-bottom: 1px solid var(--gray300);
|
||||||
}
|
}
|
||||||
|
|
||||||
.body {
|
.body {
|
||||||
height: 600px;
|
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import React, { useState, useEffect, useMemo } from 'react';
|
import React, { useState, useEffect, useMemo } from 'react';
|
||||||
import classNames from 'classnames';
|
|
||||||
import { subMinutes, startOfMinute } from 'date-fns';
|
import { subMinutes, startOfMinute } from 'date-fns';
|
||||||
import Page from 'components/layout/Page';
|
import Page from 'components/layout/Page';
|
||||||
import useFetch from 'hooks/useFetch';
|
import GridLayout, { GridRow, GridColumn } from 'components/layout/GridLayout';
|
||||||
import RealtimeChart from '../metrics/RealtimeChart';
|
import RealtimeChart from '../metrics/RealtimeChart';
|
||||||
import RealtimeLog from '../metrics/RealtimeLog';
|
import RealtimeLog from '../metrics/RealtimeLog';
|
||||||
import styles from './RealtimeDashboard.module.css';
|
import styles from './RealtimeDashboard.module.css';
|
||||||
import RealtimeHeader from '../metrics/RealtimeHeader';
|
import RealtimeHeader from '../metrics/RealtimeHeader';
|
||||||
|
import useFetch from 'hooks/useFetch';
|
||||||
|
|
||||||
const REALTIME_RANGE = 30;
|
const REALTIME_RANGE = 30;
|
||||||
const REALTIME_INTERVAL = 5000;
|
const REALTIME_INTERVAL = 5000;
|
||||||
@ -36,10 +36,22 @@ export default function RealtimeDashboard() {
|
|||||||
const realtimeData = useMemo(() => {
|
const realtimeData = useMemo(() => {
|
||||||
if (websiteId) {
|
if (websiteId) {
|
||||||
const { pageviews, sessions, events, ...props } = data;
|
const { pageviews, sessions, events, ...props } = data;
|
||||||
|
const countries = sessions.reduce((obj, { country }) => {
|
||||||
|
if (country) {
|
||||||
|
if (!obj[country]) {
|
||||||
|
obj[country] = 1;
|
||||||
|
} else {
|
||||||
|
obj[country] += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}, {});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pageviews: filterWebsite(pageviews, websiteId),
|
pageviews: filterWebsite(pageviews, websiteId),
|
||||||
sessions: filterWebsite(sessions, websiteId),
|
sessions: filterWebsite(sessions, websiteId),
|
||||||
events: filterWebsite(events, websiteId),
|
events: filterWebsite(events, websiteId),
|
||||||
|
countries,
|
||||||
...props,
|
...props,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -83,12 +95,24 @@ export default function RealtimeDashboard() {
|
|||||||
records={REALTIME_RANGE}
|
records={REALTIME_RANGE}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={classNames(styles.tables, 'row')}>
|
<GridLayout>
|
||||||
<div className="col-12 col-lg-8">
|
<GridRow>
|
||||||
<RealtimeLog data={realtimeData} websites={websites} />
|
<GridColumn xs={12} lg={8}>
|
||||||
</div>
|
<RealtimeLog data={realtimeData} websites={websites} />
|
||||||
<div className="col-12 col-lg-4">hi.</div>
|
</GridColumn>
|
||||||
</div>
|
<GridColumn xs={12} lg={4}>
|
||||||
|
x
|
||||||
|
</GridColumn>
|
||||||
|
</GridRow>
|
||||||
|
<GridRow>
|
||||||
|
<GridColumn xs={12} lg={4}>
|
||||||
|
x
|
||||||
|
</GridColumn>
|
||||||
|
<GridColumn xs={12} lg={8}>
|
||||||
|
x
|
||||||
|
</GridColumn>
|
||||||
|
</GridRow>
|
||||||
|
</GridLayout>
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import classNames from 'classnames';
|
|||||||
import WebsiteChart from 'components/metrics/WebsiteChart';
|
import WebsiteChart from 'components/metrics/WebsiteChart';
|
||||||
import WorldMap from 'components/common/WorldMap';
|
import WorldMap from 'components/common/WorldMap';
|
||||||
import Page from 'components/layout/Page';
|
import Page from 'components/layout/Page';
|
||||||
|
import GridLayout, { GridRow, GridColumn } from 'components/layout/GridLayout';
|
||||||
import MenuLayout from 'components/layout/MenuLayout';
|
import MenuLayout from 'components/layout/MenuLayout';
|
||||||
import Link from 'components/common/Link';
|
import Link from 'components/common/Link';
|
||||||
import Loading from 'components/common/Loading';
|
import Loading from 'components/common/Loading';
|
||||||
@ -19,6 +20,7 @@ import EventsTable from '../metrics/EventsTable';
|
|||||||
import EventsChart from '../metrics/EventsChart';
|
import EventsChart from '../metrics/EventsChart';
|
||||||
import useFetch from 'hooks/useFetch';
|
import useFetch from 'hooks/useFetch';
|
||||||
import usePageQuery from 'hooks/usePageQuery';
|
import usePageQuery from 'hooks/usePageQuery';
|
||||||
|
import { DEFAULT_ANIMATION_DURATION } from 'lib/constants';
|
||||||
|
|
||||||
const views = {
|
const views = {
|
||||||
url: PagesTable,
|
url: PagesTable,
|
||||||
@ -100,7 +102,7 @@ export default function WebsiteDetails({ websiteId, token }) {
|
|||||||
|
|
||||||
function handleDataLoad() {
|
function handleDataLoad() {
|
||||||
if (!chartLoaded) {
|
if (!chartLoaded) {
|
||||||
setTimeout(() => setChartLoaded(true), 300);
|
setTimeout(() => setChartLoaded(true), DEFAULT_ANIMATION_DURATION);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,45 +126,43 @@ export default function WebsiteDetails({ websiteId, token }) {
|
|||||||
</div>
|
</div>
|
||||||
{!chartLoaded && <Loading />}
|
{!chartLoaded && <Loading />}
|
||||||
{chartLoaded && !view && (
|
{chartLoaded && !view && (
|
||||||
<>
|
<GridLayout>
|
||||||
<div className={classNames(styles.row, 'row')}>
|
<GridRow>
|
||||||
<div className="col-md-12 col-lg-6">
|
<GridColumn md={12} lg={6}>
|
||||||
<PagesTable {...tableProps} />
|
<PagesTable {...tableProps} />
|
||||||
</div>
|
</GridColumn>
|
||||||
<div className="col-md-12 col-lg-6">
|
<GridColumn md={12} lg={6}>
|
||||||
<ReferrersTable {...tableProps} />
|
<ReferrersTable {...tableProps} />
|
||||||
</div>
|
</GridColumn>
|
||||||
</div>
|
</GridRow>
|
||||||
<div className={classNames(styles.row, 'row')}>
|
<GridRow>
|
||||||
<div className="col-md-12 col-lg-4">
|
<GridColumn md={12} lg={4}>
|
||||||
<BrowsersTable {...tableProps} />
|
<BrowsersTable {...tableProps} />
|
||||||
</div>
|
</GridColumn>
|
||||||
<div className="col-md-12 col-lg-4">
|
<GridColumn md={12} lg={4}>
|
||||||
<OSTable {...tableProps} />
|
<OSTable {...tableProps} />
|
||||||
</div>
|
</GridColumn>
|
||||||
<div className="col-md-12 col-lg-4">
|
<GridColumn md={12} lg={4}>
|
||||||
<DevicesTable {...tableProps} />
|
<DevicesTable {...tableProps} />
|
||||||
</div>
|
</GridColumn>
|
||||||
</div>
|
</GridRow>
|
||||||
<div className={classNames(styles.row, 'row')}>
|
<GridRow>
|
||||||
<div className="col-12 col-md-12 col-lg-8">
|
<GridColumn xs={12} md={12} lg={8}>
|
||||||
<WorldMap data={countryData} />
|
<WorldMap data={countryData} />
|
||||||
</div>
|
</GridColumn>
|
||||||
<div className="col-12 col-md-12 col-lg-4">
|
<GridColumn xs={12} md={12} lg={4}>
|
||||||
<CountriesTable {...tableProps} onDataLoad={setCountryData} />
|
<CountriesTable {...tableProps} onDataLoad={setCountryData} />
|
||||||
</div>
|
</GridColumn>
|
||||||
</div>
|
</GridRow>
|
||||||
<div
|
<GridRow className={classNames({ [styles.hidden]: !eventsData?.length > 0 })}>
|
||||||
className={classNames(styles.row, 'row', { [styles.hidden]: !eventsData?.length > 0 })}
|
<GridColumn xs={12} md={12} lg={4}>
|
||||||
>
|
|
||||||
<div className="col-12 col-md-12 col-lg-4">
|
|
||||||
<EventsTable {...tableProps} onDataLoad={setEventsData} />
|
<EventsTable {...tableProps} onDataLoad={setEventsData} />
|
||||||
</div>
|
</GridColumn>
|
||||||
<div className="col-12 col-md-12 col-lg-8 pt-5 pb-5">
|
<GridColumn xs={12} md={12} lg={8}>
|
||||||
<EventsChart websiteId={websiteId} token={token} />
|
<EventsChart className={styles.eventschart} websiteId={websiteId} token={token} />
|
||||||
</div>
|
</GridColumn>
|
||||||
</div>
|
</GridRow>
|
||||||
</>
|
</GridLayout>
|
||||||
)}
|
)}
|
||||||
{view && (
|
{view && (
|
||||||
<MenuLayout
|
<MenuLayout
|
||||||
|
@ -26,37 +26,10 @@
|
|||||||
transform: rotate(180deg);
|
transform: rotate(180deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.row {
|
|
||||||
border-top: 1px solid var(--gray300);
|
|
||||||
min-height: 430px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row > [class*='col-'] {
|
|
||||||
border-left: 1px solid var(--gray300);
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row > [class*='col-']:first-child {
|
|
||||||
border-left: 0;
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row > [class*='col-']:last-child {
|
|
||||||
padding-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hidden {
|
.hidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 992px) {
|
.eventschart {
|
||||||
.row {
|
padding: 30px 0;
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row > [class*='col-'] {
|
|
||||||
border-top: 1px solid var(--gray300);
|
|
||||||
border-left: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,9 @@ h4,
|
|||||||
h5,
|
h5,
|
||||||
h6 {
|
h6 {
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
line-height: 30px;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
button,
|
button,
|
||||||
|
Loading…
Reference in New Issue
Block a user