You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tailchat/server/admin/src/client/components/Dashboard.tsx

300 lines
7.5 KiB
TypeScript

import { IconFile, IconMessage, IconUser, IconUserGroup } from 'tushan/icon';
import React from 'react';
import {
XAxis,
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer,
AreaChart,
Area,
} from 'tushan/chart';
import {
Card,
Link,
Space,
Grid,
Divider,
Typography,
useUserStore,
createSelector,
useTranslation,
useGetList,
useAsync,
} from 'tushan';
import { request } from '../request';
const { GridItem } = Grid;
export const Dashboard: React.FC = React.memo(() => {
const { userIdentity } = useUserStore(createSelector('userIdentity'));
const { t } = useTranslation();
return (
<div>
<div>
<Space direction="vertical" style={{ width: '100%' }}>
<Card bordered={false}>
<Typography.Title heading={5}>
{t('tushan.dashboard.welcome', {
name: userIdentity.fullName,
})}
</Typography.Title>
<Divider />
<DashboardSummary />
<Divider />
<Typography.Title heading={6} style={{ marginBottom: 10 }}>
{t('custom.dashboard.newUserCount')}
</Typography.Title>
<UserCountChart />
<Typography.Title heading={6} style={{ marginBottom: 10 }}>
{t('custom.dashboard.messageCount')}
</Typography.Title>
<MessageCountChart />
</Card>
<Grid cols={3} colGap={12} rowGap={16}>
<GridItem>
<DashboardItem title="Docs" href="https://tailchat.msgbyte.com/">
{t('tushan.dashboard.tip.docs')}
</DashboardItem>
</GridItem>
<GridItem>
<DashboardItem
title="Github"
href="https://github.com/msgbyte/tailchat"
>
{t('custom.dashboard.tip.github')}
</DashboardItem>
</GridItem>
<GridItem>
<DashboardItem
title="Provide by Tushan"
href="https://tushan.msgbyte.com/"
>
{t('custom.dashboard.tip.tushan')}
</DashboardItem>
</GridItem>
</Grid>
</Space>
</div>
</div>
);
});
Dashboard.displayName = 'Dashboard';
const DashboardSummary: React.FC = React.memo(() => {
const { t } = useTranslation();
const { total: usersNum } = useGetList('users', {
pagination: { page: 1, perPage: 1 },
});
const { total: groupNum } = useGetList('groups', {
pagination: { page: 1, perPage: 1 },
});
const { total: fileNum } = useGetList('file', {
pagination: { page: 1, perPage: 1 },
});
const { total: messagesNum } = useGetList('messages', {
pagination: { page: 1, perPage: 1 },
});
return (
<Grid.Row justify="center">
<Grid.Col flex={1} style={{ paddingLeft: '1rem' }}>
<DataItem
icon={<IconUser />}
title={t('tushan.dashboard.user')}
count={usersNum}
/>
</Grid.Col>
<Divider type="vertical" style={{ height: 40 }} />
<Grid.Col flex={1} style={{ paddingLeft: '1rem' }}>
<DataItem
icon={<IconUserGroup />}
title={t('tushan.dashboard.group')}
count={groupNum}
/>
</Grid.Col>
<Divider type="vertical" style={{ height: 40 }} />
<Grid.Col flex={1} style={{ paddingLeft: '1rem' }}>
<DataItem
icon={<IconFile />}
title={t('custom.dashboard.file')}
count={fileNum}
/>
</Grid.Col>
<Divider type="vertical" style={{ height: 40 }} />
<Grid.Col flex={1} style={{ paddingLeft: '1rem' }}>
<DataItem
icon={<IconMessage />}
title={t('custom.dashboard.messages')}
count={messagesNum}
/>
</Grid.Col>
</Grid.Row>
);
});
DashboardSummary.displayName = 'DashboardSummary';
const DashboardItem: React.FC<
React.PropsWithChildren<{
title: string;
href?: string;
}>
> = React.memo((props) => {
const { t } = useTranslation();
return (
<Card
title={props.title}
extra={
props.href && (
<Link target="_blank" href={props.href}>
{t('tushan.dashboard.more')}
</Link>
)
}
bordered={false}
style={{ overflow: 'hidden' }}
>
{props.children}
</Card>
);
});
DashboardItem.displayName = 'DashboardItem';
const DataItem: React.FC<{
icon: React.ReactElement;
title: string;
count: number;
}> = React.memo((props) => {
return (
<Space>
<div
style={{
fontSize: 20,
padding: '0.5rem',
borderRadius: '9999px',
border: '1px solid #ccc',
width: 24,
height: 24,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
{props.icon}
</div>
<div>
<div style={{ fontWeight: 700 }}>{props.title}</div>
<div>{props.count}</div>
</div>
</Space>
);
});
DataItem.displayName = 'DataItem';
const UserCountChart: React.FC = React.memo(() => {
const { t } = useTranslation();
const { value: newUserCountSummary } = useAsync(async () => {
const { data } = await request.get<{
summary: {
count: number;
date: string;
}[];
}>('/user/count/summary');
return data.summary;
}, []);
return (
<ResponsiveContainer width="100%" height={320}>
<AreaChart
width={730}
height={250}
data={newUserCountSummary}
margin={{ top: 10, right: 30, left: 0, bottom: 0 }}
>
<defs>
<linearGradient id="colorUv" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#8884d8" stopOpacity={0.8} />
<stop offset="95%" stopColor="#8884d8" stopOpacity={0} />
</linearGradient>
</defs>
<XAxis dataKey="date" />
<YAxis />
<CartesianGrid strokeDasharray="3 3" />
<Tooltip />
<Area
type="monotone"
dataKey="count"
label={t('custom.dashboard.newUserCount')}
stroke="#8884d8"
fillOpacity={1}
fill="url(#colorUv)"
/>
</AreaChart>
</ResponsiveContainer>
);
});
UserCountChart.displayName = 'UserCountChart';
const MessageCountChart: React.FC = React.memo(() => {
const { t } = useTranslation();
const { value: messageCountSummary } = useAsync(async () => {
const { data } = await request.get<{
summary: {
count: number;
date: string;
}[];
}>('/message/count/summary');
return data.summary;
}, []);
return (
<ResponsiveContainer width="100%" height={320}>
<AreaChart
width={730}
height={250}
data={messageCountSummary}
margin={{ top: 10, right: 30, left: 0, bottom: 0 }}
>
<defs>
<linearGradient id="colorUv" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#8884d8" stopOpacity={0.8} />
<stop offset="95%" stopColor="#8884d8" stopOpacity={0} />
</linearGradient>
</defs>
<XAxis dataKey="date" />
<YAxis />
<CartesianGrid strokeDasharray="3 3" />
<Tooltip />
<Area
type="monotone"
dataKey="count"
label={t('custom.dashboard.messageCount')}
stroke="#8884d8"
fillOpacity={1}
fill="url(#colorUv)"
/>
</AreaChart>
</ResponsiveContainer>
);
});
MessageCountChart.displayName = 'MessageCountChart';