mirror of
https://github.com/umami-software/umami.git
synced 2026-02-05 21:27:20 +01:00
Updated reports.
This commit is contained in:
parent
28e872f219
commit
01bd21c5b4
75 changed files with 1373 additions and 980 deletions
|
|
@ -1,12 +0,0 @@
|
|||
.container {
|
||||
color: var(--base500);
|
||||
font-size: var(--font-size-md);
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 70px;
|
||||
}
|
||||
|
|
@ -1,18 +1,16 @@
|
|||
import classNames from 'classnames';
|
||||
import { Row } from '@umami/react-zen';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import styles from './Empty.module.css';
|
||||
|
||||
export interface EmptyProps {
|
||||
message?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function Empty({ message, className }: EmptyProps) {
|
||||
export function Empty({ message }: EmptyProps) {
|
||||
const { formatMessage, messages } = useMessages();
|
||||
|
||||
return (
|
||||
<div className={classNames(styles.container, className)}>
|
||||
<Row color="muted" alignItems="center" justifyContent="center" width="100%" height="100%">
|
||||
{message || formatMessage(messages.noDataAvailable)}
|
||||
</div>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,28 @@
|
|||
import { ReactNode } from 'react';
|
||||
import { Icon, Text, Column } from '@umami/react-zen';
|
||||
import { Logo } from '@/components/icons';
|
||||
|
||||
export interface EmptyPlaceholderProps {
|
||||
message?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
icon?: ReactNode;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
export function EmptyPlaceholder({ message, icon, children }: EmptyPlaceholderProps) {
|
||||
export function EmptyPlaceholder({ title, description, icon, children }: EmptyPlaceholderProps) {
|
||||
return (
|
||||
<Column alignItems="center" justifyContent="center" gap="5" height="100%" width="100%">
|
||||
<Icon size="xl">{icon || <Logo />}</Icon>
|
||||
<Text>{message}</Text>
|
||||
<div>{children}</div>
|
||||
{icon && (
|
||||
<Icon color="10" size="xl">
|
||||
{icon}
|
||||
</Icon>
|
||||
)}
|
||||
{title && (
|
||||
<Text weight="bold" size="4">
|
||||
{title}
|
||||
</Text>
|
||||
)}
|
||||
{description && <Text color="muted">{description}</Text>}
|
||||
{children}
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { useState, Key } from 'react';
|
||||
import { Grid, Row, Column, Label, List, ListItem, Button, Heading, Text } from '@umami/react-zen';
|
||||
import { Grid, Row, Column, Label, List, ListItem, Button, Heading } from '@umami/react-zen';
|
||||
import { useFilters, useMessages } from '@/components/hooks';
|
||||
import { FilterRecord } from '@/components/common/FilterRecord';
|
||||
import { Empty } from '@/components/common/Empty';
|
||||
|
||||
export interface FilterEditFormProps {
|
||||
websiteId?: string;
|
||||
|
|
@ -11,7 +12,7 @@ export interface FilterEditFormProps {
|
|||
}
|
||||
|
||||
export function FilterEditForm({ data = [], onChange, onClose }: FilterEditFormProps) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const [filters, setFilters] = useState(data);
|
||||
const { fields } = useFilters();
|
||||
|
||||
|
|
@ -72,7 +73,7 @@ export function FilterEditForm({ data = [], onChange, onClose }: FilterEditFormP
|
|||
/>
|
||||
);
|
||||
})}
|
||||
{!filters.length && <Text align="center">{formatMessage(labels.none)}</Text>}
|
||||
{!filters.length && <Empty message={formatMessage(messages.nothingSelected)} />}
|
||||
</Column>
|
||||
<Row alignItems="center" justifyContent="flex-end" gridColumn="span 2" gap>
|
||||
<Button onPress={onClose}>{formatMessage(labels.cancel)}</Button>
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
.panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.loading {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
}
|
||||
|
|
@ -1,9 +1,7 @@
|
|||
import { ReactNode } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Spinner, Dots } from '@umami/react-zen';
|
||||
import { Spinner, Dots, Column, type ColumnProps } from '@umami/react-zen';
|
||||
import { ErrorMessage } from '@/components/common/ErrorMessage';
|
||||
import { Empty } from '@/components/common/Empty';
|
||||
import styles from './LoadingPanel.module.css';
|
||||
|
||||
export function LoadingPanel({
|
||||
error,
|
||||
|
|
@ -12,25 +10,23 @@ export function LoadingPanel({
|
|||
isLoading,
|
||||
loadingIcon = 'dots',
|
||||
renderEmpty = () => <Empty />,
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: {
|
||||
data?: any;
|
||||
error?: Error;
|
||||
isEmpty?: boolean;
|
||||
isFetched?: boolean;
|
||||
isLoading?: boolean;
|
||||
loadingIcon?: 'dots' | 'spinner';
|
||||
renderEmpty?: () => ReactNode;
|
||||
className?: string;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
} & ColumnProps) {
|
||||
return (
|
||||
<div className={classNames(styles.panel, className)}>
|
||||
<Column {...props}>
|
||||
{isLoading && !isFetched && (loadingIcon === 'dots' ? <Dots /> : <Spinner />)}
|
||||
{error && <ErrorMessage />}
|
||||
{!error && !isLoading && isEmpty && renderEmpty()}
|
||||
{!error && !isLoading && !isEmpty && children}
|
||||
</div>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { AlertBanner, Loading, Column } from '@umami/react-zen';
|
|||
import { useMessages } from '@/components/hooks';
|
||||
|
||||
export function PageBody({
|
||||
maxWidth = '1600px',
|
||||
maxWidth = '1320px',
|
||||
error,
|
||||
isLoading,
|
||||
children,
|
||||
|
|
|
|||
|
|
@ -5,11 +5,13 @@ export function PageHeader({
|
|||
title,
|
||||
description,
|
||||
icon,
|
||||
showBorder = true,
|
||||
children,
|
||||
}: {
|
||||
title: string;
|
||||
description?: string;
|
||||
icon?: ReactNode;
|
||||
showBorder?: boolean;
|
||||
allowEdit?: boolean;
|
||||
className?: string;
|
||||
children?: ReactNode;
|
||||
|
|
@ -19,7 +21,7 @@ export function PageHeader({
|
|||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
paddingY="6"
|
||||
border="bottom"
|
||||
border={showBorder ? 'bottom' : undefined}
|
||||
width="100%"
|
||||
>
|
||||
<Row alignItems="center" gap="3">
|
||||
|
|
|
|||
|
|
@ -3,14 +3,11 @@ export * from './queries/useActiveUsersQuery';
|
|||
export * from './queries/useEventDataEventsQuery';
|
||||
export * from './queries/useEventDataPropertiesQuery';
|
||||
export * from './queries/useEventDataValuesQuery';
|
||||
export * from './queries/useGoalsQuery';
|
||||
export * from './queries/useLoginQuery';
|
||||
export * from './queries/useRealtimeQuery';
|
||||
export * from './queries/useResultQuery';
|
||||
export * from './queries/useReportQuery';
|
||||
export * from './queries/useReportsQuery';
|
||||
export * from './queries/useRetentionQuery';
|
||||
export * from './queries/useRevenueQuery';
|
||||
export * from './queries/useSessionActivityQuery';
|
||||
export * from './queries/useSessionDataQuery';
|
||||
export * from './queries/useSessionDataPropertiesQuery';
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
import { useApi } from '../useApi';
|
||||
import { usePagedQuery } from '../usePagedQuery';
|
||||
|
||||
export function useGoalQuery(
|
||||
{ websiteId, reportId }: { websiteId: string; reportId: string },
|
||||
params?: { [key: string]: string | number },
|
||||
) {
|
||||
const { post } = useApi();
|
||||
|
||||
return usePagedQuery({
|
||||
queryKey: ['goal', { websiteId, reportId, ...params }],
|
||||
queryFn: () => {
|
||||
return post(`/reports/goals`, {
|
||||
...params,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
import { useApi } from '../useApi';
|
||||
import { usePagedQuery } from '../usePagedQuery';
|
||||
import { useModified } from '../useModified';
|
||||
|
||||
export function useGoalsQuery(
|
||||
{ websiteId }: { websiteId: string },
|
||||
params?: { [key: string]: string | number },
|
||||
) {
|
||||
const { get } = useApi();
|
||||
const { modified } = useModified(`goals`);
|
||||
|
||||
return usePagedQuery({
|
||||
queryKey: ['goals', { websiteId, modified, ...params }],
|
||||
queryFn: () => {
|
||||
return get(`/websites/${websiteId}/goals`, {
|
||||
...params,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
import { useApi } from '../useApi';
|
||||
import { useFilterParams } from '../useFilterParams';
|
||||
import { UseQueryOptions } from '@tanstack/react-query';
|
||||
|
||||
export function useRetentionQuery(
|
||||
websiteId: string,
|
||||
queryParams?: { type: string; limit?: number; search?: string; startAt?: number; endAt?: number },
|
||||
options?: Omit<UseQueryOptions & { onDataLoad?: (data: any) => void }, 'queryKey' | 'queryFn'>,
|
||||
) {
|
||||
const { get, useQuery } = useApi();
|
||||
const filterParams = useFilterParams(websiteId);
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['retention', websiteId, { ...filterParams, ...queryParams }],
|
||||
queryFn: () =>
|
||||
get(`/websites/${websiteId}/retention`, { websiteId, ...filterParams, ...queryParams }),
|
||||
enabled: !!websiteId,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
import { useApi } from '../useApi';
|
||||
import { useFilterParams } from '../useFilterParams';
|
||||
import { UseQueryOptions } from '@tanstack/react-query';
|
||||
|
||||
export interface RevenueData {
|
||||
chart: any[];
|
||||
country: any[];
|
||||
total: {
|
||||
sum: number;
|
||||
count: number;
|
||||
unique_count: number;
|
||||
};
|
||||
table: any[];
|
||||
}
|
||||
|
||||
export function useRevenueQuery(
|
||||
websiteId: string,
|
||||
queryParams?: { type: string; limit?: number; search?: string; startAt?: number; endAt?: number },
|
||||
options?: Omit<
|
||||
UseQueryOptions<RevenueData, Error, RevenueData, any[]> & { onDataLoad?: (data: any) => void },
|
||||
'queryKey' | 'queryFn'
|
||||
>,
|
||||
) {
|
||||
const { get, useQuery } = useApi();
|
||||
const filterParams = useFilterParams(websiteId);
|
||||
const currency = 'USD';
|
||||
|
||||
return useQuery<RevenueData, Error, RevenueData, any[]>({
|
||||
queryKey: ['revenue', websiteId, { ...filterParams, ...queryParams }],
|
||||
queryFn: () =>
|
||||
get(`/websites/${websiteId}/revenue`, {
|
||||
currency,
|
||||
...filterParams,
|
||||
...queryParams,
|
||||
}),
|
||||
enabled: !!websiteId,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
import { useApi } from '../useApi';
|
||||
|
||||
export function useRevenueValuesQuery(websiteId: string, startDate: Date, endDate: Date) {
|
||||
const { get, useQuery } = useApi();
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['revenue:values', { websiteId, startDate, endDate }],
|
||||
queryFn: () =>
|
||||
get(`/reports/revenue`, {
|
||||
websiteId,
|
||||
startDate,
|
||||
endDate,
|
||||
}),
|
||||
enabled: !!(websiteId && startDate && endDate),
|
||||
});
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ export {
|
|||
AlertTriangle as Alert,
|
||||
ArrowRight as Arrow,
|
||||
Calendar,
|
||||
ChartPie,
|
||||
ChevronRight as Chevron,
|
||||
Clock,
|
||||
X as Close,
|
||||
|
|
|
|||
|
|
@ -132,6 +132,7 @@ export const labels = defineMessages({
|
|||
selectWebsite: { id: 'label.select-website', defaultMessage: 'Select website' },
|
||||
selectRole: { id: 'label.select-role', defaultMessage: 'Select role' },
|
||||
selectDate: { id: 'label.select-date', defaultMessage: 'Select date' },
|
||||
selectFilter: { id: 'label.select-filter', defaultMessage: 'Select filter' },
|
||||
all: { id: 'label.all', defaultMessage: 'All' },
|
||||
session: { id: 'label.session', defaultMessage: 'Session' },
|
||||
sessions: { id: 'label.sessions', defaultMessage: 'Sessions' },
|
||||
|
|
@ -331,6 +332,7 @@ export const messages = defineMessages({
|
|||
noUsers: { id: 'message.no-users', defaultMessage: 'There are no users.' },
|
||||
userDeleted: { id: 'message.user-deleted', defaultMessage: 'User deleted.' },
|
||||
noDataAvailable: { id: 'message.no-data-available', defaultMessage: 'No data available.' },
|
||||
nothingSelected: { id: 'message.nothing-selected', defaultMessage: 'Nothing selected.' },
|
||||
confirmReset: {
|
||||
id: 'message.confirm-reset',
|
||||
defaultMessage: 'Are you sure you want to reset {target}?',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue