mirror of
https://github.com/umami-software/umami.git
synced 2026-02-13 09:05:36 +01:00
Compare commits
4 commits
d23ad5f272
...
aa376d25df
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa376d25df | ||
|
|
caf04015bb | ||
|
|
c5994e5eb6 | ||
|
|
3496952769 |
40 changed files with 205 additions and 136 deletions
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@umami/components",
|
||||
"version": "0.127.0",
|
||||
"version": "0.128.0",
|
||||
"description": "Umami React components.",
|
||||
"author": "Mike Cao <mike@mikecao.com>",
|
||||
"license": "MIT",
|
||||
|
|
|
|||
|
|
@ -14,7 +14,10 @@ export function DateRangeSetting() {
|
|||
setDate(value);
|
||||
};
|
||||
|
||||
const handleReset = () => setItem(DATE_RANGE_CONFIG, DEFAULT_DATE_RANGE_VALUE);
|
||||
const handleReset = () => {
|
||||
setItem(DATE_RANGE_CONFIG, DEFAULT_DATE_RANGE_VALUE);
|
||||
setDate(DEFAULT_DATE_RANGE_VALUE);
|
||||
};
|
||||
|
||||
return (
|
||||
<Row gap="3">
|
||||
|
|
|
|||
|
|
@ -2,12 +2,15 @@
|
|||
import { WebsiteProvider } from '@/app/(main)/websites/WebsiteProvider';
|
||||
import { WebsiteSettings } from '@/app/(main)/websites/[websiteId]/settings/WebsiteSettings';
|
||||
import { WebsiteSettingsHeader } from '@/app/(main)/websites/[websiteId]/settings/WebsiteSettingsHeader';
|
||||
import { Column } from '@umami/react-zen';
|
||||
|
||||
export function WebsiteSettingsPage({ websiteId }: { websiteId: string }) {
|
||||
return (
|
||||
<WebsiteProvider websiteId={websiteId}>
|
||||
<WebsiteSettingsHeader />
|
||||
<WebsiteSettings websiteId={websiteId} />
|
||||
<Column margin="2">
|
||||
<WebsiteSettingsHeader />
|
||||
<WebsiteSettings websiteId={websiteId} />
|
||||
</Column>
|
||||
</WebsiteProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { WebsiteShareForm } from '@/app/(main)/websites/[websiteId]/settings/Web
|
|||
import { useMessages, useNavigation, useWebsite } from '@/components/hooks';
|
||||
import { LinkButton } from '@/components/common/LinkButton';
|
||||
|
||||
export function WebsiteHeader() {
|
||||
export function WebsiteHeader({ showActions }: { showActions?: boolean }) {
|
||||
const website = useWebsite();
|
||||
const { renderUrl, pathname } = useNavigation();
|
||||
const isSettings = pathname.endsWith('/settings');
|
||||
|
|
@ -20,15 +20,18 @@ export function WebsiteHeader() {
|
|||
<PageHeader title={website.name} icon={<Favicon domain={website.domain} />} marginBottom="3">
|
||||
<Row alignItems="center" gap="6">
|
||||
<ActiveUsers websiteId={website.id} />
|
||||
<Row alignItems="center" gap>
|
||||
<ShareButton websiteId={website.id} shareId={website.shareId} />
|
||||
<LinkButton href={renderUrl(`/websites/${website.id}/settings`, false)}>
|
||||
<Icon>
|
||||
<Edit />
|
||||
</Icon>
|
||||
<Text>Edit</Text>
|
||||
</LinkButton>
|
||||
</Row>
|
||||
|
||||
{showActions && (
|
||||
<Row alignItems="center" gap>
|
||||
<ShareButton websiteId={website.id} shareId={website.shareId} />
|
||||
<LinkButton href={renderUrl(`/websites/${website.id}/settings`, false)}>
|
||||
<Icon>
|
||||
<Edit />
|
||||
</Icon>
|
||||
<Text>Edit</Text>
|
||||
</LinkButton>
|
||||
</Row>
|
||||
)}
|
||||
</Row>
|
||||
</PageHeader>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export function WebsiteLayout({ websiteId, children }: { websiteId: string; chil
|
|||
</Column>
|
||||
)}
|
||||
<PageBody gap>
|
||||
<WebsiteHeader />
|
||||
<WebsiteHeader showActions />
|
||||
<Column>{children}</Column>
|
||||
</PageBody>
|
||||
</Grid>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,5 @@
|
|||
import { Text } from '@umami/react-zen';
|
||||
import {
|
||||
Eye,
|
||||
User,
|
||||
Clock,
|
||||
Sheet,
|
||||
Tag,
|
||||
ChartPie,
|
||||
UserPlus,
|
||||
GitCompareArrows,
|
||||
} from '@/components/icons';
|
||||
import { Eye, User, Clock, Ungroup, Tag, ChartPie, UserPlus, GitCompare } from '@/components/icons';
|
||||
import { Lightning, Path, Money, Target, Funnel, Magnet, Network } from '@/components/svg';
|
||||
import { useMessages, useNavigation } from '@/components/hooks';
|
||||
import { SideMenu } from '@/components/common/SideMenu';
|
||||
|
|
@ -56,13 +47,13 @@ export function WebsiteNav({ websiteId }: { websiteId: string }) {
|
|||
{
|
||||
id: 'compare',
|
||||
label: formatMessage(labels.compare),
|
||||
icon: <GitCompareArrows />,
|
||||
icon: <GitCompare />,
|
||||
path: renderPath('/compare'),
|
||||
},
|
||||
{
|
||||
id: 'breakdown',
|
||||
label: formatMessage(labels.breakdown),
|
||||
icon: <Sheet />,
|
||||
icon: <Ungroup />,
|
||||
path: renderPath('/breakdown'),
|
||||
},
|
||||
],
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ export function SessionActivity({
|
|||
|
||||
return (
|
||||
<Column key={eventId} gap>
|
||||
{showHeader && <Heading size="2">{formatTimezoneDate(createdAt, 'PPPP')}</Heading>}
|
||||
{showHeader && <Heading size="1">{formatTimezoneDate(createdAt, 'PPPP')}</Heading>}
|
||||
<Row alignItems="center" gap="6" height="40px">
|
||||
<StatusLight color={`#${visitId?.substring(0, 6)}`}>
|
||||
{formatTimezoneDate(createdAt, 'pp')}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { ReactNode } from 'react';
|
||||
import { Icon, TextField, Column, Row, Label, Text } from '@umami/react-zen';
|
||||
import { Icon, TextField, Column, Row, Label } from '@umami/react-zen';
|
||||
import { useFormat, useLocale, useMessages, useRegionNames } from '@/components/hooks';
|
||||
import { TypeIcon } from '@/components/common/TypeIcon';
|
||||
import { KeyRound, Calendar } from '@/components/icons';
|
||||
|
|
@ -15,7 +15,7 @@ export function SessionInfo({ data }) {
|
|||
return (
|
||||
<Column gap="6">
|
||||
<Info label="ID">
|
||||
<TextField value={data?.id} allowCopy />
|
||||
<TextField value={data?.id} style={{ width: '100%' }} allowCopy />
|
||||
</Info>
|
||||
|
||||
<Info label={formatMessage(labels.distinctId)} icon={<KeyRound />}>
|
||||
|
|
@ -83,7 +83,7 @@ const Info = ({
|
|||
<Label>{label}</Label>
|
||||
<Row alignItems="center" gap>
|
||||
{icon && <Icon>{icon}</Icon>}
|
||||
<Text>{children || '—'}</Text>
|
||||
{children || '—'}
|
||||
</Row>
|
||||
</Column>
|
||||
);
|
||||
|
|
@ -1,37 +1,52 @@
|
|||
'use client';
|
||||
import { Grid, Row, Column, Tabs, TabList, Tab, TabPanel } from '@umami/react-zen';
|
||||
import { Grid, Row, Column, Tabs, TabList, Tab, TabPanel, Icon, Button } from '@umami/react-zen';
|
||||
import { Avatar } from '@/components/common/Avatar';
|
||||
import { LoadingPanel } from '@/components/common/LoadingPanel';
|
||||
import { X } from '@/components/icons';
|
||||
import { useMessages, useWebsiteSessionQuery } from '@/components/hooks';
|
||||
import { SessionActivity } from './SessionActivity';
|
||||
import { SessionData } from './SessionData';
|
||||
import { SessionInfo } from './SessionInfo';
|
||||
import { SessionStats } from './SessionStats';
|
||||
import { Panel } from '@/components/common/Panel';
|
||||
|
||||
export function SessionDetailsPage({
|
||||
export function SessionProfile({
|
||||
websiteId,
|
||||
sessionId,
|
||||
onClose,
|
||||
}: {
|
||||
websiteId: string;
|
||||
sessionId: string;
|
||||
onClose?: () => void;
|
||||
}) {
|
||||
const { data, isLoading, error } = useWebsiteSessionQuery(websiteId, sessionId);
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<LoadingPanel data={data} isLoading={isLoading} error={error}>
|
||||
<LoadingPanel
|
||||
data={data}
|
||||
isLoading={isLoading}
|
||||
error={error}
|
||||
loadingIcon="spinner"
|
||||
loadingPlacement="absolute"
|
||||
>
|
||||
{data && (
|
||||
<Grid columns="260px 1fr" gap>
|
||||
<Column gap="6">
|
||||
<Row justifyContent="center">
|
||||
<Avatar seed={data?.id} size={128} />
|
||||
</Row>
|
||||
<SessionInfo data={data} />
|
||||
</Column>
|
||||
<Column gap>
|
||||
<SessionStats data={data} />
|
||||
<Panel>
|
||||
<Column gap>
|
||||
<Row alignItems="center" justifyContent="flex-end">
|
||||
<Button onPress={onClose} variant="quiet">
|
||||
<Icon>
|
||||
<X />
|
||||
</Icon>
|
||||
</Button>
|
||||
</Row>
|
||||
<Grid columns="340px 1fr" gap="6">
|
||||
<Column gap="6">
|
||||
<Row justifyContent="center">
|
||||
<Avatar seed={data?.id} size={128} />
|
||||
</Row>
|
||||
<SessionInfo data={data} />
|
||||
</Column>
|
||||
<Column gap>
|
||||
<SessionStats data={data} />
|
||||
|
||||
<Tabs>
|
||||
<TabList>
|
||||
<Tab id="activity">{formatMessage(labels.activity)}</Tab>
|
||||
|
|
@ -49,9 +64,9 @@ export function SessionDetailsPage({
|
|||
<SessionData sessionId={sessionId} websiteId={websiteId} />
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</Panel>
|
||||
</Column>
|
||||
</Grid>
|
||||
</Column>
|
||||
</Grid>
|
||||
</Column>
|
||||
)}
|
||||
</LoadingPanel>
|
||||
);
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { useMemo, useState } from 'react';
|
||||
import { Select, ListItem, Grid } from '@umami/react-zen';
|
||||
import { Select, ListItem, Grid, Column } from '@umami/react-zen';
|
||||
import {
|
||||
useMessages,
|
||||
useSessionDataPropertiesQuery,
|
||||
|
|
@ -24,25 +24,26 @@ export function SessionProperties({ websiteId }: { websiteId: string }) {
|
|||
data={data}
|
||||
error={error}
|
||||
minHeight="300px"
|
||||
gap="6"
|
||||
>
|
||||
{data && (
|
||||
<Grid columns="repeat(auto-fill, minmax(300px, 1fr))" gap>
|
||||
<Select
|
||||
label={formatMessage(labels.event)}
|
||||
value={propertyName}
|
||||
onChange={setPropertyName}
|
||||
placeholder=""
|
||||
>
|
||||
{properties?.map(p => (
|
||||
<ListItem key={p} id={p}>
|
||||
{p}
|
||||
</ListItem>
|
||||
))}
|
||||
</Select>
|
||||
</Grid>
|
||||
)}
|
||||
{propertyName && <SessionValues websiteId={websiteId} propertyName={propertyName} />}
|
||||
<Column gap="6">
|
||||
{data && (
|
||||
<Grid columns="repeat(auto-fill, minmax(300px, 1fr))" gap>
|
||||
<Select
|
||||
label={formatMessage(labels.event)}
|
||||
value={propertyName}
|
||||
onChange={setPropertyName}
|
||||
placeholder=""
|
||||
>
|
||||
{properties?.map(p => (
|
||||
<ListItem key={p} id={p}>
|
||||
{p}
|
||||
</ListItem>
|
||||
))}
|
||||
</Select>
|
||||
</Grid>
|
||||
)}
|
||||
{propertyName && <SessionValues websiteId={websiteId} propertyName={propertyName} />}
|
||||
</Column>
|
||||
</LoadingPanel>
|
||||
);
|
||||
}
|
||||
|
|
@ -84,7 +85,6 @@ const SessionValues = ({ websiteId, propertyName }) => {
|
|||
data={data}
|
||||
error={error}
|
||||
minHeight="300px"
|
||||
gap="6"
|
||||
>
|
||||
{data && (
|
||||
<Grid columns="1fr 1fr" gap>
|
||||
|
|
|
|||
|
|
@ -1,18 +1,35 @@
|
|||
'use client';
|
||||
import { Key, useState } from 'react';
|
||||
import { TabList, Tab, Tabs, TabPanel, Column } from '@umami/react-zen';
|
||||
import { TabList, Tab, Tabs, TabPanel, Column, Modal, Dialog } from '@umami/react-zen';
|
||||
import { SessionsDataTable } from './SessionsDataTable';
|
||||
import { SessionProperties } from './SessionProperties';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import { useMessages, useNavigation } from '@/components/hooks';
|
||||
import { Panel } from '@/components/common/Panel';
|
||||
import { WebsiteControls } from '@/app/(main)/websites/[websiteId]/WebsiteControls';
|
||||
import { getItem, setItem } from '@/lib/storage';
|
||||
import { SessionProfile } from '@/app/(main)/websites/[websiteId]/sessions/SessionProfile';
|
||||
|
||||
const KEY_NAME = 'umami.sessions.tab';
|
||||
|
||||
export function SessionsPage({ websiteId }) {
|
||||
const [tab, setTab] = useState(getItem(KEY_NAME) || 'activity');
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const {
|
||||
router,
|
||||
query: { session },
|
||||
updateParams,
|
||||
} = useNavigation();
|
||||
|
||||
const handleClose = (close: () => void) => {
|
||||
router.push(updateParams({ session: undefined }));
|
||||
close();
|
||||
};
|
||||
|
||||
const handleOpenChange = (isOpen: boolean) => {
|
||||
if (!isOpen) {
|
||||
router.push(updateParams({ session: undefined }));
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelect = (value: Key) => {
|
||||
setItem(KEY_NAME, value);
|
||||
|
|
@ -36,6 +53,26 @@ export function SessionsPage({ websiteId }) {
|
|||
</TabPanel>
|
||||
</Tabs>
|
||||
</Panel>
|
||||
<Modal isOpen={!!session} onOpenChange={handleOpenChange} isDismissable>
|
||||
<Dialog
|
||||
style={{
|
||||
maxWidth: 1320,
|
||||
width: '100vw',
|
||||
minHeight: '300px',
|
||||
height: 'calc(100vh - 40px)',
|
||||
}}
|
||||
>
|
||||
{({ close }) => {
|
||||
return (
|
||||
<SessionProfile
|
||||
websiteId={websiteId}
|
||||
sessionId={session}
|
||||
onClose={() => handleClose(close)}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</Dialog>
|
||||
</Modal>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import Link from 'next/link';
|
||||
import { DataColumn, DataTable } from '@umami/react-zen';
|
||||
import { useFormat, useMessages } from '@/components/hooks';
|
||||
import { useFormat, useMessages, useNavigation } from '@/components/hooks';
|
||||
import { Avatar } from '@/components/common/Avatar';
|
||||
import { TypeIcon } from '@/components/common/TypeIcon';
|
||||
import { DateDistance } from '@/components/common/DateDistance';
|
||||
|
|
@ -8,12 +8,13 @@ import { DateDistance } from '@/components/common/DateDistance';
|
|||
export function SessionsTable({ data = [] }: { data: any[]; showDomain?: boolean }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { formatValue } = useFormat();
|
||||
const { updateParams } = useNavigation();
|
||||
|
||||
return (
|
||||
<DataTable data={data}>
|
||||
<DataColumn id="id" label={formatMessage(labels.session)} width="100px">
|
||||
{(row: any) => (
|
||||
<Link href={`sessions/${row.id}`}>
|
||||
<Link href={updateParams({ session: row.id })}>
|
||||
<Avatar seed={row.id} size={32} />
|
||||
</Link>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
import { SessionDetailsPage } from './SessionDetailsPage';
|
||||
import { Metadata } from 'next';
|
||||
|
||||
export default async function WebsitePage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ websiteId: string; sessionId: string }>;
|
||||
}) {
|
||||
const { websiteId, sessionId } = await params;
|
||||
|
||||
return <SessionDetailsPage websiteId={websiteId} sessionId={sessionId} />;
|
||||
}
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Websites',
|
||||
};
|
||||
|
|
@ -6,6 +6,7 @@ import { useShareTokenQuery } from '@/components/hooks';
|
|||
import { PageBody } from '@/components/common/PageBody';
|
||||
import { Header } from './Header';
|
||||
import { Footer } from './Footer';
|
||||
import { WebsiteHeader } from '@/app/(main)/websites/[websiteId]/WebsiteHeader';
|
||||
|
||||
export function SharePage({ shareId }) {
|
||||
const { shareToken, isLoading } = useShareTokenQuery(shareId);
|
||||
|
|
@ -19,6 +20,7 @@ export function SharePage({ shareId }) {
|
|||
<PageBody gap>
|
||||
<Header />
|
||||
<WebsiteProvider websiteId={shareToken.websiteId}>
|
||||
<WebsiteHeader showActions={false} />
|
||||
<WebsitePage websiteId={shareToken.websiteId} />
|
||||
</WebsiteProvider>
|
||||
<Footer />
|
||||
|
|
|
|||
|
|
@ -29,25 +29,31 @@ export function LoadingPanel({
|
|||
}: LoadingPanelProps) {
|
||||
const empty = isEmpty ?? checkEmpty(data);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Show loading spinner only if no data exists */}
|
||||
{(isLoading || isFetching) && (
|
||||
<Column position="relative" height="100%" {...props}>
|
||||
<Loading icon={loadingIcon} placement={loadingPlacement} />
|
||||
</Column>
|
||||
)}
|
||||
// Show loading spinner only if no data exists
|
||||
if (isLoading || isFetching) {
|
||||
return (
|
||||
<Column position="relative" height="100%" width="100%" {...props}>
|
||||
<Loading icon={loadingIcon} placement={loadingPlacement} />
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
{/* Show error */}
|
||||
{error && <ErrorMessage />}
|
||||
// Show error
|
||||
if (error) {
|
||||
return <ErrorMessage />;
|
||||
}
|
||||
|
||||
{/* Show empty state (once loaded) */}
|
||||
{!error && !isLoading && !isFetching && empty && renderEmpty()}
|
||||
// Show empty state (once loaded)
|
||||
if (!error && !isLoading && !isFetching && empty) {
|
||||
return renderEmpty();
|
||||
}
|
||||
|
||||
{/* Show main content when data exists */}
|
||||
{!isLoading && !isFetching && !error && !empty && children}
|
||||
</>
|
||||
);
|
||||
// Show main content when data exists
|
||||
if (!isLoading && !isFetching && !error && !empty) {
|
||||
return children;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function checkEmpty(data: any) {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { ReactQueryOptions } from '@/lib/types';
|
|||
|
||||
export function useEventDataEventsQuery(websiteId: string, options?: ReactQueryOptions) {
|
||||
const { get, useQuery } = useApi();
|
||||
const date = useDateParameters(websiteId);
|
||||
const date = useDateParameters();
|
||||
const filters = useFilterParameters();
|
||||
|
||||
return useQuery({
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { ReactQueryOptions } from '@/lib/types';
|
|||
|
||||
export function useEventDataPropertiesQuery(websiteId: string, options?: ReactQueryOptions) {
|
||||
const { get, useQuery } = useApi();
|
||||
const date = useDateParameters(websiteId);
|
||||
const date = useDateParameters();
|
||||
const filters = useFilterParameters();
|
||||
|
||||
return useQuery<any>({
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { ReactQueryOptions } from '@/lib/types';
|
|||
|
||||
export function useEventDataQuery(websiteId: string, eventId: string, options?: ReactQueryOptions) {
|
||||
const { get, useQuery } = useApi();
|
||||
const date = useDateParameters(websiteId);
|
||||
const date = useDateParameters();
|
||||
const params = useFilterParameters();
|
||||
|
||||
return useQuery({
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ export function useEventDataValuesQuery(
|
|||
options?: ReactQueryOptions,
|
||||
) {
|
||||
const { get, useQuery } = useApi();
|
||||
const date = useDateParameters(websiteId);
|
||||
const date = useDateParameters();
|
||||
const filters = useFilterParameters();
|
||||
|
||||
return useQuery<any>({
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ export function useResultQuery<T = any>(
|
|||
) {
|
||||
const { websiteId, ...parameters } = params;
|
||||
const { post, useQuery } = useApi();
|
||||
const { startDate, endDate, timezone } = useDateParameters(websiteId);
|
||||
const { startDate, endDate, timezone } = useDateParameters();
|
||||
const filters = useFilterParameters();
|
||||
|
||||
return useQuery<T>({
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { ReactQueryOptions } from '@/lib/types';
|
|||
|
||||
export function useSessionDataPropertiesQuery(websiteId: string, options?: ReactQueryOptions) {
|
||||
const { get, useQuery } = useApi();
|
||||
const date = useDateParameters(websiteId);
|
||||
const date = useDateParameters();
|
||||
const filters = useFilterParameters();
|
||||
|
||||
return useQuery<any>({
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export function useSessionDataValuesQuery(
|
|||
options?: ReactQueryOptions,
|
||||
) {
|
||||
const { get, useQuery } = useApi();
|
||||
const date = useDateParameters(websiteId);
|
||||
const date = useDateParameters();
|
||||
const filters = useFilterParameters();
|
||||
|
||||
return useQuery<any>({
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export function useWebsiteEventsQuery(
|
|||
options?: ReactQueryOptions,
|
||||
) {
|
||||
const { get } = useApi();
|
||||
const date = useDateParameters(websiteId);
|
||||
const date = useDateParameters();
|
||||
const filters = useFilterParameters();
|
||||
|
||||
return usePagedQuery({
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { ReactQueryOptions } from '@/lib/types';
|
|||
|
||||
export function useWebsiteEventsSeriesQuery(websiteId: string, options?: ReactQueryOptions) {
|
||||
const { get, useQuery } = useApi();
|
||||
const date = useDateParameters(websiteId);
|
||||
const date = useDateParameters();
|
||||
const filters = useFilterParameters();
|
||||
|
||||
return useQuery({
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export function useWebsiteExpandedMetricsQuery(
|
|||
options?: ReactQueryOptions<WebsiteExpandedMetricsData>,
|
||||
) {
|
||||
const { get, useQuery } = useApi();
|
||||
const date = useDateParameters(websiteId);
|
||||
const date = useDateParameters();
|
||||
const filters = useFilterParameters();
|
||||
|
||||
return useQuery<WebsiteExpandedMetricsData>({
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export function useWebsiteMetricsQuery(
|
|||
options?: ReactQueryOptions<WebsiteMetricsData>,
|
||||
) {
|
||||
const { get, useQuery } = useApi();
|
||||
const date = useDateParameters(websiteId);
|
||||
const date = useDateParameters();
|
||||
const filters = useFilterParameters();
|
||||
|
||||
return useQuery<WebsiteMetricsData>({
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export function useWebsitePageviewsQuery(
|
|||
options?: ReactQueryOptions<WebsitePageviewsData>,
|
||||
) {
|
||||
const { get, useQuery } = useApi();
|
||||
const date = useDateParameters(websiteId);
|
||||
const date = useDateParameters();
|
||||
const queryParams = useFilterParameters();
|
||||
|
||||
return useQuery<WebsitePageviewsData>({
|
||||
|
|
|
|||
|
|
@ -8,5 +8,6 @@ export function useWebsiteSessionQuery(websiteId: string, sessionId: string) {
|
|||
queryFn: () => {
|
||||
return get(`/websites/${websiteId}/sessions/${sessionId}`);
|
||||
},
|
||||
enabled: Boolean(websiteId && sessionId),
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { useDateParameters } from '../useDateParameters';
|
|||
|
||||
export function useWebsiteSessionStatsQuery(websiteId: string, options?: Record<string, string>) {
|
||||
const { get, useQuery } = useApi();
|
||||
const date = useDateParameters(websiteId);
|
||||
const date = useDateParameters();
|
||||
const filters = useFilterParameters();
|
||||
|
||||
return useQuery({
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ export function useWebsiteSessionsQuery(
|
|||
) {
|
||||
const { get } = useApi();
|
||||
const { modified } = useModified(`sessions`);
|
||||
const date = useDateParameters(websiteId);
|
||||
const date = useDateParameters();
|
||||
const filters = useFilterParameters();
|
||||
|
||||
return usePagedQuery({
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ export function useWebsiteStatsQuery(
|
|||
options?: UseQueryOptions<WebsiteStatsData, Error, WebsiteStatsData>,
|
||||
) {
|
||||
const { get, useQuery } = useApi();
|
||||
const date = useDateParameters(websiteId);
|
||||
const date = useDateParameters();
|
||||
const filters = useFilterParameters();
|
||||
|
||||
return useQuery<WebsiteStatsData>({
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { useFilterParameters } from '@/components/hooks/useFilterParameters';
|
|||
export function useWeeklyTrafficQuery(websiteId: string, params?: Record<string, string | number>) {
|
||||
const { get, useQuery } = useApi();
|
||||
const { modified } = useModified(`sessions`);
|
||||
const date = useDateParameters(websiteId);
|
||||
const date = useDateParameters();
|
||||
const filters = useFilterParameters();
|
||||
|
||||
return useQuery({
|
||||
|
|
|
|||
|
|
@ -2,16 +2,20 @@ import { useNavigation } from '@/components/hooks/useNavigation';
|
|||
import { useMemo } from 'react';
|
||||
import { getCompareDate, getOffsetDateRange, parseDateRange } from '@/lib/date';
|
||||
import { useLocale } from '@/components/hooks/useLocale';
|
||||
import { DEFAULT_DATE_RANGE_VALUE } from '@/lib/constants';
|
||||
import { DATE_RANGE_CONFIG, DEFAULT_DATE_RANGE_VALUE } from '@/lib/constants';
|
||||
import { getItem } from '@/lib/storage';
|
||||
|
||||
export function useDateRange(options: { ignoreOffset?: boolean } = {}) {
|
||||
const {
|
||||
query: { date = DEFAULT_DATE_RANGE_VALUE, offset = 0, compare = 'prev', all },
|
||||
query: { date = '', offset = 0, compare = 'prev' },
|
||||
} = useNavigation();
|
||||
const { locale } = useLocale();
|
||||
|
||||
const dateRange = useMemo(() => {
|
||||
const dateRangeObject = parseDateRange(date, locale);
|
||||
const dateRangeObject = parseDateRange(
|
||||
date || getItem(DATE_RANGE_CONFIG) || DEFAULT_DATE_RANGE_VALUE,
|
||||
locale,
|
||||
);
|
||||
|
||||
return !options.ignoreOffset && offset
|
||||
? getOffsetDateRange(dateRangeObject, +offset)
|
||||
|
|
@ -24,7 +28,7 @@ export function useDateRange(options: { ignoreOffset?: boolean } = {}) {
|
|||
date,
|
||||
offset,
|
||||
compare,
|
||||
isAllTime: !!all,
|
||||
isAllTime: date.endsWith(`:all`),
|
||||
isCustomRange: date.startsWith('range:'),
|
||||
dateRange,
|
||||
dateCompare,
|
||||
|
|
|
|||
|
|
@ -99,11 +99,13 @@ export function DateFilter({
|
|||
);
|
||||
};
|
||||
|
||||
const selectedValue = value.endsWith(':all') ? 'all' : value;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Select
|
||||
{...props}
|
||||
value={value}
|
||||
value={selectedValue}
|
||||
placeholder={formatMessage(labels.selectDate)}
|
||||
onChange={handleChange}
|
||||
renderValue={renderValue}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { useFilterParameters } from '@/components/hooks/useFilterParameters';
|
|||
export function ExportButton({ websiteId }: { websiteId: string }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const date = useDateParameters(websiteId);
|
||||
const date = useDateParameters();
|
||||
const filters = useFilterParameters();
|
||||
const searchParams = useSearchParams();
|
||||
const { get } = useApi();
|
||||
|
|
|
|||
|
|
@ -35,13 +35,12 @@ export function WebsiteDateFilter({
|
|||
if (date === 'all') {
|
||||
router.push(
|
||||
updateParams({
|
||||
date: getDateRangeValue(websiteDateRange.startDate, websiteDateRange.endDate),
|
||||
date: `${getDateRangeValue(websiteDateRange.startDate, websiteDateRange.endDate)}:all`,
|
||||
offset: undefined,
|
||||
all: 1,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
router.push(updateParams({ date, offset: undefined, all: undefined }));
|
||||
router.push(updateParams({ date, offset: undefined }));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useState } from 'react';
|
||||
import { Select, SelectProps, ListItem } from '@umami/react-zen';
|
||||
import { useUserWebsitesQuery, useMessages, useLoginQuery } from '@/components/hooks';
|
||||
import { Select, SelectProps, ListItem, Text, Row } from '@umami/react-zen';
|
||||
import { useUserWebsitesQuery, useMessages, useLoginQuery, useWebsite } from '@/components/hooks';
|
||||
import { Empty } from '@/components/common/Empty';
|
||||
|
||||
export function WebsiteSelect({
|
||||
|
|
@ -15,12 +15,15 @@ export function WebsiteSelect({
|
|||
includeTeams?: boolean;
|
||||
} & SelectProps) {
|
||||
const { formatMessage, messages } = useMessages();
|
||||
const website = useWebsite();
|
||||
const [name, setName] = useState<string>(website?.name);
|
||||
const [search, setSearch] = useState('');
|
||||
const { user } = useLoginQuery();
|
||||
const { data, isLoading } = useUserWebsitesQuery(
|
||||
{ userId: user?.id, teamId },
|
||||
{ search, pageSize: 5, includeTeams },
|
||||
{ search, pageSize: 10, includeTeams },
|
||||
);
|
||||
const listItems: { id: string; name: string }[] = data?.['data'] || [];
|
||||
|
||||
const handleSearch = (value: string) => {
|
||||
setSearch(value);
|
||||
|
|
@ -30,19 +33,34 @@ export function WebsiteSelect({
|
|||
setSearch('');
|
||||
};
|
||||
|
||||
const handleChange = (id: string) => {
|
||||
setName(listItems.find(item => item.id === id)?.name);
|
||||
onChange(id);
|
||||
};
|
||||
|
||||
const renderValue = () => {
|
||||
return (
|
||||
<Row maxWidth="160px">
|
||||
<Text truncate>{name}</Text>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Select
|
||||
{...props}
|
||||
items={data?.['data'] || []}
|
||||
items={listItems}
|
||||
value={websiteId}
|
||||
isLoading={isLoading}
|
||||
allowSearch={true}
|
||||
searchValue={search}
|
||||
onSearch={handleSearch}
|
||||
onChange={onChange}
|
||||
onChange={handleChange}
|
||||
onOpenChange={handleOpenChange}
|
||||
renderValue={renderValue}
|
||||
listProps={{
|
||||
renderEmptyState: () => <Empty message={formatMessage(messages.noResultsFound)} />,
|
||||
style: { maxHeight: '400px' },
|
||||
}}
|
||||
>
|
||||
{({ id, name }: any) => <ListItem key={id}>{name}</ListItem>}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ body {
|
|||
font-family: var(--font-family), sans-serif;
|
||||
color: var(--font-color);
|
||||
font-size: var(--font-size);
|
||||
background-color: var(--background-color);
|
||||
background-color: var(--base-color-2);
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue