feat(admin-next): add user count chart

pull/90/head
moonrailgun 2 years ago
parent fd4bc5ffea
commit 54a73403d7

@ -85,6 +85,12 @@ export const Dashboard: React.FC = React.memo(() => {
<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>
@ -93,12 +99,12 @@ export const Dashboard: React.FC = React.memo(() => {
</Card>
<Grid cols={3} colGap={12} rowGap={16}>
<GridItem index={0}>
<GridItem>
<DashboardItem title="Docs" href="https://tailchat.msgbyte.com/">
{t('tushan.dashboard.tip.docs')}
</DashboardItem>
</GridItem>
<GridItem index={0}>
<GridItem>
<DashboardItem
title="Github"
href="https://github.com/msgbyte/tailchat"
@ -106,7 +112,7 @@ export const Dashboard: React.FC = React.memo(() => {
{t('custom.dashboard.tip.github')}
</DashboardItem>
</GridItem>
<GridItem index={0}>
<GridItem>
<DashboardItem
title="Provide by Tushan"
href="https://tushan.msgbyte.com/"
@ -180,6 +186,51 @@ const DataItem: React.FC<{
});
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 () => {

@ -1,4 +1,4 @@
import { TushanContextProps } from 'tushan';
import type { TushanContextProps } from 'tushan';
import { i18nEnTranslation } from 'tushan/client/i18n/resources/en';
import { i18nZhTranslation } from 'tushan/client/i18n/resources/zh';
@ -15,6 +15,7 @@ export const i18n: TushanContextProps['i18n'] = {
},
dashboard: {
file: 'File',
newUserCount: 'New User Count',
messageCount: 'Message Count',
tip: {
github:
@ -137,6 +138,7 @@ export const i18n: TushanContextProps['i18n'] = {
},
dashboard: {
file: '文件',
newUserCount: '用户新增',
messageCount: '消息数',
tip: {
github: 'Tailchat 是在你私有空间内的下一代noIM应用',

@ -6,6 +6,7 @@ import { configRouter } from './config';
import { networkRouter } from './network';
import { fileRouter } from './file';
import dayjs from 'dayjs';
import userModel from '../../../../models/user/user';
import messageModel from '../../../../models/chat/message';
import { raExpressMongoose } from '../middleware/express-mongoose-ra-json-server';
@ -46,10 +47,60 @@ router.use('/network', networkRouter);
router.use('/config', configRouter);
router.use('/file', fileRouter);
router.get('/user/count/summary', auth(), async (req, res) => {
// 返回最近7天的用户数统计
const day = 7;
const aggregateRes: { count: number; date: string }[] = await userModel
.aggregate([
{
$match: {
createdAt: {
$gte: dayjs().subtract(day, 'd').startOf('d').toDate(),
$lt: dayjs().endOf('d').toDate(),
},
},
},
{
$group: {
_id: {
createdAt: {
$dateToString: {
format: '%Y-%m-%d',
date: '$createdAt',
},
},
} as any,
count: {
$sum: 1,
},
},
},
{
$project: {
date: '$_id.createdAt',
count: '$count',
},
},
])
.exec();
const summary = Array.from({ length: day })
.map((_, d) => {
const date = dayjs().subtract(d, 'd').format('YYYY-MM-DD');
return {
date,
count: aggregateRes.find((r) => r.date === date)?.count ?? 0,
};
})
.reverse();
res.json({ summary });
});
router.use(
'/users',
auth(),
raExpressMongoose(require('../../../../models/user/user').default, {
raExpressMongoose(userModel, {
q: ['nickname', 'email'],
})
);

Loading…
Cancel
Save