2023-09-01 16:52:43 +00:00
|
|
|
import { Button, Tag } from 'antd';
|
|
|
|
import React from 'react';
|
|
|
|
import { Column } from '@ant-design/charts';
|
|
|
|
import { ArrowRightOutlined, SyncOutlined } from '@ant-design/icons';
|
|
|
|
import { DateFilter } from './DateFilter';
|
|
|
|
import { HealthBar } from './HealthBar';
|
2023-09-10 07:55:04 +00:00
|
|
|
import { useWorspaceWebsites, WebsiteInfo } from '../api/model/website';
|
|
|
|
import { Loading } from './Loading';
|
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-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>
|
|
|
|
<DemoChart />
|
|
|
|
</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';
|
|
|
|
|
|
|
|
export const DemoChart: React.FC = React.memo(() => {
|
|
|
|
const data = [
|
|
|
|
{
|
|
|
|
type: '家具家电',
|
|
|
|
sales: 38,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
type: '粮油副食',
|
|
|
|
sales: 52,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
type: '生鲜水果',
|
|
|
|
sales: 61,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
type: '美容洗护',
|
|
|
|
sales: 145,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
type: '母婴用品',
|
|
|
|
sales: 48,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
type: '进口食品',
|
|
|
|
sales: 38,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
type: '食品饮料',
|
|
|
|
sales: 38,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
type: '家庭清洁',
|
|
|
|
sales: 38,
|
|
|
|
},
|
|
|
|
];
|
|
|
|
const config = {
|
|
|
|
data,
|
|
|
|
xField: 'type',
|
|
|
|
yField: 'sales',
|
|
|
|
label: {
|
|
|
|
// 可手动配置 label 数据标签位置
|
|
|
|
position: 'middle' as const,
|
|
|
|
// 'top', 'bottom', 'middle',
|
|
|
|
// 配置样式
|
|
|
|
style: {
|
|
|
|
fill: '#FFFFFF',
|
|
|
|
opacity: 0.6,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
xAxis: {
|
|
|
|
label: {
|
|
|
|
autoHide: true,
|
|
|
|
autoRotate: false,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
meta: {
|
|
|
|
type: {
|
|
|
|
alias: '类别',
|
|
|
|
},
|
|
|
|
sales: {
|
|
|
|
alias: '销售额',
|
|
|
|
},
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
return <Column {...config} />;
|
|
|
|
});
|
|
|
|
DemoChart.displayName = 'DemoChart';
|