update tanstak file managment table
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "no-copy-admin-panel-frontend",
|
"name": "no-copy-admin-panel-frontend",
|
||||||
"version": "0.12.0",
|
"version": "0.13.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev -p 2996",
|
"dev": "next dev -p 2996",
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import ContentModerationTable from '@/app/ui/content/content-moderation-table';
|
import ContentModerationTable from '@/app/ui/content/content-moderation-table';
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return (
|
return (
|
||||||
<div className="admin-content">
|
<div className="admin-content">
|
||||||
|
|||||||
@@ -3,23 +3,34 @@
|
|||||||
import { getSessionData } from '@/app/actions/session';
|
import { getSessionData } from '@/app/actions/session';
|
||||||
import { API_BASE_URL } from '@/app/actions/definitions';
|
import { API_BASE_URL } from '@/app/actions/definitions';
|
||||||
|
|
||||||
export async function fetchModerationContentList(page?: number, size?: number, sortBy?: string, sortDirection?: 'asc' | 'desc' | string, nameQuery?: string) {
|
export async function fetchModerationContentList(page?: number, size?: number, sortBy?: string, sortDirection?: 'asc' | 'desc' | string) {
|
||||||
const token = await getSessionData('token');
|
const token = await getSessionData('token');
|
||||||
console.log('fetchModerationContentList');
|
console.log('fetchModerationContentList');
|
||||||
|
/* console.log(
|
||||||
|
{
|
||||||
|
action: 'files_for_moderation',
|
||||||
|
page: page,
|
||||||
|
page_size: size,
|
||||||
|
sort_by: sortBy || '',
|
||||||
|
sort_direction: sortDirection || 'asc',
|
||||||
|
}
|
||||||
|
) */
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/api/admin`, {
|
const response = await fetch(`${API_BASE_URL}/api/admin/info`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
version: 1,
|
version: 1,
|
||||||
msg_id: 20005,
|
msg_id: 40001,
|
||||||
message_body: {
|
message_body: {
|
||||||
action: 'files_for_moderation',
|
action: 'files_for_moderation',
|
||||||
/* page: page,
|
page: page,
|
||||||
size: size,
|
page_size: size,
|
||||||
sort_by: sortBy || '',
|
/* sort_by: sortBy || '',
|
||||||
sort_direction: sortDirection || 'asc',
|
sort_order: sortDirection || 'asc', */
|
||||||
full_name: nameQuery */
|
/* full_name: nameQuery, */
|
||||||
|
/* fileName: '',
|
||||||
|
userId: '' */
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
@@ -34,7 +45,48 @@ export async function fetchModerationContentList(page?: number, size?: number, s
|
|||||||
console.log(parsed);
|
console.log(parsed);
|
||||||
|
|
||||||
if (parsed.message_code === 0) {
|
if (parsed.message_code === 0) {
|
||||||
return parsed.message_body;
|
return parsed.message_body.message_body;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
throw new Error(`${response.status}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function changeModerationContentStatus(fileId?: string, fileStatus?: string) {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
console.log(`changeModerationContentStatus - ${fileId} - ${fileStatus}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/admin/info`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 40002,
|
||||||
|
message_body: {
|
||||||
|
action: 'change_file_status',
|
||||||
|
file_id: fileId,
|
||||||
|
file_status: fileStatus
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const parsed = await response.json();
|
||||||
|
console.log(parsed);
|
||||||
|
|
||||||
|
if (parsed.message_code === 0) {
|
||||||
|
return parsed.message_body.message_body;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,43 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import {fetchModerationContentList} from '@/app/actions/contentActions';
|
import { fetchModerationContentList } from '@/app/actions/contentActions';
|
||||||
|
|
||||||
export interface ContentForModeration {
|
|
||||||
|
|
||||||
|
export interface ModerationFile {
|
||||||
|
fileName: string;
|
||||||
|
moderationInfo: {
|
||||||
|
appealInfo: null | any;
|
||||||
|
hasActiveAppeal: boolean;
|
||||||
|
};
|
||||||
|
userId: number;
|
||||||
|
fileId: string;
|
||||||
|
status: 'BLOCKED' | 'MODERATION' | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useContentForModeration = (page?: number, size?: number, sortBy?: string, sortDirection?: 'asc' | 'desc' | string, nameQuery?: string) => {
|
export interface ContentForModeration {
|
||||||
|
files: ModerationFile[];
|
||||||
|
totalPages: number;
|
||||||
|
currentPage: number;
|
||||||
|
pageSize: number;
|
||||||
|
totalCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useContentForModeration = (
|
||||||
|
page?: number,
|
||||||
|
size?: number,
|
||||||
|
sortBy?: string,
|
||||||
|
sortDirection?: 'asc' | 'desc' | string
|
||||||
|
) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ['contentForModeration'],
|
queryKey: ['contentForModeration', page, size, sortBy, sortDirection],
|
||||||
queryFn: () => {
|
queryFn: () => {
|
||||||
return fetchModerationContentList(page, size, sortBy, sortDirection, nameQuery)
|
return fetchModerationContentList(page, size, sortBy, sortDirection);
|
||||||
},
|
},
|
||||||
select: (data: any[] | null) => {
|
select: (data: ContentForModeration | null) => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
return data;
|
return data; // теперь возвращаем весь объект с пагинацией
|
||||||
},
|
},
|
||||||
retry: false
|
refetchInterval: 30000,
|
||||||
|
placeholderData: (previousData) => previousData // для оптимистичных обновлений
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
@use './variable.scss' as v;
|
||||||
|
|
||||||
|
.file-moderation-modal {
|
||||||
|
padding: 1.5rem;
|
||||||
|
min-width: 400px;
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__info-section,
|
||||||
|
&__status-section,
|
||||||
|
&__status-change-section {
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
padding-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__label,
|
||||||
|
&__status-label,
|
||||||
|
&__status-change-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__status-label {
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__status-change-label {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__value {
|
||||||
|
color: #111827;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 0.75rem;
|
||||||
|
padding-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__button {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
transition: all 0.2s;
|
||||||
|
cursor: pointer;
|
||||||
|
border: none;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__button-cancel {
|
||||||
|
@extend .file-moderation-modal__button;
|
||||||
|
color: #374151;
|
||||||
|
background-color: #f3f4f6;
|
||||||
|
|
||||||
|
&:hover:not(:disabled) {
|
||||||
|
background-color: #e5e7eb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__button-save {
|
||||||
|
@extend .file-moderation-modal__button;
|
||||||
|
color: white;
|
||||||
|
background-color: #3b82f6;
|
||||||
|
|
||||||
|
&:hover:not(:disabled) {
|
||||||
|
background-color: #2563eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
background-color: #93c5fd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge {
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
border-radius: 9999px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
&--blocked {
|
||||||
|
@extend .status-badge;
|
||||||
|
background-color: #fee2e2;
|
||||||
|
color: #991b1b;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--moderation {
|
||||||
|
@extend .status-badge;
|
||||||
|
background-color: #fef3c7;
|
||||||
|
color: #92400e;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--approved {
|
||||||
|
@extend .status-badge;
|
||||||
|
background-color: #dcfce7;
|
||||||
|
color: #166534;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--default {
|
||||||
|
@extend .status-badge;
|
||||||
|
background-color: #f3f4f6;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
@use './variable.scss' as v;
|
@use './variable.scss' as v;
|
||||||
@use './edit-permissions-modal.scss';
|
@use './edit-permissions-modal.scss';
|
||||||
@use './animation.scss';
|
@use './animation.scss';
|
||||||
|
@use './file-moderation-modal.scss';
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--primary-color: #2563eb;
|
--primary-color: #2563eb;
|
||||||
|
|||||||
@@ -0,0 +1,138 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
import DropDownList from '@/app/components/dropDownList';
|
||||||
|
import { changeModerationContentStatus } from '@/app/actions/contentActions';
|
||||||
|
import { ModerationFile } from '@/app/hooks/react-query/useContentForModeration';
|
||||||
|
|
||||||
|
interface FileModerationModalProps {
|
||||||
|
file: ModerationFile | null;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StatusBadge = ({ status }: { status: string }) => {
|
||||||
|
const getBadgeClass = () => {
|
||||||
|
switch (status) {
|
||||||
|
case 'BLOCKED':
|
||||||
|
return 'status-badge--blocked';
|
||||||
|
case 'MODERATION':
|
||||||
|
return 'status-badge--moderation';
|
||||||
|
case 'APPROVED':
|
||||||
|
return 'status-badge--approved';
|
||||||
|
default:
|
||||||
|
return 'status-badge--default';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getLabel = () => {
|
||||||
|
switch (status) {
|
||||||
|
case 'BLOCKED':
|
||||||
|
return 'Заблокировано';
|
||||||
|
case 'MODERATION':
|
||||||
|
return 'На модерации';
|
||||||
|
case 'APPROVED':
|
||||||
|
return 'Одобрено';
|
||||||
|
default:
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className={getBadgeClass()}>
|
||||||
|
{getLabel()}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function FileModerationModal({ file, onClose }: FileModerationModalProps) {
|
||||||
|
const [selectedStatus, setSelectedStatus] = useState<string>(file?.status || 'MODERATION');
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
const changeStatusMutation = useMutation({
|
||||||
|
mutationFn: async ({ fileId, fileStatus }: { fileId: string; fileStatus: string }) => {
|
||||||
|
return await changeModerationContentStatus(fileId, fileStatus);
|
||||||
|
},
|
||||||
|
onSuccess: (data) => {
|
||||||
|
if (data) {
|
||||||
|
toast.success('Статус файла успешно изменен');
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['contentForModeration'] });
|
||||||
|
onClose();
|
||||||
|
} else {
|
||||||
|
toast.error('Не удалось изменить статус файла');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
toast.error('Произошла ошибка при изменении статуса');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleStatusChange = () => {
|
||||||
|
if (!file?.fileId) {
|
||||||
|
toast.error('ID файла не найден');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
changeStatusMutation.mutate({
|
||||||
|
fileId: file.fileId,
|
||||||
|
fileStatus: selectedStatus,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!file) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="file-moderation-modal">
|
||||||
|
<h2 className="file-moderation-modal__title">Управление файлом</h2>
|
||||||
|
|
||||||
|
<div className="file-moderation-modal__content">
|
||||||
|
{/* Информация о файле */}
|
||||||
|
<div className="file-moderation-modal__info-section">
|
||||||
|
<label className="file-moderation-modal__label">Название файла</label>
|
||||||
|
<p className="file-moderation-modal__value">{file.fileName}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="file-moderation-modal__info-section">
|
||||||
|
<label className="file-moderation-modal__label">User ID</label>
|
||||||
|
<p className="file-moderation-modal__value">{file.userId}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="file-moderation-modal__status-section">
|
||||||
|
<label className="file-moderation-modal__status-label">Текущий статус</label>
|
||||||
|
<StatusBadge status={file.status} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Смена статуса */}
|
||||||
|
<div className="file-moderation-modal__status-change-section">
|
||||||
|
<label className="file-moderation-modal__status-change-label">Изменить статус</label>
|
||||||
|
<DropDownList
|
||||||
|
value={selectedStatus}
|
||||||
|
callBack={(value: string) => setSelectedStatus(value)}
|
||||||
|
>
|
||||||
|
<li value="MODERATION">На модерации</li>
|
||||||
|
<li value="BLOCKED">Заблокировано</li>
|
||||||
|
</DropDownList>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Кнопки действий */}
|
||||||
|
<div className="file-moderation-modal__actions">
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
className="btn btn-secondary"
|
||||||
|
disabled={changeStatusMutation.isPending}
|
||||||
|
>
|
||||||
|
Отмена
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handleStatusChange}
|
||||||
|
className="btn btn-primary"
|
||||||
|
disabled={changeStatusMutation.isPending || selectedStatus === file.status}
|
||||||
|
>
|
||||||
|
{changeStatusMutation.isPending ? 'Сохранение...' : 'Сохранить'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -12,47 +12,27 @@ import {
|
|||||||
ColumnFiltersState,
|
ColumnFiltersState,
|
||||||
} from '@tanstack/react-table';
|
} from '@tanstack/react-table';
|
||||||
import { IconEye, IconDoubleArrowRight, IconArrowRight, IconDoubleArrowLeft, IconArrowLeft, IconArrowUp, IconArrowDown, IconFilter } from '@/app/ui/icons/icons';
|
import { IconEye, IconDoubleArrowRight, IconArrowRight, IconDoubleArrowLeft, IconArrowLeft, IconArrowUp, IconArrowDown, IconFilter } from '@/app/ui/icons/icons';
|
||||||
import { useTranslations, useLocale } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
import DropDownList from '@/app/components/dropDownList';
|
import DropDownList from '@/app/components/dropDownList';
|
||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
import ModalWindow from '@/app/components/modalWindow';
|
import ModalWindow from '@/app/components/modalWindow';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { useDebouncedCallback } from 'use-debounce';
|
import { useContentForModeration, ModerationFile, ContentForModeration } from '@/app/hooks/react-query/useContentForModeration';
|
||||||
import { fetchUsesInfo } from '@/app/actions/usersActions';
|
import FileModerationModal from './FileModerationModal';
|
||||||
import { UserInfoModalWindow } from '@/app/ui/users/user-info-modal-window';
|
|
||||||
import { useContentForModeration, ContentForModeration } from '@/app/hooks/react-query/useContentForModeration';
|
|
||||||
|
|
||||||
// Форматирование даты из timestamp
|
const cutFileName = (fileName: string) => {
|
||||||
const formatDate = (timestamp: number) => {
|
const MAX_FILE_NAME_LENGTH = 30;
|
||||||
const date = new Date(timestamp);
|
const lastDotIndex = fileName.lastIndexOf('.');
|
||||||
const day = date.getDate().toString().padStart(2, '0');
|
|
||||||
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
|
||||||
const year = date.getFullYear();
|
|
||||||
|
|
||||||
return `${day}.${month}.${year}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatDateTime = (timestamp: number) => {
|
|
||||||
const date = new Date(timestamp);
|
|
||||||
const hours = date.getHours().toString().padStart(2, '0');
|
|
||||||
const minutes = date.getMinutes().toString().padStart(2, '0');
|
|
||||||
|
|
||||||
return `${hours}:${minutes}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const cutFileName = (userId: string) => {
|
|
||||||
const MAX_FILE_NAME_LENGTH = 26;
|
|
||||||
const lastDotIndex = userId.lastIndexOf('.');
|
|
||||||
|
|
||||||
if (lastDotIndex <= 0) {
|
if (lastDotIndex <= 0) {
|
||||||
return userId.length > MAX_FILE_NAME_LENGTH ? userId.substring(0, MAX_FILE_NAME_LENGTH - 3) + '...' : userId;
|
return fileName.length > MAX_FILE_NAME_LENGTH ? fileName.substring(0, MAX_FILE_NAME_LENGTH - 3) + '...' : fileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nameWithoutExtension = userId.substring(0, lastDotIndex);
|
const nameWithoutExtension = fileName.substring(0, lastDotIndex);
|
||||||
const extension = userId.substring(lastDotIndex);
|
const extension = fileName.substring(lastDotIndex);
|
||||||
|
|
||||||
if (userId.length <= MAX_FILE_NAME_LENGTH) {
|
if (fileName.length <= MAX_FILE_NAME_LENGTH) {
|
||||||
return userId;
|
return fileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxNameLength = MAX_FILE_NAME_LENGTH - extension.length - 3;
|
const maxNameLength = MAX_FILE_NAME_LENGTH - extension.length - 3;
|
||||||
@@ -64,52 +44,49 @@ const cutFileName = (userId: string) => {
|
|||||||
return nameWithoutExtension.substring(0, maxNameLength) + '...' + extension;
|
return nameWithoutExtension.substring(0, maxNameLength) + '...' + extension;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const StatusBadge = ({ status }: { status: string }) => {
|
||||||
|
const statusConfig: Record<string, { className: string; label: string }> = {
|
||||||
|
'BLOCKED': { className: 'bg-red-100 text-red-800', label: 'Заблокировано' },
|
||||||
|
'MODERATION': { className: 'bg-yellow-100 text-yellow-800', label: 'На модерации' },
|
||||||
|
'APPROVED': { className: 'bg-green-100 text-green-800', label: 'Одобрено' },
|
||||||
|
};
|
||||||
|
|
||||||
|
const config = statusConfig[status] || { className: 'bg-gray-100 text-gray-800', label: status };
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className={`px-2 py-1 rounded-full text-xs font-medium ${config.className}`}>
|
||||||
|
{config.label}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default function ContentModerationTable({ permission }: { permission: number }) {
|
export default function ContentModerationTable({ permission }: { permission: number }) {
|
||||||
// Состояния
|
// Состояния
|
||||||
const [sorting, setSorting] = useState<SortingState>([]);
|
const [sorting, setSorting] = useState<SortingState>([]);
|
||||||
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
|
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
|
||||||
const [pagination, setPagination] = useState({
|
const [pagination, setPagination] = useState({
|
||||||
pageIndex: 0,
|
pageIndex: 0,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
});
|
});
|
||||||
const [searchInputValue, setSearchInputValue] = useState<string>('');
|
|
||||||
const [openWindow, setOpenWindow] = useState<boolean>(false);
|
const [openWindow, setOpenWindow] = useState<boolean>(false);
|
||||||
const [openWindowChildren, setOpenWindowChildren] = useState<ReactNode>(null);
|
const [openWindowChildren, setOpenWindowChildren] = useState<ReactNode>(null);
|
||||||
const debouncedSetSearchQuery = useDebouncedCallback(
|
const [selectedFile, setSelectedFile] = useState<ModerationFile | null>(null);
|
||||||
(value: string) => {
|
|
||||||
setSearchQuery(value);
|
|
||||||
setPagination(prev => ({ ...prev, pageIndex: 0 }));
|
|
||||||
},
|
|
||||||
500
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleSearchInputChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const value = e.target.value;
|
|
||||||
setSearchInputValue(value);
|
|
||||||
debouncedSetSearchQuery(value);
|
|
||||||
}, [debouncedSetSearchQuery]);
|
|
||||||
|
|
||||||
const t = useTranslations("Global");
|
const t = useTranslations("Global");
|
||||||
const locale = useLocale();
|
|
||||||
|
|
||||||
|
// Получаем параметры сортировки для API
|
||||||
const getSortParams = useMemo(() => {
|
const getSortParams = useMemo(() => {
|
||||||
if (sorting.length === 0) {
|
if (sorting.length === 0) {
|
||||||
return { sortBy: '', sortOrder: undefined };
|
return { sortBy: '', sortDirection: undefined };
|
||||||
}
|
}
|
||||||
|
|
||||||
const sort = sorting[0];
|
const sort = sorting[0];
|
||||||
const sortByMap: Record<string, string> = {
|
const sortByMap: Record<string, string> = {
|
||||||
'id': 'supportId',
|
'fileName': 'fileName',
|
||||||
'userName': 'fullName',
|
'userId': 'userId',
|
||||||
'email': 'email',
|
'status': 'status',
|
||||||
'verificationStatus': 'verificationStatus',
|
|
||||||
'createdAt': 'createdAt',
|
'createdAt': 'createdAt',
|
||||||
'tariff': 'tariff',
|
|
||||||
'tokens': 'tokens',
|
|
||||||
'userCompany': 'userCompany',
|
|
||||||
};
|
};
|
||||||
console.log(sortByMap[sort.id]);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sortBy: sortByMap[sort.id] || sort.id,
|
sortBy: sortByMap[sort.id] || sort.id,
|
||||||
@@ -117,273 +94,150 @@ export default function ContentModerationTable({ permission }: { permission: num
|
|||||||
};
|
};
|
||||||
}, [sorting]);
|
}, [sorting]);
|
||||||
|
|
||||||
const { data: tableData,
|
const {
|
||||||
|
data: moderationData,
|
||||||
isLoading,
|
isLoading,
|
||||||
isError,
|
isError,
|
||||||
isFetching,
|
isFetching,
|
||||||
error
|
error
|
||||||
} = useContentForModeration(pagination.pageIndex, pagination.pageSize, getSortParams?.sortBy, getSortParams?.sortDirection, searchQuery);
|
} = useContentForModeration(
|
||||||
|
pagination.pageIndex,
|
||||||
|
pagination.pageSize,
|
||||||
|
getSortParams.sortBy,
|
||||||
|
getSortParams.sortDirection
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log(tableData);
|
console.log('Moderation data:', moderationData);
|
||||||
}, [tableData])
|
}, [moderationData]);
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
// Определение колонок
|
const handleViewFile = useCallback((file: ModerationFile) => {
|
||||||
const columns = useMemo<ColumnDef<ContentForModeration>[]>(() => {
|
console.log('View file:', file);
|
||||||
const allColumns: ColumnDef<ContentForModeration>[] = [
|
setSelectedFile(file);
|
||||||
|
setOpenWindow(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleCloseModal = useCallback(() => {
|
||||||
|
setOpenWindow(false);
|
||||||
|
setSelectedFile(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const columns = useMemo<ColumnDef<ModerationFile>[]>(() => {
|
||||||
|
const allColumns: ColumnDef<ModerationFile>[] = [
|
||||||
{
|
{
|
||||||
accessorKey: 'id',
|
accessorKey: 'fileName',
|
||||||
header: ({ column }) => (
|
header: ({ column }) => (
|
||||||
<div className="column start">
|
<div className="column start">
|
||||||
<span>
|
<span>
|
||||||
ID
|
Название файла
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
||||||
className="sort-button"
|
className="sort-button"
|
||||||
>
|
>
|
||||||
{
|
{column.getIsSorted() === 'asc' ?
|
||||||
column.getIsSorted() === 'asc' ?
|
<span><IconArrowUp /></span>
|
||||||
<span>
|
|
||||||
<IconArrowUp />
|
|
||||||
</span>
|
|
||||||
: column.getIsSorted() === 'desc' ?
|
: column.getIsSorted() === 'desc' ?
|
||||||
<span>
|
<span><IconArrowDown /></span>
|
||||||
<IconArrowDown />
|
: <span><IconFilter /></span>
|
||||||
</span>
|
|
||||||
: <span>
|
|
||||||
<IconFilter />
|
|
||||||
</span>
|
|
||||||
}
|
}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<div className="flex items-center table-item table-item-id">
|
<div className="flex items-center table-item">
|
||||||
<div>
|
<div className="font-medium w-full truncate" title={row.original.fileName}>
|
||||||
<div className="font-medium w-full truncate" title={'row.original.id.toString()'}>
|
{cutFileName(row.original.fileName)}
|
||||||
{'row.original.id'}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
enableColumnFilter: false,
|
enableColumnFilter: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'userName',
|
accessorKey: 'userId',
|
||||||
header: ({ column }) => (
|
header: ({ column }) => (
|
||||||
<div className="column">
|
<div className="column">
|
||||||
<span>
|
<span>
|
||||||
Пользователь
|
User ID
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
||||||
className="sort-button"
|
className="sort-button"
|
||||||
>
|
>
|
||||||
{
|
{column.getIsSorted() === 'asc' ?
|
||||||
column.getIsSorted() === 'asc' ?
|
<span><IconArrowUp /></span>
|
||||||
<span>
|
|
||||||
<IconArrowUp />
|
|
||||||
</span>
|
|
||||||
: column.getIsSorted() === 'desc' ?
|
: column.getIsSorted() === 'desc' ?
|
||||||
<span>
|
<span><IconArrowDown /></span>
|
||||||
<IconArrowDown />
|
: <span><IconFilter /></span>
|
||||||
</span>
|
|
||||||
: <span>
|
|
||||||
<IconFilter />
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
cell: ({ row }) => {
|
|
||||||
return (
|
|
||||||
<div className={`text-center font-semibold`}>
|
|
||||||
{'row.original.userName'}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: 'userEmail',
|
|
||||||
header: ({ column }) => (
|
|
||||||
<div className="column">
|
|
||||||
<span>
|
|
||||||
email
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
|
||||||
className="sort-button"
|
|
||||||
>
|
|
||||||
{
|
|
||||||
column.getIsSorted() === 'asc' ?
|
|
||||||
<span>
|
|
||||||
<IconArrowUp />
|
|
||||||
</span>
|
|
||||||
: column.getIsSorted() === 'desc' ?
|
|
||||||
<span>
|
|
||||||
<IconArrowDown />
|
|
||||||
</span>
|
|
||||||
: <span>
|
|
||||||
<IconFilter />
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
cell: ({ row }) => {
|
|
||||||
return (
|
|
||||||
<div className={`text-center font-semibold text-green-600`}>
|
|
||||||
{'row.original.userEmail'}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: 'verificationStatus',
|
|
||||||
header: ({ column }) => (
|
|
||||||
<div className="column">
|
|
||||||
<span>
|
|
||||||
статус KYC
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
|
||||||
className="sort-button"
|
|
||||||
>
|
|
||||||
{
|
|
||||||
column.getIsSorted() === 'asc' ?
|
|
||||||
<span>
|
|
||||||
<IconArrowUp />
|
|
||||||
</span>
|
|
||||||
: column.getIsSorted() === 'desc' ?
|
|
||||||
<span>
|
|
||||||
<IconArrowDown />
|
|
||||||
</span>
|
|
||||||
: <span>
|
|
||||||
<IconFilter />
|
|
||||||
</span>
|
|
||||||
}
|
}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
{'row.original.verificationStatus'}
|
{row.original.userId}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'tariff',
|
accessorKey: 'status',
|
||||||
header: ({ column }) => (
|
header: ({ column }) => (
|
||||||
<div className="column">
|
<div className="column">
|
||||||
<span>
|
<span>
|
||||||
Подписка
|
Статус
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
||||||
className="sort-button"
|
className="sort-button"
|
||||||
>
|
>
|
||||||
{
|
{column.getIsSorted() === 'asc' ?
|
||||||
column.getIsSorted() === 'asc' ?
|
<span><IconArrowUp /></span>
|
||||||
<span>
|
|
||||||
<IconArrowUp />
|
|
||||||
</span>
|
|
||||||
: column.getIsSorted() === 'desc' ?
|
: column.getIsSorted() === 'desc' ?
|
||||||
<span>
|
<span><IconArrowDown /></span>
|
||||||
<IconArrowDown />
|
: <span><IconFilter /></span>
|
||||||
</span>
|
|
||||||
: <span>
|
|
||||||
<IconFilter />
|
|
||||||
</span>
|
|
||||||
}
|
}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => (
|
||||||
return (
|
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
{'row.original.tariff'}
|
<StatusBadge status={row.original.status} />
|
||||||
</div>
|
</div>
|
||||||
)
|
),
|
||||||
},
|
|
||||||
enableColumnFilter: false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'tokens',
|
accessorKey: 'moderationInfo.hasActiveAppeal',
|
||||||
header: ({ column }) => (
|
header: ({ column }) => (
|
||||||
<div className="column">
|
<div className="column">
|
||||||
<span>
|
<span>
|
||||||
Токены
|
Апелляция
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
||||||
className="sort-button"
|
className="sort-button"
|
||||||
>
|
>
|
||||||
{
|
{column.getIsSorted() === 'asc' ?
|
||||||
column.getIsSorted() === 'asc' ?
|
<span><IconArrowUp /></span>
|
||||||
<span>
|
|
||||||
<IconArrowUp />
|
|
||||||
</span>
|
|
||||||
: column.getIsSorted() === 'desc' ?
|
: column.getIsSorted() === 'desc' ?
|
||||||
<span>
|
<span><IconArrowDown /></span>
|
||||||
<IconArrowDown />
|
: <span><IconFilter /></span>
|
||||||
</span>
|
|
||||||
: <span>
|
|
||||||
<IconFilter />
|
|
||||||
</span>
|
|
||||||
}
|
}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => (
|
||||||
return (
|
<div className="text-center">
|
||||||
<div className={`text-center font-semibold`}>
|
{row.original.moderationInfo.hasActiveAppeal ? (
|
||||||
{'row.original.tokens'}
|
<span className="text-blue-600">Активна</span>
|
||||||
</div>
|
) : (
|
||||||
)
|
<span className="text-gray-400">Нет</span>
|
||||||
},
|
)}
|
||||||
}, {
|
|
||||||
accessorKey: 'userSubscription',
|
|
||||||
header: ({ column }) => (
|
|
||||||
<div className="column">
|
|
||||||
<span>
|
|
||||||
Дата регистрации
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
|
||||||
className="sort-button"
|
|
||||||
>
|
|
||||||
{
|
|
||||||
column.getIsSorted() === 'asc' ?
|
|
||||||
<span>
|
|
||||||
<IconArrowUp />
|
|
||||||
</span>
|
|
||||||
: column.getIsSorted() === 'desc' ?
|
|
||||||
<span>
|
|
||||||
<IconArrowDown />
|
|
||||||
</span>
|
|
||||||
: <span>
|
|
||||||
<IconFilter />
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
cell: ({ row }) => {
|
|
||||||
return (
|
|
||||||
<div className={`text-center font-semibold`}>
|
|
||||||
<>
|
|
||||||
{formatDate(0)}
|
|
||||||
<br />
|
|
||||||
{formatDateTime(0)}
|
|
||||||
</>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
]
|
];
|
||||||
|
|
||||||
if (permission === 3) {
|
if (permission === 3) {
|
||||||
allColumns.push({
|
allColumns.push({
|
||||||
@@ -393,8 +247,9 @@ export default function ContentModerationTable({ permission }: { permission: num
|
|||||||
<div className="actions">
|
<div className="actions">
|
||||||
<div className="actions-group">
|
<div className="actions-group">
|
||||||
<button
|
<button
|
||||||
onClick={() => {}}
|
onClick={() => handleViewFile(row.original)}
|
||||||
className="bg-blue-500 hover:bg-blue-600"
|
className="bg-blue-500 hover:bg-blue-600 text-white p-2 rounded"
|
||||||
|
title="Просмотреть файл"
|
||||||
>
|
>
|
||||||
<IconEye />
|
<IconEye />
|
||||||
</button>
|
</button>
|
||||||
@@ -407,37 +262,18 @@ export default function ContentModerationTable({ permission }: { permission: num
|
|||||||
}
|
}
|
||||||
|
|
||||||
return allColumns;
|
return allColumns;
|
||||||
}, [permission]);
|
}, [permission, handleViewFile, t]);
|
||||||
|
|
||||||
// Фильтрация по типу файла и дате
|
// Фильтрация данных (клиентская фильтрация, если нужно)
|
||||||
const filteredData = useMemo(() => {
|
const filteredData = useMemo(() => {
|
||||||
/* let result = tableData?.users; */
|
if (!moderationData?.files) {
|
||||||
let result = [];
|
|
||||||
return []
|
|
||||||
if (!result) {
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (searchQuery.trim()) {
|
let result = [...moderationData.files];
|
||||||
const query = searchQuery.toLowerCase().trim();
|
|
||||||
result = result.filter((item: any) => {
|
|
||||||
// Ищем по всем строковым полям
|
|
||||||
return Object.keys(item).some(key => {
|
|
||||||
const value = item[key];
|
|
||||||
if (typeof value === 'string') {
|
|
||||||
return value.toLowerCase().includes(query);
|
|
||||||
}
|
|
||||||
// Если нужно искать по числам или другим типам
|
|
||||||
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
||||||
return value.toString().toLowerCase().includes(query);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}, [tableData, searchQuery]);
|
}, [moderationData]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const currentPageRows = table.getRowModel().rows;
|
const currentPageRows = table.getRowModel().rows;
|
||||||
@@ -460,8 +296,7 @@ export default function ContentModerationTable({ permission }: { permission: num
|
|||||||
manualPagination: true,
|
manualPagination: true,
|
||||||
manualSorting: true,
|
manualSorting: true,
|
||||||
manualFiltering: true,
|
manualFiltering: true,
|
||||||
/* pageCount: tableData?.pagination?.totalPages, */
|
pageCount: moderationData?.totalPages || 0,
|
||||||
pageCount: 0,
|
|
||||||
onPaginationChange: setPagination,
|
onPaginationChange: setPagination,
|
||||||
onSortingChange: (updater) => {
|
onSortingChange: (updater) => {
|
||||||
const newSorting = typeof updater === 'function' ? updater(sorting) : updater;
|
const newSorting = typeof updater === 'function' ? updater(sorting) : updater;
|
||||||
@@ -473,31 +308,28 @@ export default function ContentModerationTable({ permission }: { permission: num
|
|||||||
getPaginationRowModel: getPaginationRowModel()
|
getPaginationRowModel: getPaginationRowModel()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (isError) {
|
||||||
|
return (
|
||||||
|
<div className="text-center text-red-600 p-4">
|
||||||
|
Ошибка загрузки данных: {error?.message || 'Неизвестная ошибка'}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="tanstak-table-wrapper">
|
<div className="tanstak-table-wrapper">
|
||||||
<ModalWindow state={openWindow} callBack={setOpenWindow}>
|
<ModalWindow state={openWindow} callBack={handleCloseModal}>
|
||||||
{openWindowChildren}
|
<FileModerationModal file={selectedFile} onClose={handleCloseModal} />
|
||||||
</ModalWindow>
|
</ModalWindow>
|
||||||
|
|
||||||
{/* Фильтры */}
|
{/* Фильтры */}
|
||||||
<div className="tanstak-table-filtres">
|
<div className="tanstak-table-filtres">
|
||||||
<div className="table-filtres-wrapper">
|
|
||||||
<div className="table-filtres-item">
|
|
||||||
<div className="table-filtres-label">{t('search')}:</div>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={searchInputValue}
|
|
||||||
onChange={handleSearchInputChange}
|
|
||||||
placeholder="Поиск пользователей"
|
|
||||||
className="table-filtres-input"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="table-filtres-item">
|
<div className="table-filtres-item">
|
||||||
<div className="table-filtres-label">{t('items-per-page')}:</div>
|
<div className="table-filtres-label">{t('items-per-page')}:</div>
|
||||||
<DropDownList
|
<DropDownList
|
||||||
value={table.getState().pagination.pageSize}
|
value={table.getState().pagination.pageSize}
|
||||||
//@ts-ignore сделал так потому что для table.setPageSize нужна цифра
|
//@ts-ignore
|
||||||
callBack={table.setPageSize}
|
callBack={table.setPageSize}
|
||||||
>
|
>
|
||||||
{[5, 10, 20, 50, 100].map(pageSize => (
|
{[5, 10, 20, 50, 100].map(pageSize => (
|
||||||
@@ -516,9 +348,7 @@ export default function ContentModerationTable({ permission }: { permission: num
|
|||||||
{table.getHeaderGroups().map(headerGroup => (
|
{table.getHeaderGroups().map(headerGroup => (
|
||||||
<tr key={headerGroup.id}>
|
<tr key={headerGroup.id}>
|
||||||
{headerGroup.headers.map(header => (
|
{headerGroup.headers.map(header => (
|
||||||
<th
|
<th key={header.id}>
|
||||||
key={header.id}
|
|
||||||
>
|
|
||||||
{header.isPlaceholder
|
{header.isPlaceholder
|
||||||
? null
|
? null
|
||||||
: typeof header.column.columnDef.header === 'function'
|
: typeof header.column.columnDef.header === 'function'
|
||||||
@@ -530,9 +360,15 @@ export default function ContentModerationTable({ permission }: { permission: num
|
|||||||
))}
|
))}
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className={`tanstak-table-body ${isFetching ? 'loading' : ''}`}>
|
<tbody className={`tanstak-table-body ${isFetching ? 'loading' : ''}`}>
|
||||||
{table.getRowModel().rows.length > 0 ? (
|
{isLoading ? (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={columns.length} className="text-center py-8">
|
||||||
|
Загрузка...
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
) : table.getRowModel().rows.length > 0 ? (
|
||||||
table.getRowModel().rows.map(row => (
|
table.getRowModel().rows.map(row => (
|
||||||
<tr key={row.id}>
|
<tr key={row.original.fileId}>
|
||||||
{row.getVisibleCells().map(cell => (
|
{row.getVisibleCells().map(cell => (
|
||||||
<td key={cell.id}>
|
<td key={cell.id}>
|
||||||
{typeof cell.column.columnDef.cell === 'function'
|
{typeof cell.column.columnDef.cell === 'function'
|
||||||
@@ -544,7 +380,7 @@ export default function ContentModerationTable({ permission }: { permission: num
|
|||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={columns.length} className="text-center">
|
<td colSpan={columns.length} className="text-center py-8">
|
||||||
{t('no-data-for-selected-filters')}
|
{t('no-data-for-selected-filters')}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -554,16 +390,16 @@ export default function ContentModerationTable({ permission }: { permission: num
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Пагинация */}
|
{/* Пагинация */}
|
||||||
{/* <div className="tanstak-table-pagination">
|
<div className="tanstak-table-pagination">
|
||||||
<div className="pagination-info">
|
<div className="pagination-info">
|
||||||
<span className="pagination-info-pages">
|
<span className="pagination-info-pages">
|
||||||
{t('page')}{' '}
|
{t('page')}{' '}
|
||||||
<strong>
|
<strong>
|
||||||
{(tableData?.pagination?.currentPage ?? 0) + 1} {t('out-of')} {tableData?.pagination?.totalPages ?? 1}
|
{(moderationData?.currentPage ?? 0) + 1} {t('out-of')} {moderationData?.totalPages ?? 1}
|
||||||
</strong>
|
</strong>
|
||||||
</span>
|
</span>
|
||||||
<span className="pagination-info-files">
|
<span className="pagination-info-files">
|
||||||
| {t('shown')} {table.getRowModel().rows.length} {t('out-of')} {tableData?.pagination?.totalElements ?? 0}
|
| {t('shown')} {table.getRowModel().rows.length} {t('out-of')} {moderationData?.totalCount ?? 0}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -585,7 +421,7 @@ export default function ContentModerationTable({ permission }: { permission: num
|
|||||||
|
|
||||||
<div className="pagination-controls-pages">
|
<div className="pagination-controls-pages">
|
||||||
{(() => {
|
{(() => {
|
||||||
const totalPages = tableData?.pagination?.totalPages;
|
const totalPages = moderationData?.totalPages;
|
||||||
if (!totalPages || totalPages === 0) return null;
|
if (!totalPages || totalPages === 0) return null;
|
||||||
|
|
||||||
const currentPageIndex = table.getState().pagination.pageIndex;
|
const currentPageIndex = table.getState().pagination.pageIndex;
|
||||||
@@ -626,7 +462,7 @@ export default function ContentModerationTable({ permission }: { permission: num
|
|||||||
<IconDoubleArrowRight />
|
<IconDoubleArrowRight />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div> */}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user