add deploy stage
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
#VITE_API_BASE_URL=https://admin-api.eventhub.local
|
||||
VITE_WS_URL=wss://admin-ws.eventhub.local
|
||||
VITE_APP_TITLE=EventHub Admin
|
||||
@@ -0,0 +1,3 @@
|
||||
FROM nginx:alpine
|
||||
COPY dist /usr/share/nginx/html
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
@@ -0,0 +1,38 @@
|
||||
.PHONY: install dev build docker-build docker-run docker-stop docker-restart clean
|
||||
|
||||
IMAGE_NAME = eventhub-admin-ui
|
||||
CONTAINER_NAME = admin-ui
|
||||
NETWORK_NAME = eventhub_network
|
||||
|
||||
# Установить зависимости
|
||||
install:
|
||||
npm install
|
||||
|
||||
# Запустить dev‑сервер
|
||||
dev:
|
||||
npm run dev
|
||||
|
||||
# Собрать production‑бандл
|
||||
build:
|
||||
npm run build
|
||||
|
||||
# Собрать Docker‑образ
|
||||
docker-build:
|
||||
docker build -t $(IMAGE_NAME) .
|
||||
|
||||
# Запустить контейнер (внутри сети eventhub_network)
|
||||
docker-run:
|
||||
docker run -d --name $(CONTAINER_NAME) --network $(NETWORK_NAME) --restart unless-stopped $(IMAGE_NAME)
|
||||
|
||||
# Остановить и удалить контейнер
|
||||
docker-stop:
|
||||
docker stop $(CONTAINER_NAME) || true
|
||||
docker rm $(CONTAINER_NAME) || true
|
||||
|
||||
# Перезапустить: стоп → билд → ран
|
||||
docker-restart: docker-stop docker-build docker-run
|
||||
|
||||
# Удалить сгенерированные файлы
|
||||
clean:
|
||||
rm -rf node_modules dist .vite
|
||||
npm cache clean --force
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
server {
|
||||
listen 80;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location /v1/ {
|
||||
proxy_pass http://eventhub:8445; # admin-api слушает HTTP на порту 8445
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location /admin/ws {
|
||||
proxy_pass http://eventhub:8446; # admin-ws слушает HTTP на порту 8446
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
@@ -20,8 +20,8 @@ export const useAdminWebSocket = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const accessToken = useAuthStore((s) => s.accessToken);
|
||||
const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
|
||||
const reconnectTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
|
||||
const pingIntervalRef = useRef<ReturnType<typeof setInterval>>();
|
||||
const reconnectTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const pingIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
||||
const lastMessageTimestamp = useRef<number>(0);
|
||||
const wsRef = useRef<WebSocket | null>(null);
|
||||
|
||||
@@ -32,8 +32,7 @@ export const useAdminWebSocket = () => {
|
||||
const connect = () => {
|
||||
const wsUrl = import.meta.env.DEV
|
||||
? `ws://localhost:5173/admin/ws?token=${accessToken}`
|
||||
: `${import.meta.env.VITE_WS_URL || 'wss://admin-ws.eventhub.local'}/admin/ws?token=${accessToken}`;
|
||||
|
||||
: `wss://${window.location.host}/admin/ws?token=${accessToken}`;
|
||||
const ws = new WebSocket(wsUrl);
|
||||
wsRef.current = ws;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { adminsApi } from '../api/adminsApi';
|
||||
import { AdminListParams } from '../types/api';
|
||||
import { AdminListParams, Admin } from '../types/api';
|
||||
import { message } from 'antd';
|
||||
import { normalizeData } from '../utils/normalize';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { eventsApi } from '../api/eventsApi';
|
||||
import { EventListParams } from '../types/api';
|
||||
import { Event, EventListParams } from '../types/api';
|
||||
import { message } from 'antd';
|
||||
import { normalizeData } from '../utils/normalize';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { reviewsApi } from '../api/reviewsApi';
|
||||
import { ReviewListParams } from '../types/api';
|
||||
import { ReviewListParams, Review } from '../types/api';
|
||||
import { message } from 'antd';
|
||||
import { normalizeData } from '../utils/normalize';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { subscriptionsApi } from '../api/subscriptionsApi';
|
||||
import { SubscriptionListParams } from '../types/api';
|
||||
import { SubscriptionListParams, Subscription as SubscriptionType } from '../types/api';
|
||||
import { message } from 'antd';
|
||||
import { normalizeData } from '../utils/normalize';
|
||||
|
||||
@@ -25,7 +25,7 @@ export const useSubscription = (id: string) => {
|
||||
export const useUpdateSubscription = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ id, data }: { id: string; data: Partial<Subscription> }) =>
|
||||
mutationFn: ({ id, data }: { id: string; data: Partial<SubscriptionType> }) =>
|
||||
subscriptionsApi.updateSubscription(id, data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['subscriptions'] });
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { ticketsApi } from '../api/ticketsApi';
|
||||
import { TicketListParams } from '../types/api';
|
||||
import { TicketListParams, Ticket } from '../types/api';
|
||||
import { message } from 'antd';
|
||||
import { normalizeData } from '../utils/normalize';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { usersApi } from '../api/usersApi';
|
||||
import { UserListParams } from '../types/api';
|
||||
import { UserListParams, User } from '../types/api';
|
||||
import { message } from 'antd';
|
||||
import { normalizeData } from '../utils/normalize';
|
||||
|
||||
|
||||
@@ -151,7 +151,7 @@ const AdminLayout: React.FC = () => {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100vh',
|
||||
position: 'sticky',
|
||||
// position: 'sticky',
|
||||
top: 0,
|
||||
left: 0,
|
||||
}}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,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';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>}
|
||||
|
||||
@@ -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,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';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user