tianji/src/client/components/WebsiteOverview.tsx

192 lines
5.0 KiB
TypeScript
Raw Normal View History

2023-09-17 06:41:50 +00:00
import { Button, message, Tag } from 'antd';
2023-09-15 16:03:09 +00:00
import React, { useMemo } from 'react';
import { Column, ColumnConfig } from '@ant-design/charts';
2023-09-01 16:52:43 +00:00
import { ArrowRightOutlined, SyncOutlined } from '@ant-design/icons';
import { DateFilter } from './DateFilter';
import { HealthBar } from './HealthBar';
2023-09-15 16:03:09 +00:00
import {
useWorkspaceWebsitePageview,
useWorspaceWebsites,
WebsiteInfo,
} from '../api/model/website';
import { Loading } from './Loading';
2023-09-15 16:03:09 +00:00
import dayjs from 'dayjs';
import {
DateUnit,
formatDate,
formatDateWithUnit,
getDateArray,
} from '../utils/date';
2023-09-17 06:41:50 +00:00
import { useEvent } from '../hooks/useEvent';
2023-09-01 16:52:43 +00:00
interface WebsiteOverviewProps {
workspaceId: string;
}
export const WebsiteOverview: React.FC<WebsiteOverviewProps> = React.memo(
(props) => {
const { isLoading, websites } = useWorspaceWebsites(props.workspaceId);
if (isLoading) {
return <Loading />;
}
return (
<div>
{websites.map((website) => (
<WebsiteOverviewItem key={website.id} website={website} />
))}
</div>
);
}
);
WebsiteOverview.displayName = 'WebsiteOverview';
const WebsiteOverviewItem: React.FC<{
website: WebsiteInfo;
}> = React.memo((props) => {
2023-09-15 16:03:09 +00:00
const unit: DateUnit = 'hour';
const startDate = dayjs().subtract(1, 'day').add(1, unit).startOf(unit);
const endDate = dayjs().endOf(unit);
2023-09-17 06:41:50 +00:00
const { pageviews, sessions, isLoading, refetch } =
useWorkspaceWebsitePageview(
props.website.workspaceId,
props.website.id,
startDate.unix() * 1000,
endDate.unix() * 1000
);
const handleRefresh = useEvent(async () => {
await refetch();
message.success('Refreshed');
});
2023-09-15 16:03:09 +00:00
const chartData = useMemo(() => {
2023-09-16 08:12:19 +00:00
const pageviewsArr = getDateArray(pageviews, startDate, endDate, unit);
const sessionsArr = getDateArray(sessions, startDate, endDate, unit);
return [
...pageviewsArr.map((item) => ({ ...item, type: 'pageview' })),
...sessionsArr.map((item) => ({ ...item, type: 'session' })),
];
}, [pageviews, sessions, unit]);
2023-09-15 16:03:09 +00:00
if (isLoading) {
return <Loading />;
}
2023-09-01 16:52:43 +00:00
return (
<div className="mb-10 pb-10 border-b">
2023-09-01 16:52:43 +00:00
<div className="flex">
<div className="flex flex-1 text-2xl font-bold items-center">
<span className="mr-2" title={props.website.domain ?? ''}>
{props.website.name}
</span>
2023-09-01 16:52:43 +00:00
<HealthBar
beats={Array.from({ length: 13 }).map(() => ({
status: 'health',
}))}
2023-09-01 16:52:43 +00:00
/>
</div>
<div>
<Button type="primary" size="large">
View Details <ArrowRightOutlined />
</Button>
</div>
</div>
<div className="flex mb-10 flex-wrap">
<div className="flex gap-5 flex-wrap w-full lg:w-2/3">
<MetricCard label="Views" value={20} diff={20} />
<MetricCard label="Visitors" value={20} diff={20} />
<MetricCard label="Bounce rate" value={20} diff={-20} unit="%" />
<MetricCard
label="Average visit time"
value={20}
diff={-20}
unit="s"
/>
</div>
<div className="flex items-center gap-2 justify-end w-full lg:w-1/3">
2023-09-17 06:41:50 +00:00
<Button
size="large"
icon={<SyncOutlined />}
onClick={handleRefresh}
/>
2023-09-01 16:52:43 +00:00
<DateFilter />
</div>
</div>
<div>
2023-09-15 16:03:09 +00:00
<StatsChart data={chartData} unit={unit} />
2023-09-01 16:52:43 +00:00
</div>
</div>
);
});
WebsiteOverviewItem.displayName = 'WebsiteOverviewItem';
2023-09-01 16:52:43 +00:00
const MetricCard: React.FC<{
label: string;
value: number;
diff: number;
unit?: string;
}> = React.memo((props) => {
const unit = props.unit ?? '';
return (
<div className="flex flex-col justify-center min-w-[140px] min-h-[90px]">
<div className="flex items-center whitespace-nowrap font-bold text-4xl">
{String(props.value)}
{unit}
</div>
<div className="flex items-center whitespace-nowrap font-bold">
<span className="mr-2">{props.label}</span>
<Tag color={props.diff >= 0 ? 'green' : 'red'}>
{props.diff >= 0 ? `+${props.diff}${unit}` : `${props.diff}${unit}`}
</Tag>
</div>
</div>
);
});
MetricCard.displayName = 'MetricCard';
2023-09-15 16:03:09 +00:00
export const StatsChart: React.FC<{
2023-09-16 08:12:19 +00:00
data: { x: string; y: number; type: string }[];
2023-09-15 16:03:09 +00:00
unit: DateUnit;
}> = React.memo((props) => {
const config: ColumnConfig = useMemo(
() => ({
data: props.data,
2023-09-16 08:12:19 +00:00
isStack: true,
2023-09-15 16:03:09 +00:00
xField: 'x',
yField: 'y',
2023-09-16 08:12:19 +00:00
seriesField: 'type',
2023-09-01 16:52:43 +00:00
label: {
2023-09-15 16:03:09 +00:00
position: 'middle' as const,
style: {
fill: '#FFFFFF',
opacity: 0.6,
},
2023-09-01 16:52:43 +00:00
},
2023-09-15 16:03:09 +00:00
tooltip: {
title: (t) => formatDate(t),
2023-09-01 16:52:43 +00:00
},
2023-09-15 16:03:09 +00:00
xAxis: {
label: {
autoHide: true,
autoRotate: false,
formatter: (text) => formatDateWithUnit(text, props.unit),
},
2023-09-01 16:52:43 +00:00
},
2023-09-15 16:03:09 +00:00
}),
[props.data, props.unit]
);
2023-09-01 16:52:43 +00:00
return <Column {...config} />;
});
2023-09-15 16:03:09 +00:00
StatsChart.displayName = 'StatsChart';