2023-09-01 16:52:43 +00:00
|
|
|
import { Button, 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';
|
2023-09-10 07:55:04 +00:00
|
|
|
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-01 16:52:43 +00:00
|
|
|
|
2023-09-10 07:55:04 +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);
|
|
|
|
|
|
|
|
const { stats, isLoading } = useWorkspaceWebsitePageview(
|
|
|
|
props.website.workspaceId,
|
|
|
|
props.website.id,
|
|
|
|
startDate.unix() * 1000,
|
|
|
|
endDate.unix() * 1000
|
|
|
|
);
|
|
|
|
|
|
|
|
const chartData = useMemo(() => {
|
|
|
|
return getDateArray(stats, startDate, endDate, unit);
|
|
|
|
}, [stats, unit]);
|
|
|
|
|
|
|
|
if (isLoading) {
|
|
|
|
return <Loading />;
|
|
|
|
}
|
|
|
|
|
2023-09-01 16:52:43 +00:00
|
|
|
return (
|
2023-09-10 07:55:04 +00:00
|
|
|
<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">
|
2023-09-10 07:55:04 +00:00
|
|
|
<span className="mr-2" title={props.website.domain ?? ''}>
|
|
|
|
{props.website.name}
|
|
|
|
</span>
|
2023-09-01 16:52:43 +00:00
|
|
|
|
|
|
|
<HealthBar
|
2023-09-10 07:55:04 +00:00
|
|
|
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">
|
|
|
|
<Button size="large" icon={<SyncOutlined />} />
|
|
|
|
|
|
|
|
<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>
|
|
|
|
);
|
|
|
|
});
|
2023-09-10 07:55:04 +00:00
|
|
|
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<{
|
|
|
|
data: { x: string; y: number }[];
|
|
|
|
unit: DateUnit;
|
|
|
|
}> = React.memo((props) => {
|
|
|
|
const config: ColumnConfig = useMemo(
|
|
|
|
() => ({
|
|
|
|
data: props.data,
|
|
|
|
xField: 'x',
|
|
|
|
yField: 'y',
|
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';
|