fix(stats): top-списки и группировка по типу на list-страницах

Refs EventHub/EventHubFrontAdmin#27

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-13 17:35:51 +03:00
parent 61da5b6d3a
commit 190cca7e6b
4 changed files with 117 additions and 1 deletions
+29
View File
@@ -204,8 +204,37 @@ const CalendarListPage: React.FC = () => {
</Card> </Card>
</Col> </Col>
))} ))}
{Object.entries(stats.calendars_by_type || {}).map(([type, count]) => (
<Col xs={12} sm={6} md={4} key={`type-${type}`}>
<Card>
<Statistic title={`Тип: ${type}`} value={count} />
</Card>
</Col>
))}
</Row> </Row>
)} )}
{stats?.top_calendars_by_rating && stats.top_calendars_by_rating.length > 0 && (
<Card title="Топ календарей по рейтингу" style={{ marginBottom: 16 }}>
<Table
size="small"
pagination={false}
rowKey="id"
dataSource={stats.top_calendars_by_rating}
columns={[
{
title: 'Название',
dataIndex: 'title',
key: 'title',
render: (title: string, record) => (
<Link to={`/calendars/${record.id}`}>{title || record.id}</Link>
),
},
{ title: 'Рейтинг', dataIndex: 'rating_avg', key: 'rating_avg' },
{ title: 'Отзывов', dataIndex: 'review_count', key: 'review_count' },
]}
/>
</Card>
)}
<Space style={{ marginBottom: 16 }} wrap> <Space style={{ marginBottom: 16 }} wrap>
<Input.Search <Input.Search
placeholder="Поиск" placeholder="Поиск"
+30 -1
View File
@@ -1,7 +1,7 @@
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useEffect, useRef } from 'react';
import { Table, Button, Tag, Space, Modal, Form, Select, Spin, Input, Tooltip, DatePicker, InputNumber, Card, Col, Row, Statistic } from 'antd'; import { Table, Button, Tag, Space, Modal, Form, Select, Spin, Input, Tooltip, DatePicker, InputNumber, Card, Col, Row, Statistic } from 'antd';
import { InfoCircleOutlined, EditOutlined, DeleteOutlined, CalendarOutlined } from '@ant-design/icons'; import { InfoCircleOutlined, EditOutlined, DeleteOutlined, CalendarOutlined } from '@ant-design/icons';
import { useNavigate } from 'react-router-dom'; import { Link, useNavigate } from 'react-router-dom';
import { useEvents, useUpdateEvent, useDeleteEvent, useEvent, useEventStats } from '../../hooks/useEvents'; import { useEvents, useUpdateEvent, useDeleteEvent, useEvent, useEventStats } from '../../hooks/useEvents';
import { Event, EventListParams } from '../../types/api'; import { Event, EventListParams } from '../../types/api';
import type { ColumnsType, SorterResult } from 'antd/es/table/interface'; import type { ColumnsType, SorterResult } from 'antd/es/table/interface';
@@ -228,8 +228,37 @@ const EventListPage: React.FC = () => {
</Card> </Card>
</Col> </Col>
))} ))}
{Object.entries(stats.events_by_type || {}).map(([type, count]) => (
<Col xs={12} sm={6} md={4} key={`type-${type}`}>
<Card>
<Statistic title={`Тип: ${type}`} value={count} />
</Card>
</Col>
))}
</Row> </Row>
)} )}
{stats?.top_events_by_rating && stats.top_events_by_rating.length > 0 && (
<Card title="Топ событий по рейтингу" style={{ marginBottom: 16 }}>
<Table
size="small"
pagination={false}
rowKey="id"
dataSource={stats.top_events_by_rating}
columns={[
{
title: 'Название',
dataIndex: 'title',
key: 'title',
render: (title: string, record) => (
<Link to={`/events/${record.id}`}>{title || record.id}</Link>
),
},
{ title: 'Рейтинг', dataIndex: 'rating_avg', key: 'rating_avg' },
{ title: 'Отзывов', dataIndex: 'review_count', key: 'review_count' },
]}
/>
</Card>
)}
<Table <Table
columns={columns} columns={columns}
dataSource={data?.data} dataSource={data?.data}
+29
View File
@@ -219,8 +219,37 @@ const ReportListPage: React.FC = () => {
</Card> </Card>
</Col> </Col>
))} ))}
{Object.entries(stats.reports_by_target_type || {}).map(([type, count]) => (
<Col xs={12} sm={6} md={4} key={`type-${type}`}>
<Card>
<Statistic title={`Цель: ${type}`} value={count} />
</Card>
</Col>
))}
</Row> </Row>
)} )}
{stats?.top_targets_by_reports && stats.top_targets_by_reports.length > 0 && (
<Card title="Топ целей по жалобам" style={{ marginBottom: 16 }}>
<Table
size="small"
pagination={false}
rowKey={(r) => `${r.target_type}-${r.target_id}`}
dataSource={stats.top_targets_by_reports}
columns={[
{ title: 'Тип', dataIndex: 'target_type', key: 'target_type' },
{
title: 'ID цели',
dataIndex: 'target_id',
key: 'target_id',
render: (id: string, record) => (
<Link to={`/${record.target_type}s/${id}`}>{id}</Link>
),
},
{ title: 'Жалоб', dataIndex: 'report_count', key: 'report_count' },
]}
/>
</Card>
)}
<Table <Table
columns={columns} columns={columns}
dataSource={data?.data} dataSource={data?.data}
+29
View File
@@ -253,8 +253,37 @@ const ReviewListPage: React.FC = () => {
</Card> </Card>
</Col> </Col>
))} ))}
{Object.entries(stats.reviews_by_target_type || {}).map(([type, count]) => (
<Col xs={12} sm={6} md={4} key={`type-${type}`}>
<Card>
<Statistic title={`Цель: ${type}`} value={count} />
</Card>
</Col>
))}
</Row> </Row>
)} )}
{stats?.top_targets_by_reviews && stats.top_targets_by_reviews.length > 0 && (
<Card title="Топ целей по отзывам" style={{ marginBottom: 16 }}>
<Table
size="small"
pagination={false}
rowKey={(r) => `${r.target_type}-${r.target_id}`}
dataSource={stats.top_targets_by_reviews}
columns={[
{ title: 'Тип', dataIndex: 'target_type', key: 'target_type' },
{
title: 'ID цели',
dataIndex: 'target_id',
key: 'target_id',
render: (id: string, record) => (
<Link to={`/${record.target_type}s/${id}`}>{id}</Link>
),
},
{ title: 'Отзывов', dataIndex: 'review_count', key: 'review_count' },
]}
/>
</Card>
)}
<Space style={{ marginBottom: 16 }}> <Space style={{ marginBottom: 16 }}>
<Button onClick={() => openBulkStatusModal('hidden')} disabled={selectedRowKeys.length === 0}> <Button onClick={() => openBulkStatusModal('hidden')} disabled={selectedRowKeys.length === 0}>
Скрыть выбранные Скрыть выбранные