umami/src/app/(main)/websites/[websiteId]/(reports)/goals/Goal.tsx
Mike Cao 50edb71687 Simplify i18n: remove old react-intl artifacts, rename formatMessage to t, replace FormattedMessage with t.rich().
- Rewrite messages.ts to plain string key maps (remove MessageDescriptor)
- Rewrite useMessages hook to expose t from useTranslations() directly
- Rename formatMessage → t across 193 consumer files
- Replace custom FormattedMessage component with next-intl t.rich()
- Update 52 language files to use rich text tags (<b>, <a>)
- Remove all direct imports from @/components/messages in favor of useMessages()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 11:19:04 -08:00

101 lines
3.4 KiB
TypeScript

import { Column, Dialog, Grid, Icon, ProgressBar, Row, Text } from '@umami/react-zen';
import { LoadingPanel } from '@/components/common/LoadingPanel';
import { useMessages, useNavigation, useResultQuery } from '@/components/hooks';
import { File, User } from '@/components/icons';
import { ReportEditButton } from '@/components/input/ReportEditButton';
import { Lightning } from '@/components/svg';
import { formatLongNumber } from '@/lib/format';
import { GoalEditForm } from './GoalEditForm';
export interface GoalProps {
id: string;
name: string;
type: string;
parameters: {
name: string;
type: string;
value: string;
};
websiteId: string;
startDate: Date;
endDate: Date;
}
export type GoalData = { num: number; total: number };
export function Goal({ id, name, type, parameters, websiteId, startDate, endDate }: GoalProps) {
const { t, labels } = useMessages();
const { pathname } = useNavigation();
const isSharePage = pathname.includes('/share/');
const { data, error, isLoading, isFetching } = useResultQuery<GoalData>(type, {
websiteId,
startDate,
endDate,
...parameters,
});
const isPage = parameters?.type === 'path';
return (
<LoadingPanel data={data} isLoading={isLoading} isFetching={isFetching} error={error}>
{data && (
<Grid gap>
<Grid columns="1fr auto" gap>
<Column gap>
<Row>
<Text size="lg" weight="bold">
{name}
</Text>
</Row>
</Column>
{!isSharePage && (
<Column>
<ReportEditButton id={id} name={name} type={type}>
{({ close }) => {
return (
<Dialog
title={t(labels.goal)}
variant="modal"
style={{ minHeight: 300, minWidth: 400 }}
>
<GoalEditForm id={id} websiteId={websiteId} onClose={close} />
</Dialog>
);
}}
</ReportEditButton>
</Column>
)}
</Grid>
<Row alignItems="center" justifyContent="space-between" gap>
<Text color="muted">{t(isPage ? labels.viewedPage : labels.triggeredEvent)}</Text>
<Text color="muted">{t(labels.conversionRate)}</Text>
</Row>
<Row alignItems="center" justifyContent="space-between" gap>
<Row alignItems="center" gap>
<Icon>{parameters.type === 'path' ? <File /> : <Lightning />}</Icon>
<Text>{parameters.value}</Text>
</Row>
<Row alignItems="center" gap>
<Icon>
<User />
</Icon>
<Text title={`${data?.num} / ${data?.total}`}>{`${formatLongNumber(
data?.num,
)} / ${formatLongNumber(data?.total)}`}</Text>
</Row>
</Row>
<Row alignItems="center" gap="6">
<ProgressBar
value={data?.num || 0}
minValue={0}
maxValue={data?.total || 1}
style={{ width: '100%' }}
/>
<Text weight="bold" size="4xl">
{data?.total ? Math.round((+data?.num / +data?.total) * 100) : '0'}%
</Text>
</Row>
</Grid>
)}
</LoadingPanel>
);
}