fix(dashboard): daily-графики и счётчики из GET /v1/admin/stats

Refs EventHub/EventHubFrontAdmin#26

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-13 17:35:51 +03:00
parent da3fcbb6b0
commit 61da5b6d3a
3 changed files with 133 additions and 50 deletions
+50
View File
@@ -0,0 +1,50 @@
import React from 'react';
import { Card, Col } from 'antd';
import {
LineChart,
Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
Legend,
ResponsiveContainer,
} from 'recharts';
import { DayCount } from '../types/api';
interface Props {
title: string;
data?: DayCount[];
color: string;
lg?: number;
}
const DailyLineChartCard: React.FC<Props> = ({ title, data, color, lg = 12 }) => {
const chartData = (data ?? []).map((item) => ({
date: item.date,
count: item.count,
}));
if (chartData.length === 0) {
return null;
}
return (
<Col xs={24} lg={lg}>
<Card title={title}>
<ResponsiveContainer width="100%" height={300}>
<LineChart data={chartData}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="date" />
<YAxis allowDecimals={false} />
<Tooltip />
<Legend />
<Line type="monotone" dataKey="count" stroke={color} name={title} />
</LineChart>
</ResponsiveContainer>
</Card>
</Col>
);
};
export default DailyLineChartCard;
+69 -48
View File
@@ -8,22 +8,18 @@ import {
WarningOutlined, WarningOutlined,
BugOutlined, BugOutlined,
ClockCircleOutlined, ClockCircleOutlined,
DollarOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import { useDashboardStats } from '../../hooks/useDashboard'; import { useDashboardStats } from '../../hooks/useDashboard';
import type { ColumnsType } from 'antd/es/table'; import type { ColumnsType } from 'antd/es/table';
import { AdminActivity } from '../../types/api'; import { AdminActivity } from '../../types/api';
import { import {
LineChart,
Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
Legend,
ResponsiveContainer,
AreaChart, AreaChart,
Area, Area,
XAxis,
Tooltip,
} from 'recharts'; } from 'recharts';
import DailyLineChartCard from '../../components/DailyLineChartCard';
const DashboardPage: React.FC = () => { const DashboardPage: React.FC = () => {
const { data, isLoading, error } = useDashboardStats(); const { data, isLoading, error } = useDashboardStats();
@@ -54,12 +50,12 @@ const DashboardPage: React.FC = () => {
{ title: 'Последний вход', dataIndex: 'last_login', key: 'last_login' }, { title: 'Последний вход', dataIndex: 'last_login', key: 'last_login' },
]; ];
const eventsChartData = data.events_by_day?.map(item => ({ const eventsChartData = data.events_by_day?.map((item) => ({
date: item.date, date: item.date,
events: item.count, events: item.count,
})) || []; })) || [];
const registrationsChartData = data.registrations_by_day?.map(item => ({ const registrationsChartData = data.registrations_by_day?.map((item) => ({
date: item.date, date: item.date,
registrations: item.count, registrations: item.count,
})) || []; })) || [];
@@ -68,7 +64,6 @@ const DashboardPage: React.FC = () => {
<div> <div>
<h2>Общая статистика</h2> <h2>Общая статистика</h2>
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
{/* Пользователи с мини-графиком */}
<Col xs={24} sm={12} lg={6}> <Col xs={24} sm={12} lg={6}>
<Card> <Card>
<div style={{ display: 'flex', alignItems: 'center' }}> <div style={{ display: 'flex', alignItems: 'center' }}>
@@ -109,7 +104,6 @@ const DashboardPage: React.FC = () => {
</Card> </Card>
</Col> </Col>
{/* События с мини-графиком */}
<Col xs={24} sm={12} lg={6}> <Col xs={24} sm={12} lg={6}>
<Card> <Card>
<div style={{ display: 'flex', alignItems: 'center' }}> <div style={{ display: 'flex', alignItems: 'center' }}>
@@ -190,47 +184,74 @@ const DashboardPage: React.FC = () => {
/> />
</Card> </Card>
</Col> </Col>
{data.pending_users_total !== undefined && (
<Col xs={24} sm={12} lg={6}>
<Card>
<Statistic title="Неподтверждённые" value={data.pending_users_total} prefix={<UserOutlined />} />
</Card>
</Col>
)}
{data.reports_pending !== undefined && (
<Col xs={24} sm={12} lg={6}>
<Card>
<Statistic title="Жалобы: pending" value={data.reports_pending} />
</Card>
</Col>
)}
{data.reports_reviewed !== undefined && (
<Col xs={24} sm={12} lg={6}>
<Card>
<Statistic title="Жалобы: reviewed" value={data.reports_reviewed} />
</Card>
</Col>
)}
{data.reports_dismissed !== undefined && (
<Col xs={24} sm={12} lg={6}>
<Card>
<Statistic title="Жалобы: dismissed" value={data.reports_dismissed} />
</Card>
</Col>
)}
{data.subscriptions_total !== undefined && (
<Col xs={24} sm={12} lg={6}>
<Card>
<Statistic title="Подписки" value={data.subscriptions_total} prefix={<DollarOutlined />} />
</Card>
</Col>
)}
{data.trial_subscriptions !== undefined && (
<Col xs={24} sm={12} lg={6}>
<Card>
<Statistic title="Пробные подписки" value={data.trial_subscriptions} />
</Card>
</Col>
)}
</Row> </Row>
<Row gutter={[16, 16]} style={{ marginTop: 32 }}> <Row gutter={[16, 16]} style={{ marginTop: 32 }}>
<Col xs={24} lg={12}> <DailyLineChartCard title="События по дням" data={data.events_by_day} color="#1890ff" />
<Card title="События по дням"> <DailyLineChartCard title="Регистрации по дням" data={data.registrations_by_day} color="#52c41a" />
<ResponsiveContainer width="100%" height={300}> <DailyLineChartCard title="Отзывы по дням" data={data.reviews_by_day} color="#faad14" />
<LineChart data={eventsChartData}> <DailyLineChartCard title="Календари по дням" data={data.calendars_by_day} color="#722ed1" />
<CartesianGrid strokeDasharray="3 3" /> <DailyLineChartCard title="Жалобы по дням" data={data.reports_by_day} color="#ff4d4f" />
<XAxis dataKey="date" /> <DailyLineChartCard title="Тикеты по дням" data={data.tickets_by_day} color="#13c2c2" />
<YAxis allowDecimals={false} /> <DailyLineChartCard title="Подписки по дням" data={data.subscriptions_by_day} color="#eb2f96" />
<Tooltip /> <DailyLineChartCard title="Неподтверждённые по дням" data={data.pending_users_by_day} color="#fa8c16" />
<Legend />
<Line type="monotone" dataKey="events" stroke="#1890ff" name="События" />
</LineChart>
</ResponsiveContainer>
</Card>
</Col>
<Col xs={24} lg={12}>
<Card title="Регистрации по дням">
<ResponsiveContainer width="100%" height={300}>
<LineChart data={registrationsChartData}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="date" />
<YAxis allowDecimals={false} />
<Tooltip />
<Legend />
<Line type="monotone" dataKey="registrations" stroke="#52c41a" name="Регистрации" />
</LineChart>
</ResponsiveContainer>
</Card>
</Col>
</Row> </Row>
<h2 style={{ marginTop: 32 }}>Активность администраторов</h2> {data.admin_activity && data.admin_activity.length > 0 && (
<Table <>
columns={adminColumns} <h2 style={{ marginTop: 32 }}>Активность администраторов</h2>
dataSource={data.admin_activity} <Table
rowKey="admin_id" columns={adminColumns}
pagination={false} dataSource={data.admin_activity}
size="small" rowKey="admin_id"
/> pagination={false}
size="small"
/>
</>
)}
</div> </div>
); );
}; };
+13 -1
View File
@@ -328,16 +328,28 @@ export interface AuditListParams {
// ===================== Dashboard Stats ===================== // ===================== Dashboard Stats =====================
export interface DashboardStats { export interface DashboardStats {
users_total: number; users_total: number;
pending_users_total?: number;
events_total: number; events_total: number;
reviews_total: number; reviews_total: number;
calendars_total: number; calendars_total: number;
reports_total: number; reports_total: number;
reports_pending?: number;
reports_reviewed?: number;
reports_dismissed?: number;
tickets_total: number; tickets_total: number;
tickets_open: number; tickets_open: number;
subscriptions_total?: number;
trial_subscriptions?: number;
avg_ticket_resolution_h: number; avg_ticket_resolution_h: number;
admin_activity: AdminActivity[]; admin_activity?: AdminActivity[];
events_by_day: DayCount[]; events_by_day: DayCount[];
registrations_by_day: DayCount[]; registrations_by_day: DayCount[];
reviews_by_day?: DayCount[];
calendars_by_day?: DayCount[];
reports_by_day?: DayCount[];
tickets_by_day?: DayCount[];
subscriptions_by_day?: DayCount[];
pending_users_by_day?: DayCount[];
} }
export interface AdminActivity { export interface AdminActivity {