perf: improve mobile display
This commit is contained in:
parent
6606b253d8
commit
b2fb1832e1
@ -4,12 +4,22 @@ import { DateUnit } from '@tianji/shared';
|
||||
import React from 'react';
|
||||
import { formatDate, formatDateWithUnit } from '../utils/date';
|
||||
import { Column, ColumnConfig } from '@ant-design/charts';
|
||||
import { useIsMobile } from '@/hooks/useIsMobile';
|
||||
import { useTranslation } from '@i18next-toolkit/react';
|
||||
|
||||
export const TimeEventChart: React.FC<{
|
||||
labelMapping?: Record<string, string>;
|
||||
data: { x: string; y: number; type: string }[];
|
||||
unit: DateUnit;
|
||||
}> = React.memo((props) => {
|
||||
const { colors } = useTheme();
|
||||
const isMobile = useIsMobile();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const labelMapping = props.labelMapping ?? {
|
||||
pageview: t('pageview'),
|
||||
session: t('session'),
|
||||
};
|
||||
|
||||
const config = useMemo(
|
||||
() =>
|
||||
@ -30,6 +40,13 @@ export const TimeEventChart: React.FC<{
|
||||
title: (t) => formatDate(t),
|
||||
},
|
||||
color: [colors.chart.pv, colors.chart.uv],
|
||||
legend: isMobile
|
||||
? false
|
||||
: {
|
||||
itemName: {
|
||||
formatter: (text) => labelMapping[text] ?? text,
|
||||
},
|
||||
},
|
||||
xAxis: {
|
||||
label: {
|
||||
autoHide: true,
|
||||
@ -38,7 +55,7 @@ export const TimeEventChart: React.FC<{
|
||||
},
|
||||
},
|
||||
}) satisfies ColumnConfig,
|
||||
[props.data, props.unit]
|
||||
[props.data, props.unit, props.labelMapping]
|
||||
);
|
||||
|
||||
return <Column {...config} />;
|
||||
|
@ -177,7 +177,7 @@ export const MonitorInfo: React.FC<MonitorInfoProps> = React.memo((props) => {
|
||||
<div className="h-full w-full overflow-auto">
|
||||
<Spin spinning={isLoading}>
|
||||
<Space className="w-full" direction="vertical">
|
||||
<div className="flex justify-between">
|
||||
<div className="flex flex-col-reverse sm:flex-row sm:justify-between">
|
||||
<Space direction="vertical">
|
||||
<div>
|
||||
<ColorTag label={monitorInfo.type} />
|
||||
|
@ -11,7 +11,7 @@ export const MonitorStatsBlock: React.FC<MonitorStatsBlockProps> = React.memo(
|
||||
(props) => {
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-0.5 font-bold">
|
||||
<div className="mb-0.5 text-xs font-bold sm:text-base">
|
||||
{props.title}
|
||||
{props.tooltip && (
|
||||
<TipIcon className="ml-1" content={props.tooltip} />
|
||||
|
@ -17,7 +17,7 @@ export const WebsiteOnlineCount: React.FC<{
|
||||
if (typeof count === 'number' && count > 0) {
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="h-2.5 w-2.5 rounded-full bg-green-500" />
|
||||
<div className="h-2.5 w-2.5 flex-shrink-0 rounded-full bg-green-500" />
|
||||
<span>
|
||||
{count} {t('current visitor')}
|
||||
</span>
|
||||
|
@ -83,8 +83,8 @@ export const WebsiteOverview: React.FC<{
|
||||
|
||||
return (
|
||||
<Spin spinning={loading}>
|
||||
<div className="flex">
|
||||
<div className="flex flex-1 items-center text-2xl font-bold">
|
||||
<div className="flex flex-col-reverse sm:flex-row">
|
||||
<div className="flex flex-1 flex-col gap-2 text-2xl font-bold sm:flex-row sm:items-center">
|
||||
<span className="mr-2" title={website.domain ?? ''}>
|
||||
{website.name}
|
||||
</span>
|
||||
@ -102,7 +102,7 @@ export const WebsiteOverview: React.FC<{
|
||||
</Link>
|
||||
)}
|
||||
|
||||
<div className="ml-4 text-base font-normal">
|
||||
<div className="text-base font-normal">
|
||||
<WebsiteOnlineCount
|
||||
workspaceId={website.workspaceId}
|
||||
websiteId={website.id}
|
||||
|
34
src/client/pages/Layout/Menu.tsx
Normal file
34
src/client/pages/Layout/Menu.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet';
|
||||
import { LuMenu } from 'react-icons/lu';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import { useGlobalEventSubscribe } from '@/utils/event';
|
||||
|
||||
export const MobileLayoutMenu: React.FC<{
|
||||
list?: React.ReactNode;
|
||||
}> = React.memo((props) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
useGlobalEventSubscribe('commonListSelected', () => {
|
||||
setOpen(false);
|
||||
});
|
||||
|
||||
if (!props.list) {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Sheet open={open} onOpenChange={setOpen}>
|
||||
<SheetTrigger>
|
||||
<Button variant="outline" size="icon">
|
||||
<LuMenu />
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent side="left" className="w-11/12">
|
||||
<ScrollArea className="h-full">{props.list}</ScrollArea>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
);
|
||||
});
|
||||
MobileLayoutMenu.displayName = 'MobileLayoutMenu';
|
@ -2,7 +2,6 @@ import React from 'react';
|
||||
import {
|
||||
LuAreaChart,
|
||||
LuFilePieChart,
|
||||
LuMenu,
|
||||
LuMonitorDot,
|
||||
LuMoreVertical,
|
||||
LuServer,
|
||||
@ -16,19 +15,8 @@ import { cn } from '@/utils/style';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { LayoutProps } from './types';
|
||||
import { UserConfig } from './UserConfig';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Drawer,
|
||||
DrawerClose,
|
||||
DrawerContent,
|
||||
DrawerDescription,
|
||||
DrawerFooter,
|
||||
DrawerHeader,
|
||||
DrawerTitle,
|
||||
DrawerTrigger,
|
||||
} from '@/components/ui/drawer';
|
||||
import { Drawer, DrawerContent, DrawerTrigger } from '@/components/ui/drawer';
|
||||
import { MobileLayoutMenu } from './Menu';
|
||||
|
||||
export const MobileLayout: React.FC<LayoutProps> = React.memo((props) => {
|
||||
const { t } = useTranslation();
|
||||
@ -36,20 +24,7 @@ export const MobileLayout: React.FC<LayoutProps> = React.memo((props) => {
|
||||
return (
|
||||
<div className="flex h-svh flex-col">
|
||||
<div className="flex h-[52px] items-center justify-between px-2">
|
||||
<Sheet>
|
||||
<SheetTrigger disabled={!Boolean(props.list)}>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
disabled={!Boolean(props.list)}
|
||||
>
|
||||
<LuMenu />
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent side="left" className="w-11/12">
|
||||
<ScrollArea className="h-full">{props.list}</ScrollArea>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
<MobileLayoutMenu list={props.list} />
|
||||
|
||||
<div className="rounded-md dark:bg-white/10">
|
||||
<img className="m-auto h-8 w-8" src="/icon.svg" />
|
||||
|
@ -172,7 +172,10 @@ function TelemetryDetailComponent() {
|
||||
/>
|
||||
</Card.Grid>
|
||||
|
||||
<Card.Grid hoverable={false} className="min-h-[470px] !w-1/3">
|
||||
<Card.Grid
|
||||
hoverable={false}
|
||||
className="min-h-[470px] !w-full sm:!w-1/3"
|
||||
>
|
||||
<TelemetryMetricsTable
|
||||
telemetryId={telemetryId}
|
||||
type="source"
|
||||
@ -182,7 +185,10 @@ function TelemetryDetailComponent() {
|
||||
/>
|
||||
</Card.Grid>
|
||||
|
||||
<Card.Grid hoverable={false} className="min-h-[470px] !w-1/3">
|
||||
<Card.Grid
|
||||
hoverable={false}
|
||||
className="min-h-[470px] !w-full sm:!w-1/3"
|
||||
>
|
||||
<TelemetryMetricsTable
|
||||
telemetryId={telemetryId}
|
||||
type="event"
|
||||
@ -192,7 +198,10 @@ function TelemetryDetailComponent() {
|
||||
/>
|
||||
</Card.Grid>
|
||||
|
||||
<Card.Grid hoverable={false} className="min-h-[470px] !w-1/3">
|
||||
<Card.Grid
|
||||
hoverable={false}
|
||||
className="min-h-[470px] !w-full sm:!w-1/3"
|
||||
>
|
||||
<TelemetryMetricsTable
|
||||
telemetryId={telemetryId}
|
||||
type="country"
|
||||
|
@ -83,7 +83,10 @@ function WebsiteDetailComponent() {
|
||||
<Card.Grid hoverable={false} className="!w-full">
|
||||
<WebsiteOverview website={website} showDateFilter={true} />
|
||||
</Card.Grid>
|
||||
<Card.Grid hoverable={false} className="min-h-[470px] !w-1/2">
|
||||
<Card.Grid
|
||||
hoverable={false}
|
||||
className="!w-full sm:min-h-[470px] sm:!w-1/2"
|
||||
>
|
||||
<WebsiteMetricsTable
|
||||
websiteId={websiteId}
|
||||
type="url"
|
||||
@ -92,7 +95,10 @@ function WebsiteDetailComponent() {
|
||||
endAt={endAt}
|
||||
/>
|
||||
</Card.Grid>
|
||||
<Card.Grid hoverable={false} className="min-h-[470px] !w-1/2">
|
||||
<Card.Grid
|
||||
hoverable={false}
|
||||
className="!w-full sm:min-h-[470px] sm:!w-1/2"
|
||||
>
|
||||
<WebsiteMetricsTable
|
||||
websiteId={websiteId}
|
||||
type="referrer"
|
||||
@ -101,7 +107,10 @@ function WebsiteDetailComponent() {
|
||||
endAt={endAt}
|
||||
/>
|
||||
</Card.Grid>
|
||||
<Card.Grid hoverable={false} className="min-h-[470px] !w-1/3">
|
||||
<Card.Grid
|
||||
hoverable={false}
|
||||
className="!w-full sm:min-h-[470px] sm:!w-1/3"
|
||||
>
|
||||
<WebsiteMetricsTable
|
||||
websiteId={websiteId}
|
||||
type="browser"
|
||||
@ -110,7 +119,10 @@ function WebsiteDetailComponent() {
|
||||
endAt={endAt}
|
||||
/>
|
||||
</Card.Grid>
|
||||
<Card.Grid hoverable={false} className="min-h-[470px] !w-1/3">
|
||||
<Card.Grid
|
||||
hoverable={false}
|
||||
className="!w-full sm:min-h-[470px] sm:!w-1/3"
|
||||
>
|
||||
<WebsiteMetricsTable
|
||||
websiteId={websiteId}
|
||||
type="os"
|
||||
@ -119,7 +131,10 @@ function WebsiteDetailComponent() {
|
||||
endAt={endAt}
|
||||
/>
|
||||
</Card.Grid>
|
||||
<Card.Grid hoverable={false} className="min-h-[470px] !w-1/3">
|
||||
<Card.Grid
|
||||
hoverable={false}
|
||||
className="!w-full sm:min-h-[470px] sm:!w-1/3"
|
||||
>
|
||||
<WebsiteMetricsTable
|
||||
websiteId={websiteId}
|
||||
type="device"
|
||||
@ -128,7 +143,10 @@ function WebsiteDetailComponent() {
|
||||
endAt={endAt}
|
||||
/>
|
||||
</Card.Grid>
|
||||
<Card.Grid hoverable={false} className="min-h-[470px] !w-1/3">
|
||||
<Card.Grid
|
||||
hoverable={false}
|
||||
className="!w-full sm:min-h-[470px] sm:!w-1/3"
|
||||
>
|
||||
<WebsiteMetricsTable
|
||||
websiteId={websiteId}
|
||||
type="title"
|
||||
@ -137,7 +155,10 @@ function WebsiteDetailComponent() {
|
||||
endAt={endAt}
|
||||
/>
|
||||
</Card.Grid>
|
||||
<Card.Grid hoverable={false} className="min-h-[470px] !w-1/3">
|
||||
<Card.Grid
|
||||
hoverable={false}
|
||||
className="!w-full sm:min-h-[470px] sm:!w-1/3"
|
||||
>
|
||||
<WebsiteMetricsTable
|
||||
websiteId={websiteId}
|
||||
type="country"
|
||||
@ -150,7 +171,10 @@ function WebsiteDetailComponent() {
|
||||
<WebsiteVisitorMapBtn websiteId={websiteId} />
|
||||
</div>
|
||||
</Card.Grid>
|
||||
<Card.Grid hoverable={false} className="min-h-[470px] !w-1/3">
|
||||
<Card.Grid
|
||||
hoverable={false}
|
||||
className="!w-full sm:min-h-[470px] sm:!w-1/3"
|
||||
>
|
||||
<WebsiteMetricsTable
|
||||
websiteId={websiteId}
|
||||
type="event"
|
||||
|
24
src/client/utils/event.ts
Normal file
24
src/client/utils/event.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { useEvent } from '@/hooks/useEvent';
|
||||
import { EventEmitter } from 'eventemitter-strict';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export interface GlobalEventMap {
|
||||
commonListSelected: () => void;
|
||||
}
|
||||
|
||||
export const globalEventBus = new EventEmitter<GlobalEventMap>();
|
||||
|
||||
export function useGlobalEventSubscribe<T extends keyof GlobalEventMap>(
|
||||
eventName: T,
|
||||
callback: GlobalEventMap[T]
|
||||
) {
|
||||
const fn = useEvent(callback);
|
||||
|
||||
useEffect(() => {
|
||||
globalEventBus.on(eventName, fn);
|
||||
|
||||
return () => {
|
||||
globalEventBus.off(eventName, fn);
|
||||
};
|
||||
}, [eventName]);
|
||||
}
|
@ -82,7 +82,7 @@ function HomepageMain() {
|
||||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
<div className="rounded-lg border-8 border-solid border-gray-200 shadow-lg">
|
||||
<div className="rounded-lg border-8 border-solid border-gray-200 shadow-lg dark:border-gray-800">
|
||||
<Carousel
|
||||
className="cursor-move"
|
||||
showThumbs={false}
|
||||
|
Loading…
Reference in New Issue
Block a user