start work on content moderation table
This commit is contained in:
@@ -1,28 +1,12 @@
|
|||||||
|
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">
|
||||||
<div className="content-header">
|
<div className="content-header">
|
||||||
<h3>Управление контентом</h3>
|
<h3>Управление контентом</h3>
|
||||||
<div className="content-header-actions">
|
|
||||||
<div className="search-box">
|
|
||||||
<span>🔍</span>
|
|
||||||
<input type="text" id="content-search" placeholder="Поиск контента..." />
|
|
||||||
</div>
|
|
||||||
<select id="content-type-filter">
|
|
||||||
<option value="all">Все типы</option>
|
|
||||||
<option value="image">Изображения</option>
|
|
||||||
<option value="video">Видео</option>
|
|
||||||
<option value="audio">Аудио</option>
|
|
||||||
<option value="document">Документы</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="content-body" id="content-container">
|
|
||||||
<div className="empty-state">
|
|
||||||
<div className="empty-state-icon">🗂️</div>
|
|
||||||
<h4>Контент не найден</h4>
|
|
||||||
<p>Попробуйте изменить параметры поиска</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div className="content-body">
|
||||||
|
<ContentModerationTable permission={3} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
'use server'
|
||||||
|
|
||||||
|
import { getSessionData } from '@/app/actions/session';
|
||||||
|
import { API_BASE_URL } from '@/app/actions/definitions';
|
||||||
|
|
||||||
|
export async function fetchModerationContentList(page?: number, size?: number, sortBy?: string, sortDirection?: 'asc' | 'desc' | string, nameQuery?: string) {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
console.log('fetchModerationContentList');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/admin`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 20005,
|
||||||
|
message_body: {
|
||||||
|
action: 'files_for_moderation',
|
||||||
|
/* page: page,
|
||||||
|
size: size,
|
||||||
|
sort_by: sortBy || '',
|
||||||
|
sort_direction: sortDirection || 'asc',
|
||||||
|
full_name: nameQuery */
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
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;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
throw new Error(`${response.status}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import {fetchModerationContentList} from '@/app/actions/contentActions';
|
||||||
|
|
||||||
|
export interface ContentForModeration {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useContentForModeration = (page?: number, size?: number, sortBy?: string, sortDirection?: 'asc' | 'desc' | string, nameQuery?: string) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['contentForModeration'],
|
||||||
|
queryFn: () => {
|
||||||
|
return fetchModerationContentList(page, size, sortBy, sortDirection, nameQuery)
|
||||||
|
},
|
||||||
|
select: (data: any[] | null) => {
|
||||||
|
if (!data) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
retry: false
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -355,12 +355,12 @@
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
|
||||||
&-actions {
|
/* &-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
||||||
}
|
} */
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
|
|||||||
@@ -0,0 +1,632 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState, useMemo, useEffect, ReactNode, useCallback } from 'react';
|
||||||
|
import {
|
||||||
|
useReactTable,
|
||||||
|
getCoreRowModel,
|
||||||
|
getSortedRowModel,
|
||||||
|
getPaginationRowModel,
|
||||||
|
getFilteredRowModel,
|
||||||
|
ColumnDef,
|
||||||
|
SortingState,
|
||||||
|
ColumnFiltersState,
|
||||||
|
} from '@tanstack/react-table';
|
||||||
|
import { IconEye, IconDoubleArrowRight, IconArrowRight, IconDoubleArrowLeft, IconArrowLeft, IconArrowUp, IconArrowDown, IconFilter } from '@/app/ui/icons/icons';
|
||||||
|
import { useTranslations, useLocale } from 'next-intl';
|
||||||
|
import DropDownList from '@/app/components/dropDownList';
|
||||||
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import ModalWindow from '@/app/components/modalWindow';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
import { useDebouncedCallback } from 'use-debounce';
|
||||||
|
import { fetchUsesInfo } from '@/app/actions/usersActions';
|
||||||
|
import { UserInfoModalWindow } from '@/app/ui/users/user-info-modal-window';
|
||||||
|
import { useContentForModeration, ContentForModeration } from '@/app/hooks/react-query/useContentForModeration';
|
||||||
|
|
||||||
|
// Форматирование даты из timestamp
|
||||||
|
const formatDate = (timestamp: number) => {
|
||||||
|
const date = new Date(timestamp);
|
||||||
|
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) {
|
||||||
|
return userId.length > MAX_FILE_NAME_LENGTH ? userId.substring(0, MAX_FILE_NAME_LENGTH - 3) + '...' : userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nameWithoutExtension = userId.substring(0, lastDotIndex);
|
||||||
|
const extension = userId.substring(lastDotIndex);
|
||||||
|
|
||||||
|
if (userId.length <= MAX_FILE_NAME_LENGTH) {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxNameLength = MAX_FILE_NAME_LENGTH - extension.length - 3;
|
||||||
|
|
||||||
|
if (maxNameLength <= 0) {
|
||||||
|
return '...' + extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nameWithoutExtension.substring(0, maxNameLength) + '...' + extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ContentModerationTable({ permission }: { permission: number }) {
|
||||||
|
// Состояния
|
||||||
|
const [sorting, setSorting] = useState<SortingState>([]);
|
||||||
|
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
|
||||||
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
|
const [pagination, setPagination] = useState({
|
||||||
|
pageIndex: 0,
|
||||||
|
pageSize: 10,
|
||||||
|
});
|
||||||
|
const [searchInputValue, setSearchInputValue] = useState<string>('');
|
||||||
|
const [openWindow, setOpenWindow] = useState<boolean>(false);
|
||||||
|
const [openWindowChildren, setOpenWindowChildren] = useState<ReactNode>(null);
|
||||||
|
const debouncedSetSearchQuery = useDebouncedCallback(
|
||||||
|
(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 locale = useLocale();
|
||||||
|
|
||||||
|
const getSortParams = useMemo(() => {
|
||||||
|
if (sorting.length === 0) {
|
||||||
|
return { sortBy: '', sortOrder: undefined };
|
||||||
|
}
|
||||||
|
|
||||||
|
const sort = sorting[0];
|
||||||
|
const sortByMap: Record<string, string> = {
|
||||||
|
'id': 'supportId',
|
||||||
|
'userName': 'fullName',
|
||||||
|
'email': 'email',
|
||||||
|
'verificationStatus': 'verificationStatus',
|
||||||
|
'createdAt': 'createdAt',
|
||||||
|
'tariff': 'tariff',
|
||||||
|
'tokens': 'tokens',
|
||||||
|
'userCompany': 'userCompany',
|
||||||
|
};
|
||||||
|
console.log(sortByMap[sort.id]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
sortBy: sortByMap[sort.id] || sort.id,
|
||||||
|
sortDirection: sort.desc ? 'desc' : 'asc'
|
||||||
|
};
|
||||||
|
}, [sorting]);
|
||||||
|
|
||||||
|
const { data: tableData,
|
||||||
|
isLoading,
|
||||||
|
isError,
|
||||||
|
isFetching,
|
||||||
|
error
|
||||||
|
} = useContentForModeration(pagination.pageIndex, pagination.pageSize, getSortParams?.sortBy, getSortParams?.sortDirection, searchQuery);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log(tableData);
|
||||||
|
}, [tableData])
|
||||||
|
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
// Определение колонок
|
||||||
|
const columns = useMemo<ColumnDef<ContentForModeration>[]>(() => {
|
||||||
|
const allColumns: ColumnDef<ContentForModeration>[] = [
|
||||||
|
{
|
||||||
|
accessorKey: 'id',
|
||||||
|
header: ({ column }) => (
|
||||||
|
<div className="column start">
|
||||||
|
<span>
|
||||||
|
ID
|
||||||
|
</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 }) => (
|
||||||
|
<div className="flex items-center table-item table-item-id">
|
||||||
|
<div>
|
||||||
|
<div className="font-medium w-full truncate" title={'row.original.id.toString()'}>
|
||||||
|
{'row.original.id'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
enableColumnFilter: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'userName',
|
||||||
|
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>
|
||||||
|
),
|
||||||
|
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>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="text-center">
|
||||||
|
{'row.original.verificationStatus'}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'tariff',
|
||||||
|
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>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return (
|
||||||
|
<div className="text-center">
|
||||||
|
{'row.original.tariff'}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
enableColumnFilter: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'tokens',
|
||||||
|
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>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return (
|
||||||
|
<div className={`text-center font-semibold`}>
|
||||||
|
{'row.original.tokens'}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
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>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return (
|
||||||
|
<div className={`text-center font-semibold`}>
|
||||||
|
<>
|
||||||
|
{formatDate(0)}
|
||||||
|
<br />
|
||||||
|
{formatDateTime(0)}
|
||||||
|
</>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
if (permission === 3) {
|
||||||
|
allColumns.push({
|
||||||
|
id: 'actions',
|
||||||
|
header: () => <div className="column">{t('actions')}</div>,
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="actions">
|
||||||
|
<div className="actions-group">
|
||||||
|
<button
|
||||||
|
onClick={() => {}}
|
||||||
|
className="bg-blue-500 hover:bg-blue-600"
|
||||||
|
>
|
||||||
|
<IconEye />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
enableSorting: false,
|
||||||
|
enableColumnFilter: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return allColumns;
|
||||||
|
}, [permission]);
|
||||||
|
|
||||||
|
// Фильтрация по типу файла и дате
|
||||||
|
const filteredData = useMemo(() => {
|
||||||
|
/* let result = tableData?.users; */
|
||||||
|
let result = [];
|
||||||
|
return []
|
||||||
|
if (!result) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (searchQuery.trim()) {
|
||||||
|
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;
|
||||||
|
}, [tableData, searchQuery]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const currentPageRows = table.getRowModel().rows;
|
||||||
|
const pageCount = table.getPageCount();
|
||||||
|
|
||||||
|
if (currentPageRows.length === 0 && pagination.pageIndex > 0 && pageCount > 0) {
|
||||||
|
table.setPageIndex(pagination.pageIndex - 1);
|
||||||
|
}
|
||||||
|
}, [filteredData, pagination.pageIndex]);
|
||||||
|
|
||||||
|
// Создание таблицы
|
||||||
|
const table = useReactTable({
|
||||||
|
data: filteredData,
|
||||||
|
columns,
|
||||||
|
state: {
|
||||||
|
sorting,
|
||||||
|
columnFilters,
|
||||||
|
pagination
|
||||||
|
},
|
||||||
|
manualPagination: true,
|
||||||
|
manualSorting: true,
|
||||||
|
manualFiltering: true,
|
||||||
|
/* pageCount: tableData?.pagination?.totalPages, */
|
||||||
|
pageCount: 0,
|
||||||
|
onPaginationChange: setPagination,
|
||||||
|
onSortingChange: (updater) => {
|
||||||
|
const newSorting = typeof updater === 'function' ? updater(sorting) : updater;
|
||||||
|
setSorting(newSorting);
|
||||||
|
setPagination(prev => ({ ...prev, pageIndex: 0 }));
|
||||||
|
},
|
||||||
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
getSortedRowModel: getSortedRowModel(),
|
||||||
|
getPaginationRowModel: getPaginationRowModel()
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="tanstak-table-wrapper">
|
||||||
|
<ModalWindow state={openWindow} callBack={setOpenWindow}>
|
||||||
|
{openWindowChildren}
|
||||||
|
</ModalWindow>
|
||||||
|
{/* Фильтры */}
|
||||||
|
<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-label">{t('items-per-page')}:</div>
|
||||||
|
<DropDownList
|
||||||
|
value={table.getState().pagination.pageSize}
|
||||||
|
//@ts-ignore сделал так потому что для table.setPageSize нужна цифра
|
||||||
|
callBack={table.setPageSize}
|
||||||
|
>
|
||||||
|
{[5, 10, 20, 50, 100].map(pageSize => (
|
||||||
|
<li key={pageSize} value={pageSize}>
|
||||||
|
{t('show')} {pageSize}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</DropDownList>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Таблица */}
|
||||||
|
<div className="tanstak-table-block">
|
||||||
|
<table className="tanstak-table">
|
||||||
|
<thead className="tanstak-table-head">
|
||||||
|
{table.getHeaderGroups().map(headerGroup => (
|
||||||
|
<tr key={headerGroup.id}>
|
||||||
|
{headerGroup.headers.map(header => (
|
||||||
|
<th
|
||||||
|
key={header.id}
|
||||||
|
>
|
||||||
|
{header.isPlaceholder
|
||||||
|
? null
|
||||||
|
: typeof header.column.columnDef.header === 'function'
|
||||||
|
? header.column.columnDef.header(header.getContext())
|
||||||
|
: header.column.columnDef.header as string}
|
||||||
|
</th>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</thead>
|
||||||
|
<tbody className={`tanstak-table-body ${isFetching ? 'loading' : ''}`}>
|
||||||
|
{table.getRowModel().rows.length > 0 ? (
|
||||||
|
table.getRowModel().rows.map(row => (
|
||||||
|
<tr key={row.id}>
|
||||||
|
{row.getVisibleCells().map(cell => (
|
||||||
|
<td key={cell.id}>
|
||||||
|
{typeof cell.column.columnDef.cell === 'function'
|
||||||
|
? cell.column.columnDef.cell(cell.getContext())
|
||||||
|
: cell.getValue() as string}
|
||||||
|
</td>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={columns.length} className="text-center">
|
||||||
|
{t('no-data-for-selected-filters')}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Пагинация */}
|
||||||
|
{/* <div className="tanstak-table-pagination">
|
||||||
|
<div className="pagination-info">
|
||||||
|
<span className="pagination-info-pages">
|
||||||
|
{t('page')}{' '}
|
||||||
|
<strong>
|
||||||
|
{(tableData?.pagination?.currentPage ?? 0) + 1} {t('out-of')} {tableData?.pagination?.totalPages ?? 1}
|
||||||
|
</strong>
|
||||||
|
</span>
|
||||||
|
<span className="pagination-info-files">
|
||||||
|
| {t('shown')} {table.getRowModel().rows.length} {t('out-of')} {tableData?.pagination?.totalElements ?? 0}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pagination-controls">
|
||||||
|
<button
|
||||||
|
className="arrow"
|
||||||
|
onClick={() => table.firstPage()}
|
||||||
|
disabled={!table.getCanPreviousPage()}
|
||||||
|
>
|
||||||
|
<IconDoubleArrowLeft />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="arrow"
|
||||||
|
onClick={() => table.previousPage()}
|
||||||
|
disabled={!table.getCanPreviousPage()}
|
||||||
|
>
|
||||||
|
<IconArrowLeft />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className="pagination-controls-pages">
|
||||||
|
{(() => {
|
||||||
|
const totalPages = tableData?.pagination?.totalPages;
|
||||||
|
if (!totalPages || totalPages === 0) return null;
|
||||||
|
|
||||||
|
const currentPageIndex = table.getState().pagination.pageIndex;
|
||||||
|
const maxVisiblePages = 5;
|
||||||
|
const visiblePagesCount = Math.min(maxVisiblePages, totalPages);
|
||||||
|
|
||||||
|
let startPage = currentPageIndex - Math.floor(maxVisiblePages / 2);
|
||||||
|
startPage = Math.max(0, Math.min(startPage, totalPages - visiblePagesCount));
|
||||||
|
|
||||||
|
return Array.from({ length: visiblePagesCount }, (_, i) => {
|
||||||
|
const pageIndex = startPage + i;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={pageIndex}
|
||||||
|
className={currentPageIndex === pageIndex ? 'current' : 'other'}
|
||||||
|
onClick={() => table.setPageIndex(pageIndex)}
|
||||||
|
>
|
||||||
|
{pageIndex + 1}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
})()}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="arrow"
|
||||||
|
onClick={() => table.nextPage()}
|
||||||
|
disabled={!table.getCanNextPage()}
|
||||||
|
>
|
||||||
|
<IconArrowRight />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="arrow"
|
||||||
|
onClick={() => table.lastPage()}
|
||||||
|
disabled={!table.getCanNextPage()}
|
||||||
|
>
|
||||||
|
<IconDoubleArrowRight />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div> */}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user