2025-12-12 14:22:00 +07:00
|
|
|
'use client';
|
|
|
|
|
|
2026-03-20 20:07:50 +07:00
|
|
|
import { useState, useMemo, useEffect, ReactNode, useCallback } from 'react';
|
2025-12-12 14:22:00 +07:00
|
|
|
import {
|
|
|
|
|
useReactTable,
|
|
|
|
|
getCoreRowModel,
|
|
|
|
|
getSortedRowModel,
|
|
|
|
|
getPaginationRowModel,
|
|
|
|
|
getFilteredRowModel,
|
|
|
|
|
ColumnDef,
|
|
|
|
|
SortingState,
|
|
|
|
|
ColumnFiltersState,
|
2026-03-20 19:45:18 +07:00
|
|
|
PaginationState
|
2025-12-12 14:22:00 +07:00
|
|
|
} from '@tanstack/react-table';
|
2026-04-28 16:32:23 +07:00
|
|
|
import { IconDoubleArrowRight, IconArrowRight, IconDoubleArrowLeft, IconArrowLeft, IconArrowUp, IconArrowDown, IconFilter, IconFileDownload, IconInfo, IconEye, IconCheck, IconImageFile, IconLink } from '@/app/ui/icons/icons';
|
2026-01-15 12:27:39 +07:00
|
|
|
import { useTranslations, useLocale } from 'next-intl';
|
2026-02-04 15:13:17 +07:00
|
|
|
import DropDownList from '@/app/components/DropDownList';
|
2026-02-21 15:07:37 +07:00
|
|
|
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
|
|
|
|
import { getUserFilesData, viewFileInfo } from '@/app/actions/fileEntity';
|
2026-02-04 15:13:17 +07:00
|
|
|
import ModalWindow from '@/app/components/ModalWindow';
|
2025-12-29 14:00:02 +07:00
|
|
|
import { toast } from 'sonner';
|
2026-01-14 15:01:30 +07:00
|
|
|
import { pluralize } from '@/app/lib/pluralize';
|
2026-01-20 20:09:46 +07:00
|
|
|
import { convertBytes } from '@/app/lib/convertBytes';
|
2026-01-28 18:53:08 +07:00
|
|
|
import { FileInfoModalWindow } from '@/app/ui/modal-windows/file-info-modal-window';
|
2026-01-29 16:28:05 +07:00
|
|
|
import { FileTypeIcon } from '@/app/components/FileTypeIcon';
|
2026-02-19 18:49:41 +07:00
|
|
|
import { fetchReferralUserStats } from '@/app/actions/referralsActions';
|
2026-02-20 14:55:23 +07:00
|
|
|
import { MonitoringDropDown } from '@/app/components/tanstak-table/MonitoringDropDown';
|
2026-02-23 12:36:54 +07:00
|
|
|
import { formatDate, formatDateTime } from '@/app/lib/formatDate';
|
2026-03-05 16:44:33 +07:00
|
|
|
import { useViewport } from '@/app/hooks/useViewport';
|
2026-03-20 19:45:18 +07:00
|
|
|
import { downloadFile } from '@/app/actions/fileEntity';
|
|
|
|
|
import { useDebouncedCallback } from 'use-debounce';
|
2026-03-30 12:48:31 +07:00
|
|
|
import Link from 'next/link';
|
|
|
|
|
import { SelectedFilesAction } from '@/app/components/tanstak-table/SelectedFilesActions';
|
2026-04-24 12:08:43 +07:00
|
|
|
import Image from 'next/image';
|
2026-05-05 13:07:52 +07:00
|
|
|
import { filePermisionChange } from '@/app/actions/trackingActions';
|
2025-12-29 14:00:02 +07:00
|
|
|
|
2026-02-20 14:55:23 +07:00
|
|
|
export type FileItem = {
|
2025-12-26 14:03:57 +07:00
|
|
|
id: string;
|
2025-12-12 20:28:59 +07:00
|
|
|
fileName: string;
|
2025-12-29 14:00:02 +07:00
|
|
|
fileType: string;
|
2025-12-26 14:03:57 +07:00
|
|
|
violations?: number | undefined;
|
2026-01-20 20:09:46 +07:00
|
|
|
size?: number | undefined;
|
|
|
|
|
uploadDate?: number;
|
|
|
|
|
status?: string;
|
2026-03-06 14:53:51 +07:00
|
|
|
monitoring: string;
|
2026-01-26 16:09:39 +07:00
|
|
|
protectStatus: string;
|
2025-12-29 14:00:02 +07:00
|
|
|
_original?: ApiFile;
|
2026-01-26 16:38:24 +07:00
|
|
|
supportId: number;
|
2026-05-05 13:07:52 +07:00
|
|
|
thumbnailFileUrl: string;
|
|
|
|
|
permissions: {
|
|
|
|
|
DOWNLOAD: boolean;
|
|
|
|
|
}
|
2025-12-29 14:00:02 +07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
type ApiFile = {
|
|
|
|
|
id: string;
|
|
|
|
|
originalFileName: string;
|
|
|
|
|
mimeType: string;
|
|
|
|
|
violations: number;
|
2026-01-20 20:09:46 +07:00
|
|
|
fileSize: number;
|
2025-12-29 14:00:02 +07:00
|
|
|
updatedAt: string;
|
2026-01-20 20:09:46 +07:00
|
|
|
status: string;
|
2026-03-06 14:53:51 +07:00
|
|
|
monitoring: string;
|
2026-01-26 16:09:39 +07:00
|
|
|
protectStatus: string;
|
2026-01-26 16:38:24 +07:00
|
|
|
supportId: number;
|
2026-02-12 16:45:29 +07:00
|
|
|
fileName: string;
|
2026-04-24 12:08:43 +07:00
|
|
|
thumbnailFileUrl: string;
|
2026-05-05 13:07:52 +07:00
|
|
|
permissions: {
|
|
|
|
|
DOWNLOAD: boolean;
|
|
|
|
|
}
|
2025-12-29 14:00:02 +07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
type ApiResponse = {
|
|
|
|
|
files?: ApiFile[];
|
2026-03-20 19:45:18 +07:00
|
|
|
total_count: number;
|
|
|
|
|
page: number;
|
|
|
|
|
page_size: number;
|
2025-12-12 14:22:00 +07:00
|
|
|
};
|
|
|
|
|
|
2026-01-20 20:09:46 +07:00
|
|
|
const cutFileExtension = (fileName: string) => {
|
|
|
|
|
const lastDotIndex = fileName.lastIndexOf('.');
|
|
|
|
|
const extension = fileName.substring(lastDotIndex + 1);
|
|
|
|
|
return extension;
|
2026-01-04 17:43:58 +07:00
|
|
|
}
|
|
|
|
|
|
2026-04-28 16:32:23 +07:00
|
|
|
export default function TanstakFilesTable({ fileType, showFileLink }: { fileType: string, showFileLink?: boolean }) {
|
2026-03-20 19:45:18 +07:00
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
});
|
2026-03-30 12:48:31 +07:00
|
|
|
/* const [query, setQuery] = useState<string>(''); */
|
2026-03-20 20:07:50 +07:00
|
|
|
const [searchInputValue, setSearchInputValue] = useState<string>('');
|
|
|
|
|
const [searchQuery, setSearchQuery] = useState<string>('');
|
2026-03-30 12:48:31 +07:00
|
|
|
const [selectedFiles, setSelectedFiles] = useState<Set<string>>(new Set());
|
2026-03-20 19:45:18 +07:00
|
|
|
|
2026-03-20 20:07:50 +07:00
|
|
|
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]);
|
2026-03-20 19:45:18 +07:00
|
|
|
|
|
|
|
|
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]);
|
|
|
|
|
|
2025-12-26 14:03:57 +07:00
|
|
|
const {
|
2026-03-20 19:45:18 +07:00
|
|
|
data: apiResponse,
|
2025-12-26 14:03:57 +07:00
|
|
|
isLoading,
|
2026-04-17 14:36:04 +07:00
|
|
|
isFetching,
|
2025-12-26 14:03:57 +07:00
|
|
|
isError,
|
2025-12-26 16:10:42 +07:00
|
|
|
error,
|
2026-03-20 19:45:18 +07:00
|
|
|
refetch
|
|
|
|
|
} = useQuery({
|
|
|
|
|
queryKey: [
|
|
|
|
|
'userFilesData',
|
|
|
|
|
pagination.pageIndex,
|
|
|
|
|
pagination.pageSize,
|
|
|
|
|
getSortParams.sortBy,
|
|
|
|
|
getSortParams.sortOrder,
|
|
|
|
|
getTypeParam,
|
|
|
|
|
getDateParam,
|
2026-03-20 20:07:50 +07:00
|
|
|
searchQuery
|
2026-03-20 19:45:18 +07:00
|
|
|
],
|
|
|
|
|
queryFn: () => getUserFilesData({
|
|
|
|
|
page: pagination.pageIndex + 1,
|
|
|
|
|
pageSize: pagination.pageSize,
|
|
|
|
|
sortBy: getSortParams.sortBy,
|
|
|
|
|
sortOrder: getSortParams?.sortOrder,
|
|
|
|
|
type: getTypeParam,
|
|
|
|
|
date: getDateParam,
|
2026-03-20 20:07:50 +07:00
|
|
|
query: searchQuery
|
2026-03-20 19:45:18 +07:00
|
|
|
}),
|
|
|
|
|
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 };
|
|
|
|
|
}
|
2025-12-26 14:03:57 +07:00
|
|
|
|
2026-03-20 19:45:18 +07:00
|
|
|
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,
|
2026-04-24 12:08:43 +07:00
|
|
|
_original: item,
|
2026-05-05 13:07:52 +07:00
|
|
|
thumbnailFileUrl: item.thumbnailFileUrl,
|
|
|
|
|
permissions: {
|
|
|
|
|
DOWNLOAD: item.permissions.DOWNLOAD,
|
|
|
|
|
}
|
2026-03-20 19:45:18 +07:00
|
|
|
}));
|
2025-12-26 14:03:57 +07:00
|
|
|
|
2026-03-20 19:45:18 +07:00
|
|
|
return {
|
|
|
|
|
items,
|
2026-03-20 20:17:14 +07:00
|
|
|
total_count: data.total_count || data.files.length,
|
2026-03-20 19:45:18 +07:00
|
|
|
page: data.page || pagination.pageIndex + 1,
|
|
|
|
|
pageSize: data.page_size || pagination.pageSize
|
|
|
|
|
};
|
2025-12-29 14:00:02 +07:00
|
|
|
},
|
2026-03-20 19:45:18 +07:00
|
|
|
refetchInterval: 30000,
|
|
|
|
|
placeholderData: (previousData) => previousData // для оптимистичных обновлений
|
2025-12-29 14:00:02 +07:00
|
|
|
});
|
2025-12-26 16:10:42 +07:00
|
|
|
|
2025-12-29 14:00:02 +07:00
|
|
|
const queryClient = useQueryClient();
|
2025-12-26 14:03:57 +07:00
|
|
|
|
2026-03-20 19:45:18 +07:00
|
|
|
// Данные для таблицы
|
|
|
|
|
const tableData = apiResponse?.items || [];
|
|
|
|
|
const totalItems = apiResponse?.total_count || 0;
|
|
|
|
|
const totalPages = Math.ceil(totalItems / pagination.pageSize);
|
|
|
|
|
|
|
|
|
|
|
2025-12-26 16:10:42 +07:00
|
|
|
const [openWindow, setOpenWindow] = useState<boolean>(false);
|
|
|
|
|
const [openWindowChildren, setOpenWindowChildren] = useState<ReactNode>(null);
|
2025-12-12 20:28:59 +07:00
|
|
|
|
2026-01-02 12:52:01 +07:00
|
|
|
const [isFileLoading, setIsFileLoading] = useState(false);
|
|
|
|
|
|
2025-12-12 20:28:59 +07:00
|
|
|
const t = useTranslations("Global");
|
2026-01-15 12:27:39 +07:00
|
|
|
const locale = useLocale();
|
2026-03-05 16:44:33 +07:00
|
|
|
const viewport = useViewport();
|
|
|
|
|
|
|
|
|
|
const getMaxLengthByWidth = (): number => {
|
2026-04-01 12:47:17 +07:00
|
|
|
if (viewport.device === 'MOBILE') return 22;
|
|
|
|
|
if (viewport.device === 'TABLET') return 30;
|
|
|
|
|
if (viewport.device === 'DESKTOP') return 50;
|
2026-03-05 16:44:33 +07:00
|
|
|
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 */;
|
|
|
|
|
}
|
2025-12-12 14:22:00 +07:00
|
|
|
|
2026-03-30 12:48:31 +07:00
|
|
|
function selectHandler(fileId: string) {
|
|
|
|
|
|
|
|
|
|
setSelectedFiles((prev) => {
|
|
|
|
|
const newSet = new Set(prev)
|
|
|
|
|
if (newSet.has(fileId)) {
|
|
|
|
|
newSet.delete(fileId)
|
|
|
|
|
} else {
|
|
|
|
|
newSet.add(fileId)
|
|
|
|
|
}
|
|
|
|
|
return newSet;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-01 13:34:50 +07:00
|
|
|
|
|
|
|
|
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]);
|
|
|
|
|
|
2025-12-12 14:22:00 +07:00
|
|
|
// Определение колонок
|
2025-12-12 20:28:59 +07:00
|
|
|
const columns = useMemo<ColumnDef<FileItem>[]>(
|
2025-12-12 14:22:00 +07:00
|
|
|
() => [
|
2026-03-30 12:48:31 +07:00
|
|
|
{
|
|
|
|
|
accessorKey: 'selectd',
|
|
|
|
|
header: ({ column }) => (
|
2026-04-01 13:34:50 +07:00
|
|
|
<div
|
|
|
|
|
className="column"
|
|
|
|
|
onClick={() => {
|
|
|
|
|
selectAllHandler();
|
|
|
|
|
}}
|
|
|
|
|
>
|
2026-03-30 12:48:31 +07:00
|
|
|
<span>
|
2026-04-01 13:34:50 +07:00
|
|
|
<button
|
|
|
|
|
className={`table-item-checkbox ${isAllSelected ? 'selected' : ''}`}
|
|
|
|
|
>
|
|
|
|
|
{isAllSelected && <IconCheck />}
|
|
|
|
|
</button>
|
2026-03-30 12:48:31 +07:00
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
),
|
|
|
|
|
cell: ({ row }) => {
|
|
|
|
|
return (
|
2026-04-01 13:34:50 +07:00
|
|
|
<div
|
|
|
|
|
className="text-center table-item table-item-id"
|
|
|
|
|
onClick={() => {
|
|
|
|
|
selectHandler(row.original.id);
|
|
|
|
|
}}
|
|
|
|
|
>
|
2026-03-30 12:48:31 +07:00
|
|
|
<button
|
|
|
|
|
className={`table-item-checkbox ${selectedFiles.has(row.original.id) ? 'selected' : ''}`}
|
|
|
|
|
>
|
|
|
|
|
{selectedFiles.has(row.original.id) && (
|
|
|
|
|
<IconCheck />
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
},
|
2026-01-26 16:38:24 +07:00
|
|
|
{
|
|
|
|
|
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">
|
2026-01-27 19:52:01 +07:00
|
|
|
{row.original.supportId ? row.original.supportId : '-'}
|
2026-01-26 16:38:24 +07:00
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
},
|
2025-12-12 14:22:00 +07:00
|
|
|
{
|
2025-12-12 20:28:59 +07:00
|
|
|
accessorKey: 'fileName',
|
|
|
|
|
header: ({ column }) => (
|
2025-12-30 13:35:20 +07:00
|
|
|
<div className="column start">
|
2025-12-12 20:28:59 +07:00
|
|
|
<span>
|
|
|
|
|
{t('file')}
|
|
|
|
|
</span>
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
2025-12-30 13:35:20 +07:00
|
|
|
className="sort-button"
|
2025-12-12 20:28:59 +07:00
|
|
|
>
|
|
|
|
|
{
|
|
|
|
|
column.getIsSorted() === 'asc' ?
|
|
|
|
|
<span>
|
2025-12-17 16:41:07 +07:00
|
|
|
<IconArrowUp />
|
2025-12-12 20:28:59 +07:00
|
|
|
</span>
|
|
|
|
|
: column.getIsSorted() === 'desc' ?
|
|
|
|
|
<span>
|
2025-12-17 16:41:07 +07:00
|
|
|
<IconArrowDown />
|
2025-12-12 20:28:59 +07:00
|
|
|
</span>
|
|
|
|
|
: <span>
|
2025-12-17 16:41:07 +07:00
|
|
|
<IconFilter />
|
2025-12-12 20:28:59 +07:00
|
|
|
</span>
|
|
|
|
|
}
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
2025-12-12 14:22:00 +07:00
|
|
|
),
|
|
|
|
|
cell: ({ row }) => (
|
2026-03-05 16:44:33 +07:00
|
|
|
<div className="flex items-center space-x-2 table-item table-item-file-name">
|
2026-04-24 12:08:43 +07:00
|
|
|
{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} />
|
|
|
|
|
)}
|
2025-12-12 20:28:59 +07:00
|
|
|
<div>
|
2026-01-04 17:43:58 +07:00
|
|
|
<div className="font-medium w-full truncate" title={row.original.fileName}>
|
2026-01-21 15:05:02 +07:00
|
|
|
<span className="font-semibold">
|
|
|
|
|
{cutFileName(row.original.fileName)}
|
|
|
|
|
</span>
|
2026-01-20 20:09:46 +07:00
|
|
|
<br />
|
2026-02-02 15:09:29 +07:00
|
|
|
<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>
|
2026-01-04 17:43:58 +07:00
|
|
|
</div>
|
2025-12-12 20:28:59 +07:00
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-12-12 14:22:00 +07:00
|
|
|
),
|
|
|
|
|
enableColumnFilter: false,
|
|
|
|
|
},
|
2026-01-20 20:09:46 +07:00
|
|
|
{
|
|
|
|
|
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 }) => (
|
2026-01-21 15:05:02 +07:00
|
|
|
<div className="text-center table-item">
|
2026-01-20 20:09:46 +07:00
|
|
|
{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 (
|
2026-01-21 15:05:02 +07:00
|
|
|
<div className="text-center table-item">
|
2026-01-20 20:09:46 +07:00
|
|
|
{row.original.uploadDate ? (
|
|
|
|
|
<>
|
|
|
|
|
{formatDate(row.original.uploadDate)}
|
|
|
|
|
<br />
|
|
|
|
|
{formatDateTime(row.original.uploadDate)}
|
2026-02-21 15:07:37 +07:00
|
|
|
<br />
|
2026-01-20 20:09:46 +07:00
|
|
|
</>
|
|
|
|
|
) : (
|
|
|
|
|
<div>-</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
enableColumnFilter: false,
|
|
|
|
|
},
|
2025-12-26 14:03:57 +07:00
|
|
|
{
|
2026-03-06 14:53:51 +07:00
|
|
|
accessorKey: 'monitoring',
|
2025-12-26 14:03:57 +07:00
|
|
|
header: ({ column }) => (
|
2025-12-30 13:35:20 +07:00
|
|
|
<div className="column">
|
2025-12-26 14:03:57 +07:00
|
|
|
<span>
|
2026-02-20 14:55:23 +07:00
|
|
|
{t('monitoring-status')}
|
2025-12-26 14:03:57 +07:00
|
|
|
</span>
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
2025-12-30 13:35:20 +07:00
|
|
|
className="sort-button"
|
2025-12-26 14:03:57 +07:00
|
|
|
>
|
|
|
|
|
{
|
|
|
|
|
column.getIsSorted() === 'asc' ?
|
|
|
|
|
<span>
|
|
|
|
|
<IconArrowUp />
|
|
|
|
|
</span>
|
|
|
|
|
: column.getIsSorted() === 'desc' ?
|
|
|
|
|
<span>
|
|
|
|
|
<IconArrowDown />
|
|
|
|
|
</span>
|
|
|
|
|
: <span>
|
|
|
|
|
<IconFilter />
|
|
|
|
|
</span>
|
|
|
|
|
}
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
),
|
|
|
|
|
cell: ({ row }) => {
|
|
|
|
|
return (
|
2026-01-21 15:05:02 +07:00
|
|
|
<div className="text-center font-semibold table-item">
|
2026-03-09 14:38:52 +07:00
|
|
|
{row.original.fileType === 'image' ? (
|
|
|
|
|
<MonitoringDropDown file={row.original} />
|
|
|
|
|
) : (
|
2026-03-14 11:52:13 +07:00
|
|
|
<div
|
|
|
|
|
className="table-item-not-supported"
|
|
|
|
|
>
|
2026-03-09 14:38:52 +07:00
|
|
|
{t('monitoring-is-not-supported')}
|
2026-03-14 11:52:13 +07:00
|
|
|
<br />
|
|
|
|
|
{t('for-this-file-type')}
|
2026-03-09 14:38:52 +07:00
|
|
|
</div>
|
|
|
|
|
)}
|
2025-12-26 14:03:57 +07:00
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
},
|
2026-05-05 13:07:52 +07:00
|
|
|
{
|
|
|
|
|
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>
|
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
},
|
2025-12-12 14:22:00 +07:00
|
|
|
{
|
2025-12-12 20:28:59 +07:00
|
|
|
id: 'actions',
|
|
|
|
|
header: () => {
|
|
|
|
|
return (
|
2025-12-30 13:35:20 +07:00
|
|
|
<div className="column">
|
2025-12-12 20:28:59 +07:00
|
|
|
{t('actions')}
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
cell: ({ row }) => (
|
2026-01-04 13:27:59 +07:00
|
|
|
<div className="actions">
|
2026-05-05 13:07:52 +07:00
|
|
|
{(showFileLink && row.original.fileType === 'document') ? (
|
|
|
|
|
<div className="actions-group">
|
2026-04-27 17:29:16 +07:00
|
|
|
<Link
|
|
|
|
|
href={`/watch-doc?docid=${row.original.id}`}
|
|
|
|
|
className="bg-indigo-500 hover:bg-indigo-600"
|
|
|
|
|
title={t('view')}
|
|
|
|
|
target='_blank'
|
|
|
|
|
>
|
|
|
|
|
<IconLink />
|
|
|
|
|
</Link>
|
2026-05-05 13:07:52 +07:00
|
|
|
</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>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
2025-12-12 20:28:59 +07:00
|
|
|
</div>
|
|
|
|
|
),
|
|
|
|
|
enableSorting: false,
|
|
|
|
|
enableColumnFilter: false,
|
2025-12-12 14:22:00 +07:00
|
|
|
},
|
|
|
|
|
],
|
2026-04-01 13:34:50 +07:00
|
|
|
[viewport.device, selectedFiles, selectAllHandler]
|
2025-12-12 14:22:00 +07:00
|
|
|
);
|
|
|
|
|
|
2026-01-28 18:53:08 +07:00
|
|
|
const handleView = async (file: FileItem) => {
|
|
|
|
|
const fileInfo = await viewFileInfo(file.id);
|
|
|
|
|
|
|
|
|
|
setOpenWindowChildren(() => {
|
|
|
|
|
return (
|
|
|
|
|
<FileInfoModalWindow fileInfo={fileInfo} setWindowClose={setOpenWindow} setWindowChildren={setOpenWindowChildren} />
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
setOpenWindow(true);
|
2025-12-12 20:28:59 +07:00
|
|
|
};
|
|
|
|
|
|
2026-01-02 12:52:01 +07:00
|
|
|
const handleDownload = async (file: FileItem) => {
|
2026-03-09 13:04:28 +07:00
|
|
|
setIsFileLoading(true)
|
2026-01-02 13:00:37 +07:00
|
|
|
|
2026-01-02 12:52:01 +07:00
|
|
|
try {
|
2026-03-09 13:04:28 +07:00
|
|
|
const result = await downloadFile(
|
|
|
|
|
file.id,
|
|
|
|
|
file._original?.fileName || file.fileName || `file-${file.id}`
|
|
|
|
|
);
|
2026-01-02 12:52:01 +07:00
|
|
|
|
2026-03-09 13:04:28 +07:00
|
|
|
const byteCharacters = atob(result.data);
|
|
|
|
|
const byteNumbers = new Array(byteCharacters.length);
|
|
|
|
|
for (let i = 0; i < byteCharacters.length; i++) {
|
|
|
|
|
byteNumbers[i] = byteCharacters.charCodeAt(i)
|
2026-01-02 12:52:01 +07:00
|
|
|
}
|
|
|
|
|
|
2026-03-09 13:04:28 +07:00
|
|
|
const byteArray = new Uint8Array(byteNumbers);
|
|
|
|
|
const blob = new Blob([byteArray], { type: result.contentType });
|
|
|
|
|
|
2026-01-02 12:52:01 +07:00
|
|
|
const url = window.URL.createObjectURL(blob);
|
|
|
|
|
const a = document.createElement('a');
|
|
|
|
|
a.href = url;
|
2026-03-09 13:04:28 +07:00
|
|
|
a.download = result.fileName;
|
2026-01-02 12:52:01 +07:00
|
|
|
document.body.appendChild(a);
|
|
|
|
|
a.click();
|
|
|
|
|
window.URL.revokeObjectURL(url);
|
|
|
|
|
document.body.removeChild(a);
|
2026-01-02 13:00:37 +07:00
|
|
|
toast.success(`${t('file-is-downloading')} - ${file.fileName}`);
|
2026-01-02 12:52:01 +07:00
|
|
|
} catch (error) {
|
2026-03-09 13:04:28 +07:00
|
|
|
toast.error(t('failed-to-download-file'));
|
2026-01-02 12:52:01 +07:00
|
|
|
} finally {
|
|
|
|
|
setIsFileLoading(false);
|
|
|
|
|
}
|
2026-03-09 13:04:28 +07:00
|
|
|
}
|
2025-12-12 20:28:59 +07:00
|
|
|
|
2026-05-05 13:07:52 +07:00
|
|
|
const changeDownloadPermition = async (fileId: string, permission: boolean) => {
|
|
|
|
|
const response = await filePermisionChange(fileId, !permission);
|
|
|
|
|
if (response) {
|
|
|
|
|
queryClient.invalidateQueries({ queryKey: ['userFilesData'] });
|
|
|
|
|
toast.success('Разрешение на скачивание изменено.');
|
|
|
|
|
} else {
|
|
|
|
|
toast.warning('Не удалось изменить разрешение на скачивание.');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-12 20:28:59 +07:00
|
|
|
// Фильтрация по типу файла и дате
|
|
|
|
|
const filteredData = useMemo(() => {
|
2025-12-26 14:03:57 +07:00
|
|
|
let result = tableData;
|
2025-12-29 14:00:02 +07:00
|
|
|
if (!result) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
2025-12-12 20:28:59 +07:00
|
|
|
|
|
|
|
|
// Фильтр по типу файла
|
|
|
|
|
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);
|
2025-12-26 14:03:57 +07:00
|
|
|
result = result.filter(item => {
|
2026-01-20 20:09:46 +07:00
|
|
|
if (item.uploadDate) {
|
|
|
|
|
return item.uploadDate >= todayStart
|
2025-12-26 14:03:57 +07:00
|
|
|
} else {
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
});
|
2025-12-12 20:28:59 +07:00
|
|
|
break;
|
|
|
|
|
case 'week':
|
|
|
|
|
const weekAgo = now - sevenDays;
|
2025-12-26 14:03:57 +07:00
|
|
|
result = result.filter(item => {
|
2026-01-20 20:09:46 +07:00
|
|
|
if (item.uploadDate) {
|
|
|
|
|
return item.uploadDate >= weekAgo;
|
2025-12-26 14:03:57 +07:00
|
|
|
}
|
|
|
|
|
});
|
2025-12-12 20:28:59 +07:00
|
|
|
break;
|
|
|
|
|
case 'month':
|
|
|
|
|
const monthAgo = now - thirtyDays;
|
2025-12-26 14:03:57 +07:00
|
|
|
result = result.filter(item => {
|
2026-01-20 20:09:46 +07:00
|
|
|
if (item.uploadDate) {
|
|
|
|
|
return item.uploadDate >= monthAgo;
|
2025-12-26 14:03:57 +07:00
|
|
|
}
|
|
|
|
|
});
|
2025-12-12 20:28:59 +07:00
|
|
|
break;
|
|
|
|
|
case 'older':
|
|
|
|
|
const monthAgo2 = now - thirtyDays;
|
2025-12-26 14:03:57 +07:00
|
|
|
result = result.filter(item => {
|
2026-01-20 20:09:46 +07:00
|
|
|
if (item.uploadDate) {
|
|
|
|
|
return item.uploadDate < monthAgo2;
|
2025-12-26 14:03:57 +07:00
|
|
|
}
|
|
|
|
|
});
|
2025-12-12 20:28:59 +07:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
2026-03-30 12:48:31 +07:00
|
|
|
}, [/* query, */ typeFilter, dateFilter]);
|
2025-12-12 20:28:59 +07:00
|
|
|
|
2025-12-26 16:10:42 +07:00
|
|
|
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]);
|
|
|
|
|
|
2025-12-12 14:22:00 +07:00
|
|
|
// Создание таблицы
|
|
|
|
|
const table = useReactTable({
|
2026-03-20 19:45:18 +07:00
|
|
|
data: tableData,
|
2025-12-12 14:22:00 +07:00
|
|
|
columns,
|
|
|
|
|
state: {
|
|
|
|
|
sorting,
|
2026-03-20 19:45:18 +07:00
|
|
|
pagination,
|
2026-05-05 13:07:52 +07:00
|
|
|
columnVisibility: {
|
|
|
|
|
monitoring: showFileLink ? false : true,
|
|
|
|
|
downloadPermision: showFileLink ? true : false,
|
|
|
|
|
}
|
2025-12-12 14:22:00 +07:00
|
|
|
},
|
2026-03-20 19:45:18 +07:00
|
|
|
pageCount: totalPages,
|
|
|
|
|
manualPagination: true,
|
|
|
|
|
manualSorting: true,
|
|
|
|
|
manualFiltering: true,
|
2025-12-26 16:10:42 +07:00
|
|
|
onPaginationChange: setPagination,
|
2026-03-20 19:45:18 +07:00
|
|
|
onSortingChange: (updater) => {
|
|
|
|
|
const newSorting = typeof updater === 'function' ? updater(sorting) : updater;
|
|
|
|
|
setSorting(newSorting);
|
|
|
|
|
setPagination(prev => ({ ...prev, pageIndex: 0 }));
|
|
|
|
|
},
|
2025-12-12 14:22:00 +07:00
|
|
|
getCoreRowModel: getCoreRowModel(),
|
|
|
|
|
getSortedRowModel: getSortedRowModel(),
|
|
|
|
|
getPaginationRowModel: getPaginationRowModel(),
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-20 19:45:18 +07:00
|
|
|
const handleDateFilterChange = (newDateFilter: string) => {
|
|
|
|
|
setDateFilter(newDateFilter);
|
|
|
|
|
setPagination(prev => ({ ...prev, pageIndex: 0 }));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleTypeFilterChange = (newTypeFilter: string) => {
|
|
|
|
|
setTypeFilter(newTypeFilter);
|
|
|
|
|
setPagination(prev => ({ ...prev, pageIndex: 0 }));
|
|
|
|
|
};
|
|
|
|
|
|
2026-01-14 15:01:30 +07:00
|
|
|
const pluralizeFiles = (number: number) => {
|
|
|
|
|
const translate = [t('file'), t('files-few'), t('files')];
|
2026-01-15 12:27:39 +07:00
|
|
|
return pluralize(number, translate[0], translate[1], translate[2], locale);
|
2026-01-14 15:01:30 +07:00
|
|
|
};
|
|
|
|
|
|
2026-03-20 19:45:18 +07:00
|
|
|
useEffect(() => {
|
|
|
|
|
if (tableData.length === 0 && pagination.pageIndex > 0 && totalPages > 0) {
|
|
|
|
|
setPagination(prev => ({ ...prev, pageIndex: Math.max(0, totalPages - 1) }));
|
|
|
|
|
}
|
|
|
|
|
}, [tableData, pagination.pageIndex, totalPages]);
|
|
|
|
|
|
2025-12-12 14:22:00 +07:00
|
|
|
return (
|
2025-12-30 13:35:20 +07:00
|
|
|
<div className="tanstak-table-wrapper">
|
2026-01-21 15:05:02 +07:00
|
|
|
<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')
|
2026-04-28 16:32:23 +07:00
|
|
|
case 'document':
|
|
|
|
|
return t('protected-document')
|
2026-01-21 15:05:02 +07:00
|
|
|
default:
|
|
|
|
|
return t('table-description')
|
|
|
|
|
}
|
|
|
|
|
})()}
|
|
|
|
|
</h3>
|
2026-02-18 18:22:11 +07:00
|
|
|
<ModalWindow state={openWindow} callBack={setOpenWindow}>
|
|
|
|
|
{openWindowChildren}
|
|
|
|
|
</ModalWindow>
|
2025-12-12 20:28:59 +07:00
|
|
|
{/* Фильтры */}
|
2026-02-17 14:43:26 +07:00
|
|
|
<div className="tanstak-table-filtres">
|
2025-12-30 13:35:20 +07:00
|
|
|
<div className="table-filtres-wrapper">
|
|
|
|
|
<div className="table-filtres-item">
|
|
|
|
|
<div className="table-filtres-label">{t('date-filter')}:</div>
|
2025-12-12 20:28:59 +07:00
|
|
|
<DropDownList
|
2025-12-19 13:05:50 +07:00
|
|
|
value={dateFilter}
|
|
|
|
|
translatedValue={(() => {
|
2025-12-15 11:24:07 +07:00
|
|
|
switch (dateFilter) {
|
|
|
|
|
case 'all':
|
2026-03-20 19:45:18 +07:00
|
|
|
return t('all-dates');
|
|
|
|
|
case 'today':
|
|
|
|
|
return t('today');
|
2025-12-15 11:24:07 +07:00
|
|
|
case 'week':
|
2026-03-20 19:45:18 +07:00
|
|
|
return t('for-a-week');
|
2025-12-15 11:24:07 +07:00
|
|
|
case 'month':
|
2026-03-20 19:45:18 +07:00
|
|
|
return t('for-a-month');
|
2025-12-15 11:24:07 +07:00
|
|
|
default:
|
2026-03-20 19:45:18 +07:00
|
|
|
return t('all-dates');
|
2025-12-15 11:24:07 +07:00
|
|
|
}
|
|
|
|
|
})()}
|
2026-03-20 19:45:18 +07:00
|
|
|
callBack={handleDateFilterChange}
|
2025-12-12 20:28:59 +07:00
|
|
|
>
|
2026-03-20 19:45:18 +07:00
|
|
|
<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>
|
2025-12-12 20:28:59 +07:00
|
|
|
</DropDownList>
|
|
|
|
|
</div>
|
2025-12-12 14:22:00 +07:00
|
|
|
|
2025-12-29 16:08:38 +07:00
|
|
|
{fileType === "all" && (
|
2025-12-30 13:35:20 +07:00
|
|
|
<div className="table-filtres-item">
|
|
|
|
|
<div className="table-filtres-label">{t('type-filter')}:</div>
|
2025-12-29 16:08:38 +07:00
|
|
|
<DropDownList
|
|
|
|
|
value={typeFilter}
|
2026-03-20 19:45:18 +07:00
|
|
|
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}
|
2025-12-29 16:08:38 +07:00
|
|
|
>
|
2026-03-20 19:45:18 +07:00
|
|
|
<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>
|
2026-03-20 20:42:52 +07:00
|
|
|
<li value="document">{t('documents')}</li>
|
2025-12-29 16:08:38 +07:00
|
|
|
</DropDownList>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
2026-03-23 17:00:00 +07:00
|
|
|
</div>
|
|
|
|
|
<div className="table-filtres-wrapper text-filter-search">
|
2026-03-20 19:45:18 +07:00
|
|
|
<div className="table-filtres-item">
|
|
|
|
|
<div className="table-filtres-label">{t('search')}:</div>
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
className="table-filtres-text-filter"
|
2026-03-20 20:07:50 +07:00
|
|
|
value={searchInputValue}
|
|
|
|
|
onChange={handleSearchInputChange}
|
2026-03-20 19:45:18 +07:00
|
|
|
placeholder={t('search')}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
2026-02-20 15:13:52 +07:00
|
|
|
<div className="table-filtres-item">
|
|
|
|
|
<div className="table-filtres-label">{t('items-per-page')}:</div>
|
|
|
|
|
<DropDownList
|
|
|
|
|
value={table.getState().pagination.pageSize}
|
2026-03-20 19:45:18 +07:00
|
|
|
callBack={(newSize: string) => {
|
|
|
|
|
table.setPageSize(Number(newSize));
|
|
|
|
|
setPagination(prev => ({ ...prev, pageIndex: 0 }));
|
|
|
|
|
}}
|
2026-02-20 15:13:52 +07:00
|
|
|
>
|
2026-03-20 19:45:18 +07:00
|
|
|
{['5', '10', '20', '50', '100'].map(pageSize => (
|
2026-02-20 15:13:52 +07:00
|
|
|
<li key={pageSize} value={pageSize}>
|
|
|
|
|
{t('show')} {pageSize}
|
|
|
|
|
</li>
|
|
|
|
|
))}
|
|
|
|
|
</DropDownList>
|
|
|
|
|
</div>
|
2025-12-12 20:28:59 +07:00
|
|
|
</div>
|
2025-12-12 14:22:00 +07:00
|
|
|
</div>
|
|
|
|
|
|
2026-03-20 19:45:18 +07:00
|
|
|
{/* {isLoading && (
|
|
|
|
|
<div className="loading-indicator">
|
|
|
|
|
loading...
|
|
|
|
|
</div>
|
|
|
|
|
)} */}
|
|
|
|
|
|
2025-12-12 14:22:00 +07:00
|
|
|
{/* Таблица */}
|
2025-12-30 13:35:20 +07:00
|
|
|
<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>
|
2026-04-17 14:36:04 +07:00
|
|
|
<tbody className={`tanstak-table-body ${isFetching ? 'loading' : ''}`}>
|
2026-04-29 15:15:04 +07:00
|
|
|
{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>
|
|
|
|
|
)}
|
|
|
|
|
|
2025-12-30 13:35:20 +07:00
|
|
|
{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>
|
2025-12-12 20:28:59 +07:00
|
|
|
))}
|
|
|
|
|
</tr>
|
2025-12-30 13:35:20 +07:00
|
|
|
))
|
|
|
|
|
) : (
|
|
|
|
|
<tr>
|
2026-04-29 15:15:04 +07:00
|
|
|
{!isLoading && (
|
|
|
|
|
<td colSpan={columns.length} className="text-center">
|
|
|
|
|
{t('no-data-for-selected-filters')}
|
|
|
|
|
</td>
|
|
|
|
|
)}
|
2025-12-30 13:35:20 +07:00
|
|
|
</tr>
|
|
|
|
|
)}
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
2025-12-12 14:22:00 +07:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Пагинация */}
|
2025-12-30 13:35:20 +07:00
|
|
|
<div className="tanstak-table-pagination">
|
|
|
|
|
<div className="pagination-info">
|
|
|
|
|
<span className="pagination-info-pages">
|
2025-12-12 20:28:59 +07:00
|
|
|
{t('page')}{' '}
|
|
|
|
|
<strong>
|
2026-03-20 19:45:18 +07:00
|
|
|
{pagination.pageIndex + 1} {t('out-of')} {totalPages || 1}
|
2025-12-12 20:28:59 +07:00
|
|
|
</strong>
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-12-30 13:35:20 +07:00
|
|
|
<div className="pagination-controls">
|
2026-03-20 19:45:18 +07:00
|
|
|
<button
|
|
|
|
|
className="arrow"
|
|
|
|
|
onClick={() => table.firstPage()}
|
|
|
|
|
disabled={!table.getCanPreviousPage() || isLoading}
|
|
|
|
|
>
|
|
|
|
|
<IconDoubleArrowLeft />
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
className="arrow"
|
|
|
|
|
onClick={() => table.previousPage()}
|
|
|
|
|
disabled={!table.getCanPreviousPage() || isLoading}
|
|
|
|
|
>
|
|
|
|
|
<IconArrowLeft />
|
|
|
|
|
</button>
|
2025-12-12 20:28:59 +07:00
|
|
|
|
2025-12-30 13:35:20 +07:00
|
|
|
<div className="pagination-controls-pages">
|
2026-03-20 19:45:18 +07:00
|
|
|
{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;
|
|
|
|
|
}
|
2025-12-12 20:28:59 +07:00
|
|
|
|
2026-03-20 19:45:18 +07:00
|
|
|
if (pageIndex >= 0 && pageIndex < totalPages) {
|
2025-12-12 20:28:59 +07:00
|
|
|
return (
|
|
|
|
|
<button
|
|
|
|
|
key={pageIndex}
|
2026-03-20 19:45:18 +07:00
|
|
|
className={pagination.pageIndex === pageIndex ? 'current' : 'other'}
|
2025-12-12 20:28:59 +07:00
|
|
|
onClick={() => table.setPageIndex(pageIndex)}
|
2026-03-20 19:45:18 +07:00
|
|
|
disabled={isLoading}
|
2025-12-12 20:28:59 +07:00
|
|
|
>
|
|
|
|
|
{pageIndex + 1}
|
|
|
|
|
</button>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
})}
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-03-20 19:45:18 +07:00
|
|
|
<button
|
|
|
|
|
className="arrow"
|
|
|
|
|
onClick={() => table.nextPage()}
|
|
|
|
|
disabled={!table.getCanNextPage() || isLoading}
|
|
|
|
|
>
|
|
|
|
|
<IconArrowRight />
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
className="arrow"
|
|
|
|
|
onClick={() => table.lastPage()}
|
|
|
|
|
disabled={!table.getCanNextPage() || isLoading}
|
|
|
|
|
>
|
|
|
|
|
<IconDoubleArrowRight />
|
|
|
|
|
</button>
|
2025-12-12 14:22:00 +07:00
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-03-30 12:48:31 +07:00
|
|
|
<SelectedFilesAction filesId={selectedFiles} callBack={setSelectedFiles} />
|
2026-01-21 15:05:02 +07:00
|
|
|
</div >
|
2025-12-12 14:22:00 +07:00
|
|
|
);
|
|
|
|
|
}
|