add deploy stage

This commit is contained in:
2026-05-23 22:31:14 +03:00
parent f5961c5529
commit 896e3bcd35
26 changed files with 130 additions and 56 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { Card, Descriptions, Spin, Button, Tag, Space, Modal, Form, Input, Select, message } from 'antd';
import { Card, Descriptions, Spin, Button, Tag, Modal, Form, Input, Select, message } from 'antd';
import { EditOutlined } from '@ant-design/icons';
import { useAdmin, useUpdateAdmin } from '../../hooks/useAdmins';
import dayjs from 'dayjs';
+1 -1
View File
@@ -87,7 +87,7 @@ const AdminListPage: React.FC = () => {
const handleTableChange = (
pagination: any,
filters: any,
_filters: any,
sorter: SorterResult<Admin> | SorterResult<Admin>[]
) => {
const s = Array.isArray(sorter) ? sorter[0] : sorter;
+2 -6
View File
@@ -5,7 +5,6 @@ import { useAudit } from '../../hooks/useAudit';
import { useAdmin, useAdmins } from '../../hooks/useAdmins';
import { useUser } from '../../hooks/useUsers';
import { useEvent } from '../../hooks/useEvents';
import { useReview } from '../../hooks/useReviews';
import { AuditRecord, AuditListParams } from '../../types/api';
import type { ColumnsType, SorterResult } from 'antd/es/table/interface';
@@ -43,7 +42,6 @@ const AuditPage: React.FC = () => {
const EntityNameCell: React.FC<{ entityType: string; entityId: string }> = ({ entityType, entityId }) => {
const { data: user, isLoading: loadingUser } = useUser(entityType === 'user' ? entityId : '');
const { data: event, isLoading: loadingEvent } = useEvent(entityType === 'event' ? entityId : '');
const { data: review, isLoading: loadingReview } = useReview(entityType === 'review' ? entityId : '');
if (entityType === 'user') {
if (loadingUser) return <Spin size="small" />;
@@ -60,18 +58,16 @@ const AuditPage: React.FC = () => {
}
if (entityType === 'review') {
if (loadingReview) return <Spin size="small" />;
// Для отзыва показываем ссылку на страницу отзыва
// для review показываем ссылку без загрузки, так как нет API для загрузки названия
return <Link to={`/reviews/${entityId}`}>{entityId}</Link>;
}
// Для остальных типов (calendar, report, ticket, subscription, admin) пока просто ID
return <span>{entityId}</span>;
};
const handleTableChange = (
pagination: any,
filters: any,
_filters: any,
sorter: SorterResult<AuditRecord> | SorterResult<AuditRecord>[]
) => {
const s = Array.isArray(sorter) ? sorter[0] : sorter;
+12 -5
View File
@@ -1,11 +1,12 @@
import React, { useState } from 'react';
import { Table, Button, Input, Space, Popconfirm, Tooltip, Spin } from 'antd';
import { SearchOutlined, DeleteOutlined } from '@ant-design/icons';
import { DeleteOutlined } from '@ant-design/icons';
import { Link } from 'react-router-dom';
import { useBannedWords, useAddBannedWord, useRemoveBannedWord, BannedWordListParams } from '../../hooks/useBannedWords';
import { useBannedWords, useAddBannedWord, useRemoveBannedWord } from '../../hooks/useBannedWords';
import { BannedWordListParams } from '../../api/bannedWordsApi';
import { useAdmin } from '../../hooks/useAdmins';
import { BannedWord } from '../../types/api';
import type { ColumnsType, SorterResult } from 'antd/es/table';
import type { ColumnsType, SorterResult } from 'antd/es/table/interface';
import dayjs from 'dayjs';
const BannedWordsPage: React.FC = () => {
@@ -23,9 +24,10 @@ const BannedWordsPage: React.FC = () => {
}
};
// Обработчик изменения таблицы (сортировка/пагинация)
const handleTableChange = (
pagination: any,
filters: any,
_filters: any,
sorter: SorterResult<BannedWord> | SorterResult<BannedWord>[]
) => {
const s = Array.isArray(sorter) ? sorter[0] : sorter;
@@ -38,6 +40,11 @@ const BannedWordsPage: React.FC = () => {
}));
};
// Обработчик поиска
const handleSearch = (value: string) => {
setParams(prev => ({ ...prev, q: value || undefined, offset: 0 }));
};
// Компонент для отображения "Кем добавлено"
const AddedByCell: React.FC<{ adminId: string | null }> = ({ adminId }) => {
const { data: admin, isLoading: loadingAdmin } = useAdmin(adminId || '');
@@ -108,7 +115,7 @@ const BannedWordsPage: React.FC = () => {
</Button>
<Input.Search
placeholder="Поиск по слову"
onSearch={(value) => setParams(prev => ({ ...prev, q: value || undefined, offset: 0 }))}
onSearch={handleSearch}
style={{ width: 200 }}
allowClear
/>
+1 -1
View File
@@ -1,6 +1,6 @@
import React from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { Card, Descriptions, Spin, Button, Tag, Space } from 'antd';
import { Card, Descriptions, Spin, Button, Tag } from 'antd';
import { Link } from 'react-router-dom';
import { useEvent } from '../../hooks/useEvents';
import dayjs from 'dayjs';
+3 -3
View File
@@ -1,7 +1,7 @@
import React, { useState, useEffect, useRef } from 'react';
import { Table, Button, Tag, Space, Modal, Form, Select, message, Spin, Input, Tooltip, DatePicker, InputNumber } from 'antd';
import { Table, Button, Tag, Space, Modal, Form, Select, Spin, Input, Tooltip, DatePicker, InputNumber } from 'antd';
import { InfoCircleOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';
import { Link, useNavigate } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import { useEvents, useUpdateEvent, useDeleteEvent, useEvent } from '../../hooks/useEvents';
import { Event, EventListParams } from '../../types/api';
import type { ColumnsType, SorterResult } from 'antd/es/table/interface';
@@ -106,7 +106,7 @@ const EventListPage: React.FC = () => {
const handleTableChange = (
pagination: any,
filters: any,
_filters: any,
sorter: SorterResult<Event> | SorterResult<Event>[]
) => {
const s = Array.isArray(sorter) ? sorter[0] : sorter;
+3 -3
View File
@@ -38,7 +38,7 @@ const ProfilePage: React.FC = () => {
);
if (payload.preferences) {
try {
payload.preferences = JSON.parse(payload.preferences);
payload.preferences = JSON.parse(payload.preferences as string);
} catch {
message.error('Поле «Настройки» должно быть валидным JSON');
return;
@@ -72,7 +72,7 @@ const ProfilePage: React.FC = () => {
{!editing ? (
<>
<div style={{ marginBottom: 16, display: 'flex', alignItems: 'center', gap: 16 }}>
<Avatar size={64} icon={<UserOutlined />} src={isBadValue(user.avatar_url) ? undefined : user.avatar_url} />
<Avatar size={64} icon={<UserOutlined />} src={isBadValue(user.avatar_url) ? '-' : <a href={user.avatar_url || undefined} target="_blank" rel="noreferrer">{user.avatar_url}</a>} />
<div>
<h3>{!isBadValue(user.nickname) ? user.nickname : user.email}</h3>
<Tag
@@ -115,7 +115,7 @@ const ProfilePage: React.FC = () => {
<Descriptions.Item label="Язык">{isBadValue(user.language) ? '-' : user.language}</Descriptions.Item>
<Descriptions.Item label="Телефон">{isBadValue(user.phone) ? '-' : user.phone}</Descriptions.Item>
<Descriptions.Item label="Аватар URL">
{isBadValue(user.avatar_url) ? '-' : <a href={user.avatar_url} target="_blank" rel="noreferrer">{user.avatar_url}</a>}
{isBadValue(user.avatar_url) ? '-' : <a href={user.avatar_url || undefined} target="_blank" rel="noreferrer">{user.avatar_url}</a>}
</Descriptions.Item>
<Descriptions.Item label="Настройки">
{isBadValue(user.preferences) ? '-' : <pre>{JSON.stringify(user.preferences, null, 2)}</pre>}
+7 -8
View File
@@ -35,7 +35,7 @@ const ReportListPage: React.FC = () => {
const handleTableChange = (
pagination: any,
filters: any,
_filters: any,
sorter: SorterResult<Report> | SorterResult<Report>[]
) => {
const s = Array.isArray(sorter) ? sorter[0] : sorter;
@@ -60,14 +60,13 @@ const ReportListPage: React.FC = () => {
const { data: user, isLoading: loading } = useUser(reporterId);
if (loading) return <Spin size="small" />;
if (!user) return <span>{reporterId}</span>;
// Используем ту же логику, что и в renderLink
const nick = user.nickname;
const email = user.email;
const isGoodString = (val: any): val is string => !isBadValue(val);
let name = '';
if (!isBadValue(nick)) {
name = nick;
} else if (!isBadValue(email)) {
name = email;
if (isGoodString(user.nickname)) {
name = user.nickname;
} else if (isGoodString(user.email)) {
name = user.email;
} else {
name = user.id;
}
+1 -1
View File
@@ -1,6 +1,6 @@
import React, { useState } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { Card, Descriptions, Spin, Button, Tag, Space, Modal, Form, Select, Input, message } from 'antd';
import { Card, Descriptions, Spin, Button, Tag, Space, Modal, Form, Input, message } from 'antd';
import { Link } from 'react-router-dom';
import { useReview, useUpdateReview } from '../../hooks/useReviews';
import { useUser } from '../../hooks/useUsers';
+7 -7
View File
@@ -92,7 +92,7 @@ const ReviewListPage: React.FC = () => {
// ---- Таблица ----
const handleTableChange = (
pagination: any,
filters: any,
_filters: any,
sorter: SorterResult<Review> | SorterResult<Review>[]
) => {
const s = Array.isArray(sorter) ? sorter[0] : sorter;
@@ -111,13 +111,13 @@ const ReviewListPage: React.FC = () => {
const { data: user, isLoading: loading } = useUser(userId);
if (loading) return <Spin size="small" />;
if (!user) return <span>{userId}</span>;
const nick = user.nickname;
const email = user.email;
const isGoodString = (val: any): val is string => !isBadValue(val);
let name = '';
if (!isBadValue(nick)) {
name = nick;
} else if (!isBadValue(email)) {
name = email;
if (isGoodString(user.nickname)) {
name = user.nickname;
} else if (isGoodString(user.email)) {
name = user.email;
} else {
name = user.id;
}
@@ -72,7 +72,7 @@ const SubscriptionListPage: React.FC = () => {
const handleTableChange = (
pagination: any,
filters: any,
_filters: any,
sorter: SorterResult<Subscription> | SorterResult<Subscription>[]
) => {
const s = Array.isArray(sorter) ? sorter[0] : sorter;
+3 -3
View File
@@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { Card, Descriptions, Spin, Button, Tag, Space, Form, Select, Input, message } from 'antd';
import { Card, Descriptions, Spin, Button, Tag, Space, Form, Select, Input } from 'antd';
import { Link } from 'react-router-dom';
import { useTicket, useUpdateTicket } from '../../hooks/useTickets';
import { useUser } from '../../hooks/useUsers';
@@ -123,7 +123,7 @@ const TicketDetailPage: React.FC = () => {
showSearch
optionFilterProp="label"
>
{(admins || []).map((admin) => {
{(admins?.data || []).map((admin) => {
const label = `${admin.email}${admin.nickname && admin.nickname !== '-' ? ` ${admin.nickname}` : ''}`;
return (
<Select.Option key={admin.id} value={admin.id} label={label}>
+2 -2
View File
@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { Table, Button, Tag, Space, Popconfirm, Tooltip, Spin, Card, Col, Row, Statistic } from 'antd';
import { Table, Button, Tag, Popconfirm, Tooltip, Spin, Card, Col, Row, Statistic } from 'antd';
import { InfoCircleOutlined, DeleteOutlined, BugOutlined, ClockCircleOutlined, CheckCircleOutlined, CloseCircleOutlined } from '@ant-design/icons';
import { Link, useNavigate } from 'react-router-dom';
import { useTickets, useDeleteTicket, useTicketStats } from '../../hooks/useTickets';
@@ -29,7 +29,7 @@ const TicketListPage: React.FC = () => {
const handleTableChange = (
pagination: any,
filters: any,
_filters: any,
sorter: SorterResult<Ticket> | SorterResult<Ticket>[]
) => {
const s = Array.isArray(sorter) ? sorter[0] : sorter;
+2 -2
View File
@@ -1,5 +1,5 @@
import React, { useState, useEffect, useRef } from 'react';
import { Table, Button, Tag, Space, Modal, Form, Select, message, Spin, Input, Tooltip } from 'antd';
import { Table, Button, Tag, Space, Modal, Form, Select, Spin, Input, Tooltip } from 'antd';
import { InfoCircleOutlined, EditOutlined, LockOutlined, UnlockOutlined, DeleteOutlined } from '@ant-design/icons';
import { useNavigate } from 'react-router-dom';
import { useUsers, useUpdateUser, useDeleteUser, useUser } from '../../hooks/useUsers';
@@ -150,7 +150,7 @@ const UserListPage: React.FC = () => {
// ==================== Таблица ====================
const handleTableChange = (
pagination: any,
filters: any,
_filters: any,
sorter: SorterResult<User> | SorterResult<User>[]
) => {
const s = Array.isArray(sorter) ? sorter[0] : sorter;