mirror of
https://github.com/umami-software/umami.git
synced 2026-02-15 01:55:36 +01:00
More work on reports. Added Funnel page.
This commit is contained in:
parent
5159dd470f
commit
3847e32f39
59 changed files with 1815 additions and 2370 deletions
|
|
@ -59,7 +59,7 @@ export function WebsiteNav({ websiteId }: { websiteId: string }) {
|
|||
},
|
||||
{
|
||||
id: 'funnel',
|
||||
label: formatMessage(labels.funnel),
|
||||
label: formatMessage(labels.funnels),
|
||||
icon: <Funnel />,
|
||||
path: '/funnels',
|
||||
},
|
||||
|
|
|
|||
117
src/app/(main)/websites/[websiteId]/funnels/Funnel.tsx
Normal file
117
src/app/(main)/websites/[websiteId]/funnels/Funnel.tsx
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
import { Grid, Column, Row, Text, Icon, ProgressBar, Dialog } from '@umami/react-zen';
|
||||
import { useMessages, useResultQuery } from '@/components/hooks';
|
||||
import { LoadingPanel } from '@/components/common/LoadingPanel';
|
||||
import { File, Lightning, User } from '@/components/icons';
|
||||
import { formatLongNumber } from '@/lib/format';
|
||||
import { ReportEditButton } from '@/components/input/ReportEditButton';
|
||||
import { FunnelEditForm } from './FunnelEditForm';
|
||||
import { ChangeLabel } from '@/components/metrics/ChangeLabel';
|
||||
|
||||
type FunnelResult = {
|
||||
type: string;
|
||||
value: string;
|
||||
visitors: number;
|
||||
previous: number;
|
||||
dropped: number;
|
||||
droppoff: number;
|
||||
remaining: number;
|
||||
};
|
||||
|
||||
export function Funnel({ id, name, type, parameters, websiteId, startDate, endDate }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { data, error, isLoading } = useResultQuery<any>(type, {
|
||||
websiteId,
|
||||
dateRange: {
|
||||
startDate,
|
||||
endDate,
|
||||
},
|
||||
parameters,
|
||||
});
|
||||
|
||||
return (
|
||||
<LoadingPanel isEmpty={!data} isLoading={isLoading} error={error}>
|
||||
<Grid gap>
|
||||
<Grid columns="1fr auto" gap>
|
||||
<Column gap>
|
||||
<Row>
|
||||
<Text size="4" weight="bold">
|
||||
{name}
|
||||
</Text>
|
||||
</Row>
|
||||
</Column>
|
||||
<Column>
|
||||
<ReportEditButton id={id} name={name} type={type}>
|
||||
{({ close }) => {
|
||||
return (
|
||||
<Dialog title={formatMessage(labels.funnel)} variant="modal">
|
||||
<FunnelEditForm id={id} websiteId={websiteId} onClose={close} />
|
||||
</Dialog>
|
||||
);
|
||||
}}
|
||||
</ReportEditButton>
|
||||
</Column>
|
||||
</Grid>
|
||||
{data?.map(
|
||||
(
|
||||
{ type, value, visitors, previous, dropped, remaining }: FunnelResult,
|
||||
index: number,
|
||||
) => {
|
||||
const isPage = type === 'page';
|
||||
return (
|
||||
<Grid key={index} columns="auto 1fr" gap="6">
|
||||
<Column>
|
||||
<Row
|
||||
borderRadius="full"
|
||||
backgroundColor="2"
|
||||
width="40px"
|
||||
height="40px"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
>
|
||||
<Text weight="bold" size="4">
|
||||
{index + 1}
|
||||
</Text>
|
||||
</Row>
|
||||
</Column>
|
||||
<Column gap>
|
||||
<Row alignItems="center" justifyContent="space-between" gap>
|
||||
<Text color="muted">
|
||||
{formatMessage(isPage ? labels.viewedPage : labels.triggeredEvent)}
|
||||
</Text>
|
||||
<Text color="muted">{formatMessage(labels.conversionRate)}</Text>
|
||||
</Row>
|
||||
<Row alignItems="center" justifyContent="space-between" gap>
|
||||
<Row alignItems="center" gap>
|
||||
<Icon>{type === 'page' ? <File /> : <Lightning />}</Icon>
|
||||
<Text>{value}</Text>
|
||||
</Row>
|
||||
<Row alignItems="center" gap>
|
||||
{index > 0 && (
|
||||
<ChangeLabel value={-dropped}>{formatLongNumber(dropped)}</ChangeLabel>
|
||||
)}
|
||||
<Icon>
|
||||
<User />
|
||||
</Icon>
|
||||
<Text title={visitors.toString()}>{formatLongNumber(visitors)}</Text>
|
||||
</Row>
|
||||
</Row>
|
||||
<Row alignItems="center" gap="6">
|
||||
<ProgressBar
|
||||
value={visitors || 0}
|
||||
minValue={0}
|
||||
maxValue={previous || 1}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
<Text weight="bold" size="7">
|
||||
{Math.round(remaining * 100)}%
|
||||
</Text>
|
||||
</Row>
|
||||
</Column>
|
||||
</Grid>
|
||||
);
|
||||
},
|
||||
)}
|
||||
</Grid>
|
||||
</LoadingPanel>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import { Button, DialogTrigger, Dialog, Icon, Text, Modal } from '@umami/react-zen';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import { FunnelEditForm } from './FunnelEditForm';
|
||||
import { Plus } from '@/components/icons';
|
||||
|
||||
export function FunnelAddButton({ websiteId }: { websiteId: string }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<DialogTrigger>
|
||||
<Button variant="primary">
|
||||
<Icon>
|
||||
<Plus />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.funnel)}</Text>
|
||||
</Button>
|
||||
<Modal>
|
||||
<Dialog
|
||||
variant="modal"
|
||||
title={formatMessage(labels.funnel)}
|
||||
style={{ minHeight: 375, minWidth: 600 }}
|
||||
>
|
||||
{({ close }) => <FunnelEditForm websiteId={websiteId} onClose={close} />}
|
||||
</Dialog>
|
||||
</Modal>
|
||||
</DialogTrigger>
|
||||
);
|
||||
}
|
||||
158
src/app/(main)/websites/[websiteId]/funnels/FunnelEditForm.tsx
Normal file
158
src/app/(main)/websites/[websiteId]/funnels/FunnelEditForm.tsx
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
import {
|
||||
Form,
|
||||
FormField,
|
||||
FormFieldArray,
|
||||
TextField,
|
||||
Grid,
|
||||
FormController,
|
||||
FormButtons,
|
||||
FormSubmitButton,
|
||||
Button,
|
||||
RadioGroup,
|
||||
Radio,
|
||||
Text,
|
||||
Icon,
|
||||
Row,
|
||||
Loading,
|
||||
} from '@umami/react-zen';
|
||||
import { useApi, useMessages, useModified, useReportQuery } from '@/components/hooks';
|
||||
import { File, Lightning, Close, Plus } from '@/components/icons';
|
||||
|
||||
const FUNNEL_STEPS_MAX = 8;
|
||||
|
||||
export function FunnelEditForm({
|
||||
id,
|
||||
websiteId,
|
||||
onSave,
|
||||
onClose,
|
||||
}: {
|
||||
id?: string;
|
||||
websiteId: string;
|
||||
onSave?: () => void;
|
||||
onClose?: () => void;
|
||||
}) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { touch } = useModified();
|
||||
const { post, useMutation } = useApi();
|
||||
const { data } = useReportQuery(id);
|
||||
const { mutate, error, isPending } = useMutation({
|
||||
mutationFn: (params: any) => post(`/reports${id ? `/${id}` : ''}`, params),
|
||||
});
|
||||
|
||||
const handleSubmit = async ({ name, ...parameters }) => {
|
||||
mutate(
|
||||
{ ...data, id, name, type: 'funnel', websiteId, parameters },
|
||||
{
|
||||
onSuccess: async () => {
|
||||
touch('reports:funnel');
|
||||
onSave?.();
|
||||
onClose?.();
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
if (id && !data) {
|
||||
return <Loading position="page" />;
|
||||
}
|
||||
|
||||
const defaultValues = {
|
||||
name: data?.name || '',
|
||||
window: data?.parameters?.window || 60,
|
||||
steps: data?.parameters?.steps || [{ type: 'page', value: '/' }],
|
||||
};
|
||||
|
||||
return (
|
||||
<Form onSubmit={handleSubmit} error={error?.message} defaultValues={defaultValues}>
|
||||
<FormField
|
||||
name="name"
|
||||
label={formatMessage(labels.name)}
|
||||
rules={{ required: formatMessage(labels.required) }}
|
||||
>
|
||||
<TextField autoFocus />
|
||||
</FormField>
|
||||
<FormField
|
||||
name="window"
|
||||
label={formatMessage(labels.window)}
|
||||
rules={{ required: formatMessage(labels.required) }}
|
||||
>
|
||||
<TextField />
|
||||
</FormField>
|
||||
<FormFieldArray name="steps" label={formatMessage(labels.steps)}>
|
||||
{({ fields, append, remove, control }) => {
|
||||
return (
|
||||
<Grid gap>
|
||||
{fields.map((field: { id: string; type: string; value: string }, index: number) => {
|
||||
return (
|
||||
<Row key={field.id} alignItems="center" justifyContent="space-between" gap>
|
||||
<FormController control={control} name={`steps.${index}.type`}>
|
||||
{({ field }) => {
|
||||
return (
|
||||
<RadioGroup
|
||||
orientation="horizontal"
|
||||
variant="box"
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
>
|
||||
<Grid columns="1fr 1fr" flexGrow={1} gap>
|
||||
<Radio id="page" value="page">
|
||||
<Icon>
|
||||
<File />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.page)}</Text>
|
||||
</Radio>
|
||||
<Radio id="event" value="event">
|
||||
<Icon>
|
||||
<Lightning />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.event)}</Text>
|
||||
</Radio>
|
||||
</Grid>
|
||||
</RadioGroup>
|
||||
);
|
||||
}}
|
||||
</FormController>
|
||||
<FormController control={control} name={`steps.${index}.value`}>
|
||||
{({ field }) => {
|
||||
return (
|
||||
<TextField
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
defaultValue={field.value}
|
||||
style={{ flexGrow: 1 }}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</FormController>
|
||||
<Button variant="quiet" onPress={() => remove(index)}>
|
||||
<Icon size="sm">
|
||||
<Close />
|
||||
</Icon>
|
||||
</Button>
|
||||
</Row>
|
||||
);
|
||||
})}
|
||||
<Row>
|
||||
<Button
|
||||
onPress={() => append({ type: 'page', value: '/' })}
|
||||
isDisabled={fields.length >= FUNNEL_STEPS_MAX}
|
||||
>
|
||||
<Icon>
|
||||
<Plus />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.add)}</Text>
|
||||
</Button>
|
||||
</Row>
|
||||
</Grid>
|
||||
);
|
||||
}}
|
||||
</FormFieldArray>
|
||||
<FormButtons>
|
||||
<Button onPress={onClose} isDisabled={isPending}>
|
||||
{formatMessage(labels.cancel)}
|
||||
</Button>
|
||||
<FormSubmitButton>{formatMessage(labels.save)}</FormSubmitButton>
|
||||
</FormButtons>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
38
src/app/(main)/websites/[websiteId]/funnels/FunnelsPage.tsx
Normal file
38
src/app/(main)/websites/[websiteId]/funnels/FunnelsPage.tsx
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
'use client';
|
||||
import { Grid, Loading } from '@umami/react-zen';
|
||||
import { SectionHeader } from '@/components/common/SectionHeader';
|
||||
import { Funnel } from './Funnel';
|
||||
import { FunnelAddButton } from './FunnelAddButton';
|
||||
import { WebsiteControls } from '../WebsiteControls';
|
||||
import { useDateRange, useReportsQuery } from '@/components/hooks';
|
||||
import { LoadingPanel } from '@/components/common/LoadingPanel';
|
||||
import { Panel } from '@/components/common/Panel';
|
||||
|
||||
export function FunnelsPage({ websiteId }: { websiteId: string }) {
|
||||
const { result } = useReportsQuery({ websiteId, type: 'funnel' });
|
||||
const {
|
||||
dateRange: { startDate, endDate },
|
||||
} = useDateRange(websiteId);
|
||||
|
||||
if (!result) {
|
||||
return <Loading position="page" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<WebsiteControls websiteId={websiteId} />
|
||||
<LoadingPanel isEmpty={!result?.data} isLoading={!result}>
|
||||
<SectionHeader>
|
||||
<FunnelAddButton websiteId={websiteId} />
|
||||
</SectionHeader>
|
||||
<Grid gap>
|
||||
{result?.data?.map((report: any) => (
|
||||
<Panel key={report.id}>
|
||||
<Funnel {...report} startDate={startDate} endDate={endDate} />
|
||||
</Panel>
|
||||
))}
|
||||
</Grid>
|
||||
</LoadingPanel>
|
||||
</>
|
||||
);
|
||||
}
|
||||
12
src/app/(main)/websites/[websiteId]/funnels/page.tsx
Normal file
12
src/app/(main)/websites/[websiteId]/funnels/page.tsx
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { Metadata } from 'next';
|
||||
import { FunnelsPage } from './FunnelsPage';
|
||||
|
||||
export default async function ({ params }: { params: Promise<{ websiteId: string }> }) {
|
||||
const { websiteId } = await params;
|
||||
|
||||
return <FunnelsPage websiteId={websiteId} />;
|
||||
}
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Funnels',
|
||||
};
|
||||
|
|
@ -1,26 +1,10 @@
|
|||
import { useState } from 'react';
|
||||
import {
|
||||
Grid,
|
||||
Row,
|
||||
Column,
|
||||
Text,
|
||||
Icon,
|
||||
Button,
|
||||
MenuTrigger,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Popover,
|
||||
ProgressBar,
|
||||
Dialog,
|
||||
Modal,
|
||||
AlertDialog,
|
||||
} from '@umami/react-zen';
|
||||
import { Grid, Row, Column, Text, Icon, ProgressBar, Dialog } from '@umami/react-zen';
|
||||
import { ReportEditButton } from '@/components/input/ReportEditButton';
|
||||
import { useMessages, useResultQuery } from '@/components/hooks';
|
||||
import { Edit, More, Trash, File, Lightning, User } from '@/components/icons';
|
||||
import { File, Lightning, User } from '@/components/icons';
|
||||
import { LoadingPanel } from '@/components/common/LoadingPanel';
|
||||
import { formatLongNumber } from '@/lib/format';
|
||||
import { GoalAddForm } from '@/app/(main)/websites/[websiteId]/goals/GoalAddForm';
|
||||
import { useDeleteQuery } from '@/components/hooks/queries/useDeleteQuery';
|
||||
import { GoalEditForm } from './GoalEditForm';
|
||||
|
||||
export interface GoalProps {
|
||||
id: string;
|
||||
|
|
@ -41,12 +25,12 @@ export type GoalData = { num: number; total: number };
|
|||
export function Goal({ id, name, type, parameters, websiteId, startDate, endDate }: GoalProps) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { data, error, isLoading } = useResultQuery<GoalData>(type, {
|
||||
...parameters,
|
||||
websiteId,
|
||||
dateRange: {
|
||||
startDate,
|
||||
endDate,
|
||||
},
|
||||
parameters,
|
||||
});
|
||||
const isPage = parameters?.type === 'page';
|
||||
|
||||
|
|
@ -62,7 +46,19 @@ export function Goal({ id, name, type, parameters, websiteId, startDate, endDate
|
|||
</Row>
|
||||
</Column>
|
||||
<Column>
|
||||
<ActionsButton id={id} name={name} websiteId={websiteId} />
|
||||
<ReportEditButton id={id} name={name} type={type}>
|
||||
{({ close }) => {
|
||||
return (
|
||||
<Dialog
|
||||
title={formatMessage(labels.goal)}
|
||||
variant="modal"
|
||||
style={{ minHeight: 375, minWidth: 400 }}
|
||||
>
|
||||
<GoalEditForm id={id} websiteId={websiteId} onClose={close} />
|
||||
</Dialog>
|
||||
);
|
||||
}}
|
||||
</ReportEditButton>
|
||||
</Column>
|
||||
</Grid>
|
||||
<Row alignItems="center" justifyContent="space-between" gap>
|
||||
|
|
@ -85,7 +81,7 @@ export function Goal({ id, name, type, parameters, websiteId, startDate, endDate
|
|||
)} / ${formatLongNumber(data?.total)}`}</Text>
|
||||
</Row>
|
||||
</Row>
|
||||
<Row alignItems="center" gap>
|
||||
<Row alignItems="center" gap="6">
|
||||
<ProgressBar
|
||||
value={data?.num || 0}
|
||||
minValue={0}
|
||||
|
|
@ -100,88 +96,3 @@ export function Goal({ id, name, type, parameters, websiteId, startDate, endDate
|
|||
</LoadingPanel>
|
||||
);
|
||||
}
|
||||
|
||||
const ActionsButton = ({
|
||||
id,
|
||||
name,
|
||||
websiteId,
|
||||
}: {
|
||||
id: string;
|
||||
name: string;
|
||||
websiteId: string;
|
||||
}) => {
|
||||
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(`goals`);
|
||||
setShowDelete(false);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
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 && (
|
||||
<Dialog
|
||||
title={formatMessage(labels.goal)}
|
||||
variant="modal"
|
||||
style={{ minHeight: 375, minWidth: 400 }}
|
||||
>
|
||||
<GoalAddForm id={id} websiteId={websiteId} onClose={handleClose} />
|
||||
</Dialog>
|
||||
)}
|
||||
{showDelete && (
|
||||
<AlertDialog
|
||||
title={formatMessage(labels.delete)}
|
||||
onConfirm={handleDelete}
|
||||
onCancel={handleClose}
|
||||
>
|
||||
{formatMessage(messages.confirmDelete, { target: name })}
|
||||
</AlertDialog>
|
||||
)}
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Button, DialogTrigger, Dialog, Icon, Text, Modal } from '@umami/react-zen';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import { GoalAddForm } from './GoalAddForm';
|
||||
import { GoalEditForm } from './GoalEditForm';
|
||||
import { Plus } from '@/components/icons';
|
||||
|
||||
export function GoalAddButton({ websiteId }: { websiteId: string }) {
|
||||
|
|
@ -12,15 +12,15 @@ export function GoalAddButton({ websiteId }: { websiteId: string }) {
|
|||
<Icon>
|
||||
<Plus />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.addGoal)}</Text>
|
||||
<Text>{formatMessage(labels.goal)}</Text>
|
||||
</Button>
|
||||
<Modal>
|
||||
<Dialog
|
||||
variant="modal"
|
||||
title={formatMessage(labels.addGoal)}
|
||||
title={formatMessage(labels.goal)}
|
||||
style={{ minHeight: 375, minWidth: 400 }}
|
||||
>
|
||||
{({ close }) => <GoalAddForm websiteId={websiteId} onClose={close} />}
|
||||
{({ close }) => <GoalEditForm websiteId={websiteId} onClose={close} />}
|
||||
</Dialog>
|
||||
</Modal>
|
||||
</DialogTrigger>
|
||||
|
|
|
|||
|
|
@ -10,17 +10,12 @@ import {
|
|||
Radio,
|
||||
Text,
|
||||
Icon,
|
||||
Loading,
|
||||
} from '@umami/react-zen';
|
||||
import { useApi, useMessages, useModified, useReportQuery } from '@/components/hooks';
|
||||
import { File, Lightning } from '@/components/icons';
|
||||
|
||||
const defaultValues = {
|
||||
name: '',
|
||||
type: 'page',
|
||||
value: '',
|
||||
};
|
||||
|
||||
export function GoalAddForm({
|
||||
export function GoalEditForm({
|
||||
id,
|
||||
websiteId,
|
||||
onSave,
|
||||
|
|
@ -36,36 +31,46 @@ export function GoalAddForm({
|
|||
const { post, useMutation } = useApi();
|
||||
const { data } = useReportQuery(id);
|
||||
const { mutate, error, isPending } = useMutation({
|
||||
mutationFn: (params: any) => post(`/websites/${websiteId}/goals`, params),
|
||||
mutationFn: (params: any) => post(`/reports${id ? `/${id}` : ''}`, params),
|
||||
});
|
||||
|
||||
const handleSubmit = async (data: any) => {
|
||||
const handleSubmit = async ({ name, ...parameters }) => {
|
||||
mutate(
|
||||
{ id, ...data },
|
||||
{ ...data, id, name, type: 'goal', websiteId, parameters },
|
||||
{
|
||||
onSuccess: async () => {
|
||||
if (id) touch(`report:${id}`);
|
||||
touch('reports:goal');
|
||||
onSave?.();
|
||||
onClose?.();
|
||||
touch('goals');
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
if (id && !data) {
|
||||
return null;
|
||||
return <Loading position="page" />;
|
||||
}
|
||||
|
||||
const defaultValues = {
|
||||
name: data?.name || '',
|
||||
type: data?.parameters?.type || 'page',
|
||||
value: data?.parameters?.value || '',
|
||||
};
|
||||
|
||||
return (
|
||||
<Form
|
||||
onSubmit={handleSubmit}
|
||||
error={error?.message}
|
||||
defaultValues={data?.parameters || defaultValues}
|
||||
>
|
||||
<Form onSubmit={handleSubmit} error={error?.message} defaultValues={defaultValues}>
|
||||
{({ watch }) => {
|
||||
const watchType = watch('type');
|
||||
return (
|
||||
<>
|
||||
<FormField
|
||||
name="name"
|
||||
label={formatMessage(labels.name)}
|
||||
rules={{ required: formatMessage(labels.required) }}
|
||||
>
|
||||
<TextField />
|
||||
</FormField>
|
||||
<FormField
|
||||
name="type"
|
||||
label={formatMessage(labels.type)}
|
||||
|
|
@ -88,13 +93,6 @@ export function GoalAddForm({
|
|||
</Grid>
|
||||
</RadioGroup>
|
||||
</FormField>
|
||||
<FormField
|
||||
name="name"
|
||||
label={formatMessage(labels.name)}
|
||||
rules={{ required: formatMessage(labels.required) }}
|
||||
>
|
||||
<TextField />
|
||||
</FormField>
|
||||
<FormField
|
||||
name="value"
|
||||
label={formatMessage(watchType === 'event' ? labels.eventName : labels.path)}
|
||||
|
|
@ -106,7 +104,7 @@ export function GoalAddForm({
|
|||
<Button onPress={onClose} isDisabled={isPending}>
|
||||
{formatMessage(labels.cancel)}
|
||||
</Button>
|
||||
<FormSubmitButton>{formatMessage(id ? labels.save : labels.add)}</FormSubmitButton>
|
||||
<FormSubmitButton>{formatMessage(labels.save)}</FormSubmitButton>
|
||||
</FormButtons>
|
||||
</>
|
||||
);
|
||||
|
|
@ -4,12 +4,12 @@ import { SectionHeader } from '@/components/common/SectionHeader';
|
|||
import { Goal } from './Goal';
|
||||
import { GoalAddButton } from './GoalAddButton';
|
||||
import { WebsiteControls } from '../WebsiteControls';
|
||||
import { useDateRange, useGoalsQuery } from '@/components/hooks';
|
||||
import { useDateRange, useReportsQuery } from '@/components/hooks';
|
||||
import { LoadingPanel } from '@/components/common/LoadingPanel';
|
||||
import { Panel } from '@/components/common/Panel';
|
||||
|
||||
export function GoalsPage({ websiteId }: { websiteId: string }) {
|
||||
const { result } = useGoalsQuery({ websiteId });
|
||||
const { result } = useReportsQuery({ websiteId, type: 'goal' });
|
||||
const {
|
||||
dateRange: { startDate, endDate },
|
||||
} = useDateRange(websiteId);
|
||||
|
|
@ -26,9 +26,9 @@ export function GoalsPage({ websiteId }: { websiteId: string }) {
|
|||
<GoalAddButton websiteId={websiteId} />
|
||||
</SectionHeader>
|
||||
<Grid columns="1fr 1fr" gap>
|
||||
{result?.data?.map((goal: any) => (
|
||||
<Panel key={goal.id}>
|
||||
<Goal {...goal} reportId={goal.id} startDate={startDate} endDate={endDate} />
|
||||
{result?.data?.map((report: any) => (
|
||||
<Panel key={report.id}>
|
||||
<Goal {...report} reportId={report.id} startDate={startDate} endDate={endDate} />
|
||||
</Panel>
|
||||
))}
|
||||
</Grid>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue