More work on reports. Added Funnel page.

This commit is contained in:
Mike Cao 2025-06-05 22:19:35 -07:00
parent 5159dd470f
commit 3847e32f39
59 changed files with 1815 additions and 2370 deletions

View file

@ -9,9 +9,8 @@ export function useGoalQuery(
return usePagedQuery({
queryKey: ['goal', { websiteId, reportId, ...params }],
queryFn: (data: any) => {
queryFn: () => {
return post(`/reports/goals`, {
...data,
...params,
});
},

View file

@ -11,9 +11,8 @@ export function useGoalsQuery(
return usePagedQuery({
queryKey: ['goals', { websiteId, modified, ...params }],
queryFn: (data: any) => {
queryFn: () => {
return get(`/websites/${websiteId}/goals`, {
...data,
...params,
});
},

View file

@ -7,10 +7,8 @@ export function useReportQuery(reportId: string) {
return useQuery({
queryKey: ['report', { reportId, modified }],
queryFn: (data: any) => {
return get(`/reports/${reportId}`, {
...data,
});
queryFn: () => {
return get(`/reports/${reportId}`);
},
enabled: !!reportId,
});

View file

@ -2,14 +2,13 @@ import { useApi } from '../useApi';
import { usePagedQuery } from '../usePagedQuery';
import { useModified } from '../useModified';
export function useReportsQuery({ websiteId, teamId }: { websiteId?: string; teamId?: string }) {
const { modified } = useModified(`reports`);
export function useReportsQuery({ websiteId, type }: { websiteId: string; type?: string }) {
const { modified } = useModified(`reports:${type}`);
const { get } = useApi();
return usePagedQuery({
queryKey: ['reports', { websiteId, teamId, modified }],
queryFn: (params: any) => {
return get('/reports', { websiteId, teamId, ...params });
},
queryKey: ['reports', { websiteId, type, modified }],
queryFn: async () => get('/reports', { websiteId, type }),
enabled: !!websiteId && !!type,
});
}

View file

@ -10,7 +10,7 @@ export function useResultQuery<T>(
return useQuery<T>({
queryKey: ['reports', type, params],
queryFn: () => post(`/reports/${type}`, params),
queryFn: () => post(`/reports/${type}`, { type, ...params }),
enabled: !!type,
...options,
});

View file

@ -13,9 +13,8 @@ export function useWebsiteSessionsQuery(
return usePagedQuery({
queryKey: ['sessions', { websiteId, modified, ...params, ...filters }],
queryFn: (data: any) => {
queryFn: () => {
return get(`/websites/${websiteId}/sessions`, {
...data,
...params,
...filters,
pageSize: 20,

View file

@ -13,9 +13,8 @@ export function useWebsites(
return usePagedQuery({
queryKey: ['websites', { userId, teamId, modified, ...params }],
queryFn: (data: any) => {
queryFn: () => {
return get(teamId ? `/teams/${teamId}/websites` : `/users/${userId || user.id}/websites`, {
...data,
...params,
});
},

View file

@ -0,0 +1,100 @@
import { ReactNode, useState } from 'react';
import { useMessages } from '@/components/hooks';
import { useDeleteQuery } from '@/components/hooks/queries/useDeleteQuery';
import {
AlertDialog,
Button,
Icon,
Menu,
MenuItem,
MenuTrigger,
Modal,
Popover,
Text,
Row,
} from '@umami/react-zen';
import { Edit, More, Trash } from '@/components/icons';
export function ReportEditButton({
id,
name,
type,
children,
onDelete,
}: {
id: string;
name: string;
type: string;
onDelete?: () => void;
children: ({ close }: { close: () => void }) => ReactNode;
}) {
const { formatMessage, labels, messages } = useMessages();
const [showEdit, setShowEdit] = useState(false);
const [showDelete, setShowDelete] = useState(false);
const { mutate, touch } = useDeleteQuery(`/reports/${id}`);
const handleAction = (id: any) => {
if (id === 'edit') {
setShowEdit(true);
} else if (id === 'delete') {
setShowDelete(true);
}
};
const handleClose = () => {
setShowEdit(false);
setShowDelete(false);
};
const handleDelete = async () => {
mutate(null, {
onSuccess: async () => {
touch(`reports:${type}`);
setShowDelete(false);
onDelete?.();
},
});
};
return (
<>
<MenuTrigger>
<Button variant="quiet">
<Icon>
<More />
</Icon>
</Button>
<Popover placement="bottom">
<Menu onAction={handleAction}>
<MenuItem id="edit">
<Icon>
<Edit />
</Icon>
<Text>{formatMessage(labels.edit)}</Text>
</MenuItem>
<MenuItem id="delete">
<Icon>
<Trash />
</Icon>
<Text>{formatMessage(labels.delete)}</Text>
</MenuItem>
</Menu>
</Popover>
</MenuTrigger>
<Modal isOpen={showEdit || showDelete} isDismissable={true}>
{showEdit && children({ close: handleClose })}
{showDelete && (
<AlertDialog
title={formatMessage(labels.delete)}
onConfirm={handleDelete}
onCancel={handleClose}
>
<Row gap="1">
{formatMessage(messages.confirmDelete, { target: <b key={name}>{name}</b> })}
</Row>
</AlertDialog>
)}
</Modal>
</>
);
}

View file

@ -277,7 +277,6 @@ export const labels = defineMessages({
addStep: { id: 'label.add-step', defaultMessage: 'Add step' },
goal: { id: 'label.goal', defaultMessage: 'Goal' },
goals: { id: 'label.goals', defaultMessage: 'Goals' },
addGoal: { id: 'label.add-goal', defaultMessage: 'Add Goal' },
goalsDescription: {
id: 'label.goals-description',
defaultMessage: 'Track your goals for pageviews and events.',

View file

@ -11,12 +11,12 @@
.positive {
color: var(--success-color);
background: color-mix(in srgb, var(--success-color), var(--background-color) 85%);
background: color-mix(in srgb, var(--success-color), var(--background-color) 95%);
}
.negative {
color: var(--danger-color);
background: color-mix(in srgb, var(--danger-color), var(--background-color) 85%);
background: color-mix(in srgb, var(--danger-color), var(--background-color) 95%);
}
.neutral {

View file

@ -1,16 +1,16 @@
import classNames from 'classnames';
import { Icon, Text } from '@umami/react-zen';
import { ReactNode } from 'react';
import { HTMLAttributes, ReactNode } from 'react';
import { Arrow } from '@/components/icons';
import styles from './ChangeLabel.module.css';
export function ChangeLabel({
value,
size,
title,
reverseColors,
className,
children,
...props
}: {
value: number;
size?: 'xs' | 'sm' | 'md' | 'lg';
@ -19,7 +19,7 @@ export function ChangeLabel({
showPercentage?: boolean;
className?: string;
children?: ReactNode;
}) {
} & HTMLAttributes<HTMLDivElement>) {
const positive = value >= 0;
const negative = value < 0;
const neutral = value === 0 || isNaN(value);
@ -27,12 +27,12 @@ export function ChangeLabel({
return (
<div
{...props}
className={classNames(styles.label, className, {
[styles.positive]: good,
[styles.negative]: !good,
[styles.neutral]: neutral,
})}
title={title}
>
{!neutral && (
<Icon rotate={positive ? -90 : 90} size={size}>