mirror of
https://github.com/umami-software/umami.git
synced 2026-02-24 06:25:43 +01:00
New website nav.
This commit is contained in:
parent
5e6799a715
commit
a534c51b5e
38 changed files with 190 additions and 159 deletions
|
|
@ -0,0 +1,69 @@
|
|||
import { Text, DataTable, DataColumn } from '@umami/react-zen';
|
||||
import { useMessages, useResultQuery, useFormat, useFields } from '@/components/hooks';
|
||||
import { LoadingPanel } from '@/components/common/LoadingPanel';
|
||||
import { formatShortTime } from '@/lib/format';
|
||||
|
||||
export interface BreakdownProps {
|
||||
websiteId: string;
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
selectedFields: string[];
|
||||
}
|
||||
|
||||
export function Breakdown({ websiteId, selectedFields = [], startDate, endDate }: BreakdownProps) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { formatValue } = useFormat();
|
||||
const { fields } = useFields();
|
||||
const { data, error, isLoading } = useResultQuery<any>(
|
||||
'breakdown',
|
||||
{
|
||||
websiteId,
|
||||
startDate,
|
||||
endDate,
|
||||
fields: selectedFields,
|
||||
},
|
||||
{ enabled: !!selectedFields.length },
|
||||
);
|
||||
|
||||
return (
|
||||
<LoadingPanel data={data} isLoading={isLoading} error={error}>
|
||||
<DataTable data={data}>
|
||||
{selectedFields.map(field => {
|
||||
return (
|
||||
<DataColumn key={field} id={field} label={fields.find(f => f.name === field)?.label}>
|
||||
{row => {
|
||||
const value = formatValue(row[field], field);
|
||||
return (
|
||||
<Text truncate title={value}>
|
||||
{value}
|
||||
</Text>
|
||||
);
|
||||
}}
|
||||
</DataColumn>
|
||||
);
|
||||
})}
|
||||
<DataColumn id="visitors" label={formatMessage(labels.visitors)} align="end">
|
||||
{row => row?.['visitors']?.toLocaleString()}
|
||||
</DataColumn>
|
||||
<DataColumn id="visits" label={formatMessage(labels.visits)} align="end">
|
||||
{row => row?.['visits']?.toLocaleString()}
|
||||
</DataColumn>
|
||||
<DataColumn id="views" label={formatMessage(labels.views)} align="end">
|
||||
{row => row?.['views']?.toLocaleString()}
|
||||
</DataColumn>
|
||||
<DataColumn id="bounceRate" label={formatMessage(labels.bounceRate)} align="end">
|
||||
{row => {
|
||||
const n = (Math.min(row?.['visits'], row?.['bounces']) / row?.['visits']) * 100;
|
||||
return Math.round(+n) + '%';
|
||||
}}
|
||||
</DataColumn>
|
||||
<DataColumn id="visitDuration" label={formatMessage(labels.visitDuration)} align="end">
|
||||
{row => {
|
||||
const n = (row?.['totaltime'] / row?.['visits']) * 100;
|
||||
return `${+n < 0 ? '-' : ''}${formatShortTime(Math.abs(~~n), ['m', 's'], ' ')}`;
|
||||
}}
|
||||
</DataColumn>
|
||||
</DataTable>
|
||||
</LoadingPanel>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
'use client';
|
||||
import { useState } from 'react';
|
||||
import { Button, Column, Box, Text, Icon, DialogTrigger, Modal, Dialog } from '@umami/react-zen';
|
||||
import { useDateRange, useMessages } from '@/components/hooks';
|
||||
import { ListCheck } from '@/components/icons';
|
||||
import { Panel } from '@/components/common/Panel';
|
||||
import { Breakdown } from './Breakdown';
|
||||
import { WebsiteControls } from '@/app/(main)/websites/[websiteId]/WebsiteControls';
|
||||
import { FieldSelectForm } from '@/app/(main)/websites/[websiteId]/(reports)/breakdown/FieldSelectForm';
|
||||
|
||||
export function BreakdownPage({ websiteId }: { websiteId: string }) {
|
||||
const {
|
||||
dateRange: { startDate, endDate },
|
||||
} = useDateRange(websiteId);
|
||||
const [fields, setFields] = useState(['path']);
|
||||
|
||||
return (
|
||||
<Column gap>
|
||||
<WebsiteControls websiteId={websiteId} />
|
||||
<FieldsButton value={fields} onChange={setFields} />
|
||||
<Panel height="900px" overflow="auto" allowFullscreen>
|
||||
<Breakdown
|
||||
websiteId={websiteId}
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
selectedFields={fields}
|
||||
/>
|
||||
</Panel>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
const FieldsButton = ({ value, onChange }) => {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<DialogTrigger>
|
||||
<Button>
|
||||
<Icon>
|
||||
<ListCheck />
|
||||
</Icon>
|
||||
<Text>Fields</Text>
|
||||
</Button>
|
||||
<Modal>
|
||||
<Dialog title={formatMessage(labels.fields)} style={{ width: 400 }}>
|
||||
{({ close }) => (
|
||||
<FieldSelectForm selectedFields={value} onChange={onChange} onClose={close} />
|
||||
)}
|
||||
</Dialog>
|
||||
</Modal>
|
||||
</DialogTrigger>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
import { Column, List, ListItem, Grid, Button } from '@umami/react-zen';
|
||||
import { useFields, useMessages } from '@/components/hooks';
|
||||
import { useState } from 'react';
|
||||
|
||||
export function FieldSelectForm({
|
||||
selectedFields = [],
|
||||
onChange,
|
||||
onClose,
|
||||
}: {
|
||||
selectedFields?: string[];
|
||||
onChange: (values: string[]) => void;
|
||||
onClose?: () => void;
|
||||
}) {
|
||||
const [selected, setSelected] = useState(selectedFields);
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { fields } = useFields();
|
||||
|
||||
const handleChange = (value: string[]) => {
|
||||
setSelected(value);
|
||||
};
|
||||
|
||||
const handleApply = () => {
|
||||
onChange?.(selected);
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Column width="300px" gap="6">
|
||||
<List value={selected} onChange={handleChange} selectionMode="multiple">
|
||||
{fields.map(({ name, label }) => {
|
||||
return (
|
||||
<ListItem key={name} id={name}>
|
||||
{label}
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
<Grid columns="1fr 1fr" gap>
|
||||
<Button onPress={onClose}>{formatMessage(labels.cancel)}</Button>
|
||||
<Button onPress={handleApply} variant="primary">
|
||||
{formatMessage(labels.apply)}
|
||||
</Button>
|
||||
</Grid>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import { Metadata } from 'next';
|
||||
import { BreakdownPage } from './BreakdownPage';
|
||||
|
||||
export default async function ({ params }: { params: Promise<{ websiteId: string }> }) {
|
||||
const { websiteId } = await params;
|
||||
|
||||
return <BreakdownPage websiteId={websiteId} />;
|
||||
}
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Insights',
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue