Files
no-copy-frontend/src/app/components/tanstak-table/TanstakTable.tsx
T
2026-05-20 11:09:15 +07:00

1085 lines
29 KiB
TypeScript

'use client';
import { useState, useMemo, useEffect, ReactNode, useCallback } from 'react';
import {
useReactTable,
getCoreRowModel,
getSortedRowModel,
getPaginationRowModel,
getFilteredRowModel,
ColumnDef,
SortingState,
ColumnFiltersState,
PaginationState
} from '@tanstack/react-table';
import { IconDoubleArrowRight, IconArrowRight, IconDoubleArrowLeft, IconArrowLeft, IconArrowUp, IconArrowDown, IconFilter, IconFileDownload, IconInfo, IconEye, IconCheck, IconImageFile, IconLink } from '@/app/ui/icons/icons';
import { useTranslations, useLocale } from 'next-intl';
import DropDownList from '@/app/components/DropDownList';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { getUserFilesData, viewFileInfo } from '@/app/actions/fileEntity';
import ModalWindow from '@/app/components/ModalWindow';
import { toast } from 'sonner';
import { pluralize } from '@/app/lib/pluralize';
import { convertBytes } from '@/app/lib/convertBytes';
import { FileInfoModalWindow } from '@/app/ui/modal-windows/file-info-modal-window';
import { FileTypeIcon } from '@/app/components/FileTypeIcon';
import { fetchReferralUserStats } from '@/app/actions/referralsActions';
import { MonitoringDropDown } from '@/app/components/tanstak-table/MonitoringDropDown';
import { formatDate, formatDateTime } from '@/app/lib/formatDate';
import { useViewport } from '@/app/hooks/useViewport';
import { downloadFile } from '@/app/actions/fileEntity';
import { useDebouncedCallback } from 'use-debounce';
import Link from 'next/link';
import { SelectedFilesAction } from '@/app/components/tanstak-table/SelectedFilesActions';
import Image from 'next/image';
import { filePermisionChange } from '@/app/actions/trackingActions';
export type FileItem = {
id: string;
fileName: string;
fileType: string;
violations?: number | undefined;
size?: number | undefined;
uploadDate?: number;
status?: string;
monitoring: string;
protectStatus: string;
_original?: ApiFile;
supportId: number;
thumbnailFileUrl: string;
permissions: {
DOWNLOAD: boolean;
}
};
type ApiFile = {
id: string;
originalFileName: string;
mimeType: string;
violations: number;
fileSize: number;
updatedAt: string;
status: string;
monitoring: string;
protectStatus: string;
supportId: number;
fileName: string;
thumbnailFileUrl: string;
permissions: {
DOWNLOAD: boolean;
}
};
type ApiResponse = {
files?: ApiFile[];
total_count: number;
page: number;
page_size: number;
};
const cutFileExtension = (fileName: string) => {
const lastDotIndex = fileName.lastIndexOf('.');
const extension = fileName.substring(lastDotIndex + 1);
return extension;
}
export default function TanstakFilesTable({ fileType, showFileLink }: { fileType: string, showFileLink?: boolean }) {
const [sorting, setSorting] = useState<SortingState>([]);
const [dateFilter, setDateFilter] = useState<string>('month');
const [typeFilter, setTypeFilter] = useState<string>(fileType);
const [pagination, setPagination] = useState({
pageIndex: 0,
pageSize: 10,
});
/* const [query, setQuery] = useState<string>(''); */
const [searchInputValue, setSearchInputValue] = useState<string>('');
const [searchQuery, setSearchQuery] = useState<string>('');
const [selectedFiles, setSelectedFiles] = useState<Set<string>>(new Set());
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 handleClearSearch = useCallback(() => {
setSearchInputValue('');
setSearchQuery('');
debouncedSetSearchQuery.cancel();
setPagination(prev => ({ ...prev, pageIndex: 0 }));
}, [debouncedSetSearchQuery]);
const getSortParams = useMemo(() => {
if (sorting.length === 0) {
return { sortBy: '', sortOrder: undefined };
}
const sort = sorting[0];
const sortByMap: Record<string, string> = {
'id': 'supportId',
'fileName': 'fileName',
'size': 'fileSize',
'uploadDate': 'updatedAt',
'monitoring': 'monitoring'
};
return {
sortBy: sortByMap[sort.id] || sort.id,
sortOrder: sort.desc ? 'desc' : 'asc'
};
}, [sorting]);
// Преобразование фильтра типа
const getTypeParam = useMemo(() => {
return typeFilter !== 'all' ? typeFilter : '';
}, [typeFilter]);
// Преобразование фильтра даты
const getDateParam = useMemo(() => {
return dateFilter !== 'all' ? dateFilter : '';
}, [dateFilter]);
const {
data: apiResponse,
isLoading,
isFetching,
isError,
error,
refetch
} = useQuery({
queryKey: [
'userFilesData',
pagination.pageIndex,
pagination.pageSize,
getSortParams.sortBy,
getSortParams.sortOrder,
getTypeParam,
getDateParam,
searchQuery
],
queryFn: () => getUserFilesData({
page: pagination.pageIndex + 1,
pageSize: pagination.pageSize,
sortBy: getSortParams.sortBy,
sortOrder: getSortParams?.sortOrder,
type: getTypeParam,
date: getDateParam,
query: searchQuery
}),
select: (data: any): {
items: FileItem[];
total_count: number;
page: number;
pageSize: number;
} => {
if (!data?.files) {
return { items: [], total_count: 0, page: 1, pageSize: pagination.pageSize };
}
const items = data.files.map((item: ApiFile) => ({
id: item.id,
fileName: item.originalFileName,
fileType: item.mimeType.toLowerCase(),
violations: item.violations,
size: item.fileSize,
uploadDate: new Date(item.updatedAt).getTime(),
status: item.status,
monitoring: item.monitoring,
protectStatus: item.protectStatus,
supportId: item.supportId,
_original: item,
thumbnailFileUrl: item.thumbnailFileUrl,
permissions: {
DOWNLOAD: item.permissions.DOWNLOAD,
}
}));
return {
items,
total_count: data.total_count || data.files.length,
page: data.page || pagination.pageIndex + 1,
pageSize: data.page_size || pagination.pageSize
};
},
refetchInterval: 30000,
placeholderData: (previousData) => previousData // для оптимистичных обновлений
});
const queryClient = useQueryClient();
// Данные для таблицы
const tableData = apiResponse?.items || [];
const totalItems = apiResponse?.total_count || 0;
const totalPages = Math.ceil(totalItems / pagination.pageSize);
const [openWindow, setOpenWindow] = useState<boolean>(false);
const [openWindowChildren, setOpenWindowChildren] = useState<ReactNode>(null);
const [isFileLoading, setIsFileLoading] = useState(false);
const t = useTranslations("Global");
const locale = useLocale();
const viewport = useViewport();
const getMaxLengthByWidth = (): number => {
if (viewport.device === 'MOBILE') return 22;
if (viewport.device === 'TABLET') return 30;
if (viewport.device === 'DESKTOP') return 50;
return 100;
};
const cutFileName = (fileName: string) => {
const MAX_FILE_NAME_LENGTH = getMaxLengthByWidth();
const lastDotIndex = fileName.lastIndexOf('.');
if (lastDotIndex <= 0) {
return fileName.length > MAX_FILE_NAME_LENGTH ? fileName.substring(0, MAX_FILE_NAME_LENGTH - 3) + '...' : fileName;
}
const nameWithoutExtension = fileName.substring(0, lastDotIndex);
const extension = fileName.substring(lastDotIndex);
if (fileName.length <= MAX_FILE_NAME_LENGTH) {
return fileName;
}
const maxNameLength = MAX_FILE_NAME_LENGTH - extension.length - 3;
if (maxNameLength <= 0) {
return '...' /* + extension */;
}
return nameWithoutExtension.substring(0, maxNameLength) + '...' /* + extension */;
}
function selectHandler(fileId: string) {
setSelectedFiles((prev) => {
const newSet = new Set(prev)
if (newSet.has(fileId)) {
newSet.delete(fileId)
} else {
newSet.add(fileId)
}
return newSet;
});
}
const selectAllHandler = useCallback(() => {
if (!apiResponse?.items?.length) return;
setSelectedFiles((prev) => {
const allIds = new Set(apiResponse.items.map(item => item.id));
const isAllSelected = Array.from(allIds).every(id => prev.has(id));
if (isAllSelected) {
const newSet = new Set(prev);
allIds.forEach(id => newSet.delete(id));
return newSet;
} else {
return new Set([...prev, ...allIds]);
}
});
}, [apiResponse]);
const isAllSelected = useMemo(() => {
if (!apiResponse?.items?.length) return false;
const allIds = new Set(apiResponse.items.map(item => item.id));
return Array.from(allIds).every(id => selectedFiles.has(id));
}, [apiResponse, selectedFiles]);
// Определение колонок
const columns = useMemo<ColumnDef<FileItem>[]>(
() => [
{
accessorKey: 'selectd',
header: ({ column }) => (
<div
className="column"
onClick={() => {
selectAllHandler();
}}
>
<span>
<button
className={`table-item-checkbox ${isAllSelected ? 'selected' : ''}`}
>
{isAllSelected && <IconCheck />}
</button>
</span>
</div>
),
cell: ({ row }) => {
return (
<div
className="text-center table-item table-item-id"
onClick={() => {
selectHandler(row.original.id);
}}
>
<button
className={`table-item-checkbox ${selectedFiles.has(row.original.id) ? 'selected' : ''}`}
>
{selectedFiles.has(row.original.id) && (
<IconCheck />
)}
</button>
</div>
)
},
},
{
accessorKey: 'id',
header: ({ column }) => (
<div className="column">
<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 }) => {
return (
<div className="text-center table-item table-item-id">
{row.original.supportId ? row.original.supportId : '-'}
</div>
)
},
},
{
accessorKey: 'fileName',
header: ({ column }) => (
<div className="column start">
<span>
{t('file')}
</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 space-x-2 table-item table-item-file-name">
{row.original.fileType === 'image' && row.original?.thumbnailFileUrl.length !== 0 ? (
<span
className="table-item-file-name-image-wrapper"
>
<Image
sizes="40px"
src={row.original?.thumbnailFileUrl}
alt={row.original?.fileName}
unoptimized
fill
onError={(e) => {
const target = e.target as HTMLImageElement;
target.src = '/images/no-image.png';
}}
/>
</span>
) : (
<FileTypeIcon type={row.original.fileType} />
)}
<div>
<div className="font-medium w-full truncate" title={row.original.fileName}>
<span className="font-semibold">
{cutFileName(row.original.fileName)}
</span>
<br />
<span className="table-item-extension">{cutFileExtension(row.original.fileName)}</span> <span className="table-item-protected">
{row.original?.protectStatus ? t(row.original?.protectStatus) : t('error')}
</span>
</div>
</div>
</div>
),
enableColumnFilter: false,
},
{
accessorKey: 'size',
header: ({ column }) => (
<div className="column">
<span>
{t('size')}
</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 table-item">
{row.original.size !== undefined ? convertBytes(row.original.size) : '-'}
</div>
),
},
{
accessorKey: 'uploadDate',
header: ({ column }) => (
<div className="column">
<span>
{t('upload-date')}
</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 table-item">
{row.original.uploadDate ? (
<>
{formatDate(row.original.uploadDate)}
<br />
{formatDateTime(row.original.uploadDate)}
<br />
</>
) : (
<div>-</div>
)}
</div>
)
},
enableColumnFilter: false,
},
{
accessorKey: 'monitoring',
header: ({ column }) => (
<div className="column">
<span>
{t('monitoring-status')}
</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 table-item">
{row.original.fileType === 'image' ? (
<MonitoringDropDown file={row.original} />
) : (
<div
className="table-item-not-supported"
>
{t('monitoring-is-not-supported')}
<br />
{t('for-this-file-type')}
</div>
)}
</div>
)
},
},
{
accessorKey: 'downloadPermision',
header: ({ column }) => (
<div className="column">
<span>
Можно скачать по ссылке
</span>
</div>
),
cell: ({ row }) => {
return (
<div className="text-center font-semibold table-item">
<button
onClick={() => changeDownloadPermition(row.original.id, row.original.permissions.DOWNLOAD)}
disabled={isFileLoading}
className={`toggle-switch ${row.original.permissions?.DOWNLOAD ? 'active' : 'inactive'}`}
title={'change-permission'}
>
<span className="toggle-slider"></span>
<span className="toggle-label">
{row.original.permissions?.DOWNLOAD ? 'Да' : 'Нет'}
</span>
</button>
</div>
)
},
},
{
id: 'actions',
header: () => {
return (
<div className="column">
{t('actions')}
</div>
)
},
cell: ({ row }) => (
<div className="actions">
{(showFileLink && row.original.fileType === 'document') ? (
<div className="actions-group">
<Link
href={`/watch-doc?docid=${row.original.id}`}
className="bg-indigo-500 hover:bg-indigo-600"
title={t('view')}
target='_blank'
>
<IconLink />
</Link>
</div>
) : (
<>
<div className="actions-group">
<Link
href={`/pages/file/${row.original.id}`}
className="bg-violet-500 hover:bg-violet-600"
title={t('view')}
>
<IconEye />
</Link>
</div>
<div className="actions-group">
{row.original.protectStatus === 'PROTECTED' && (
<button
onClick={() => handleDownload(row.original)}
disabled={isFileLoading}
className="table-action-download"
title={t('download')}
>
<IconFileDownload />
</button>
)}
<button
onClick={() => {
handleView(row.original)
}}
className="table-action-view"
title={t('make-check')}
>
<IconInfo />
</button>
</div>
</>
)}
</div>
),
enableSorting: false,
enableColumnFilter: false,
},
],
[viewport.device, selectedFiles, selectAllHandler]
);
const handleView = async (file: FileItem) => {
const fileInfo = await viewFileInfo(file.id);
setOpenWindowChildren(() => {
return (
<FileInfoModalWindow fileInfo={fileInfo} setWindowClose={setOpenWindow} setWindowChildren={setOpenWindowChildren} />
)
})
setOpenWindow(true);
};
const handleDownload = async (file: FileItem) => {
setIsFileLoading(true)
try {
const result = await downloadFile(
file.id,
file._original?.fileName || file.fileName || `file-${file.id}`
);
const byteCharacters = atob(result.data);
const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i)
}
const byteArray = new Uint8Array(byteNumbers);
const blob = new Blob([byteArray], { type: result.contentType });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = result.fileName;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
toast.success(`${t('file-is-downloading')} - ${file.fileName}`);
} catch (error) {
toast.error(t('failed-to-download-file'));
} finally {
setIsFileLoading(false);
}
}
const changeDownloadPermition = async (fileId: string, permission: boolean) => {
const response = await filePermisionChange(fileId, !permission);
if (response) {
queryClient.invalidateQueries({ queryKey: ['userFilesData'] });
toast.success('Разрешение на скачивание изменено.');
} else {
toast.warning('Не удалось изменить разрешение на скачивание.');
}
}
// Фильтрация по типу файла и дате
const filteredData = useMemo(() => {
let result = tableData;
if (!result) {
return [];
}
// Фильтр по типу файла
if (typeFilter !== 'all') {
result = result.filter(item => item.fileType === typeFilter);
}
// Фильтр по дате
if (dateFilter !== 'all') {
const now = Date.now();
const oneDay = 24 * 60 * 60 * 1000;
const sevenDays = 7 * oneDay;
const thirtyDays = 30 * oneDay;
switch (dateFilter) {
case 'today':
const todayStart = new Date().setHours(0, 0, 0, 0);
result = result.filter(item => {
if (item.uploadDate) {
return item.uploadDate >= todayStart
} else {
return 0
}
});
break;
case 'week':
const weekAgo = now - sevenDays;
result = result.filter(item => {
if (item.uploadDate) {
return item.uploadDate >= weekAgo;
}
});
break;
case 'month':
const monthAgo = now - thirtyDays;
result = result.filter(item => {
if (item.uploadDate) {
return item.uploadDate >= monthAgo;
}
});
break;
case 'older':
const monthAgo2 = now - thirtyDays;
result = result.filter(item => {
if (item.uploadDate) {
return item.uploadDate < monthAgo2;
}
});
break;
}
}
return result;
}, [/* query, */ typeFilter, dateFilter]);
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: tableData,
columns,
state: {
sorting,
pagination,
columnVisibility: {
monitoring: showFileLink ? false : true,
downloadPermision: showFileLink ? true : false,
}
},
pageCount: totalPages,
manualPagination: true,
manualSorting: true,
manualFiltering: true,
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(),
});
const handleDateFilterChange = (newDateFilter: string) => {
setDateFilter(newDateFilter);
setPagination(prev => ({ ...prev, pageIndex: 0 }));
};
const handleTypeFilterChange = (newTypeFilter: string) => {
setTypeFilter(newTypeFilter);
setPagination(prev => ({ ...prev, pageIndex: 0 }));
};
const pluralizeFiles = (number: number) => {
const translate = [t('file'), t('files-few'), t('files')];
return pluralize(number, translate[0], translate[1], translate[2], locale);
};
useEffect(() => {
if (tableData.length === 0 && pagination.pageIndex > 0 && totalPages > 0) {
setPagination(prev => ({ ...prev, pageIndex: Math.max(0, totalPages - 1) }));
}
}, [tableData, pagination.pageIndex, totalPages]);
return (
<div className="tanstak-table-wrapper">
<h3
className="tanstak-table-title"
>
{(() => {
switch (fileType) {
case 'image':
return t('protected-image')
case 'video':
return t('protected-video')
case 'audio':
return t('protected-audio')
case 'document':
return t('protected-document')
default:
return t('table-description')
}
})()}
</h3>
<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('date-filter')}:</div>
<DropDownList
value={dateFilter}
translatedValue={(() => {
switch (dateFilter) {
case 'all':
return t('all-dates');
case 'today':
return t('today');
case 'week':
return t('for-a-week');
case 'month':
return t('for-a-month');
default:
return t('all-dates');
}
})()}
callBack={handleDateFilterChange}
>
<li value="all">{t('all-dates')}</li>
<li value="today">{t('today')}</li>
<li value="week">{t('for-a-week')}</li>
<li value="month">{t('for-a-month')}</li>
</DropDownList>
</div>
{fileType === "all" && (
<div className="table-filtres-item">
<div className="table-filtres-label">{t('type-filter')}:</div>
<DropDownList
value={typeFilter}
translatedValue={(() => {
switch (typeFilter) {
case 'image':
return t('images');
case 'video':
return t('videos');
case 'audio':
return t('audios');
case 'document':
return t('documents');
default:
return t('all-types');
}
})()}
callBack={handleTypeFilterChange}
>
<li value="all">{t('all-types')}</li>
<li value="image">{t('images-few')}</li>
<li value="video">{t('videos')}</li>
<li value="audio">{t('audios')}</li>
<li value="document">{t('documents')}</li>
</DropDownList>
</div>
)}
</div>
<div className="table-filtres-wrapper text-filter-search">
<div className="table-filtres-item">
<div className="table-filtres-label">{t('search')}:</div>
<input
type="text"
className="table-filtres-text-filter"
value={searchInputValue}
onChange={handleSearchInputChange}
placeholder={t('search')}
/>
</div>
<div className="table-filtres-item">
<div className="table-filtres-label">{t('items-per-page')}:</div>
<DropDownList
value={table.getState().pagination.pageSize}
callBack={(newSize: string) => {
table.setPageSize(Number(newSize));
setPagination(prev => ({ ...prev, pageIndex: 0 }));
}}
>
{['5', '10', '20', '50', '100'].map(pageSize => (
<li key={pageSize} value={pageSize}>
{t('show')} {pageSize}
</li>
))}
</DropDownList>
</div>
</div>
</div>
{/* {isLoading && (
<div className="loading-indicator">
loading...
</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' : ''}`}>
{isLoading && (
<tr>
<td colSpan={columns.length} className="text-center relative h-12.5">
<div
className="loading-animation"
>
<div
className="global-spinner"
>
</div>
</div>
</td>
</tr>
)}
{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>
{!isLoading && (
<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>
{pagination.pageIndex + 1} {t('out-of')} {totalPages || 1}
</strong>
</span>
</div>
<div className="pagination-controls">
<button
className="arrow"
onClick={() => table.firstPage()}
disabled={!table.getCanPreviousPage() || isLoading}
>
<IconDoubleArrowLeft />
</button>
<button
className="arrow"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage() || isLoading}
>
<IconArrowLeft />
</button>
<div className="pagination-controls-pages">
{Array.from({ length: Math.min(5, totalPages) }, (_, i) => {
let pageIndex;
if (totalPages <= 5) {
pageIndex = i;
} else if (pagination.pageIndex <= 2) {
pageIndex = i;
} else if (pagination.pageIndex >= totalPages - 3) {
pageIndex = totalPages - 5 + i;
} else {
pageIndex = pagination.pageIndex - 2 + i;
}
if (pageIndex >= 0 && pageIndex < totalPages) {
return (
<button
key={pageIndex}
className={pagination.pageIndex === pageIndex ? 'current' : 'other'}
onClick={() => table.setPageIndex(pageIndex)}
disabled={isLoading}
>
{pageIndex + 1}
</button>
);
}
return null;
})}
</div>
<button
className="arrow"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage() || isLoading}
>
<IconArrowRight />
</button>
<button
className="arrow"
onClick={() => table.lastPage()}
disabled={!table.getCanNextPage() || isLoading}
>
<IconDoubleArrowRight />
</button>
</div>
</div>
<SelectedFilesAction filesId={selectedFiles} callBack={setSelectedFiles} />
</div >
);
}