'use client'; import { useState, useMemo, useEffect, ReactNode } from 'react'; import { useReactTable, getCoreRowModel, getSortedRowModel, getPaginationRowModel, getFilteredRowModel, ColumnDef, SortingState, ColumnFiltersState, } from '@tanstack/react-table'; import { IconImageFile, IconVideoFile, IconAudioFile, IconEye, IconDoubleArrowRight, IconArrowRight, IconDoubleArrowLeft, IconArrowLeft, IconArrowUp, IconArrowDown, IconFilter, IconFileDownload, IconShieldExclamation, IconDelete } 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 { getUserFilesData, removeUserFile } 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'; type FileType = 'image' | 'video' | 'audio'; type FileItem = { id: string; fileName: string; fileType: string; violations?: number | undefined; size?: number | undefined; uploadDate?: number; status?: string; _original?: ApiFile; }; type ApiFile = { id: string; originalFileName: string; mimeType: string; violations: number; fileSize: number; updatedAt: string; status: string; }; type ApiResponse = { files?: ApiFile[]; }; // Иконки для типов файлов const FileTypeIcon = ({ type }: { type: string }) => { switch (type) { case 'image': return ; case 'video': return ; case 'audio': return ; default: return 📄; } }; // Форматирование даты из 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 = (fileName: string) => { const MAX_FILE_NAME_LENGTH = 26; 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 */; } const cutFileExtension = (fileName: string) => { const lastDotIndex = fileName.lastIndexOf('.'); const extension = fileName.substring(lastDotIndex + 1); return extension; } export default function TanstakFilesTable({ fileType }: { fileType: string }) { const { data: tableData, isLoading, isError, error, } = useQuery({ queryKey: ['userFilesData', 1, 10000], queryFn: () => getUserFilesData(1, 10000), select: (data: ApiResponse): FileItem[] => { if (!data?.files) return []; return data.files.map((item: ApiFile) => { const [datePart, timePart] = item.updatedAt.split(' '); const [day, month, year] = datePart.split('-').map(Number); const [hours, minutes, seconds] = timePart.split(':').map(Number); const newDate = new Date(year, month - 1, day, hours, minutes, seconds).getTime(); return { id: item.id, fileName: item.originalFileName, fileType: item.mimeType.toLocaleLowerCase(), violations: item.violations, size: item.fileSize, uploadDate: newDate, status: item.status, _original: item }; }); }, }); const queryClient = useQueryClient(); // Состояния const [sorting, setSorting] = useState([]); const [columnFilters, setColumnFilters] = useState([]); const [dateFilter, setDateFilter] = useState('all'); const [typeFilter, setTypeFilter] = useState(fileType); const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10, }); const [openWindow, setOpenWindow] = useState(false); const [openWindowChildren, setOpenWindowChildren] = useState(null); const [isFileLoading, setIsFileLoading] = useState(false); const t = useTranslations("Global"); const locale = useLocale(); // Определение колонок const columns = useMemo[]>( () => [ { accessorKey: 'fileName', header: ({ column }) => (
{t('file')}
), cell: ({ row }) => (
{cutFileName(row.original.fileName)}
{cutFileExtension(row.original.fileName)} {row.original?.status}
), enableColumnFilter: false, }, /* { accessorKey: 'violations', header: ({ column }) => (
{t('violations')}
), cell: ({ row }) => { let classCollor = () => { let result = '' if (row.original.violations !== undefined) { result = row.original.violations > 0 ? 'text-red-600' : 'text-green-600' } return result; } return (
{row.original.violations !== undefined ? row.original.violations : '-'}
) }, }, */ { accessorKey: 'size', header: ({ column }) => (
{t('size')}
), cell: ({ row }) => (
{row.original.size !== undefined ? convertBytes(row.original.size) : '-'}
), }, { accessorKey: 'uploadDate', header: ({ column }) => (
{t('upload-date')}
), cell: ({ row }) => { return (
{row.original.uploadDate ? ( <> {formatDate(row.original.uploadDate)}
{formatDateTime(row.original.uploadDate)} ) : (
-
)}
) }, enableColumnFilter: false, }, { accessorKey: 'status', header: ({ column }) => (
{t('status')}
), cell: ({ row }) => { return (
{t('status')}
) }, }, { id: 'actions', header: () => { return (
{t('actions')}
) }, cell: ({ row }) => (
), enableSorting: false, enableColumnFilter: false, }, ], [] ); // Обработчики действий const handleView = (file: FileItem) => { console.log(`Просмотр файла: ${file.fileName}`); }; const handleDownload = async (file: FileItem) => { setIsFileLoading(true); try { const response = await fetch(`/api/download/${file.id}`); if (!response.ok) { throw new Error(`error: ${response.status}`); } const blob = await response.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = file.fileName || `file-${file.id}`; 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 handleProtect = (file: FileItem) => { console.log(`Щиток: ${file.fileName}`); }; const handleOpenWindowForRemove = async (file: FileItem) => { setOpenWindowChildren(() => { return (

{t('you-sure-you-want-to-delete')}

{file.fileName}
) }) setOpenWindow(true); }; const deleteMutation = useMutation({ mutationFn: removeUserFile, onMutate: async (fileId: string) => { await queryClient.cancelQueries({ queryKey: ['userFilesData'] }); queryClient.setQueryData(['userFilesData'], (old) => { if (!old?.files) return old; return { ...old, files: old.files.filter(file => file.id !== fileId) }; }); }, onError: () => { queryClient.invalidateQueries({ queryKey: ['userFilesData'] }); toast.error(t('error')); }, onSuccess: (response, fileId) => { if (response === fileId) { queryClient.invalidateQueries({ queryKey: ['userFilesData'], refetchType: 'active' }); queryClient.invalidateQueries({ queryKey: ['userFilesInfo'] }); setOpenWindow(false); setOpenWindowChildren(null); toast.success(t('file-has-been-deleted')); } }, }); // Фильтрация по типу файла и дате 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; }, [tableData, 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: filteredData, columns, state: { sorting, columnFilters, pagination }, autoResetPageIndex: false, onPaginationChange: setPagination, onSortingChange: setSorting, onColumnFiltersChange: setColumnFilters, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), getPaginationRowModel: getPaginationRowModel(), getFilteredRowModel: getFilteredRowModel(), initialState: { pagination: { pageSize: 10, }, }, }); const pluralizeFiles = (number: number) => { const translate = [t('file'), t('files-few'), t('files')]; return pluralize(number, translate[0], translate[1], translate[2], locale); }; return (

{(() => { switch (fileType) { case 'image': return t('protected-image') case 'video': return t('protected-video') case 'audio': return t('protected-audio') default: return t('table-description') } })()}

{/* Фильтры */}
{t('date-filter')}:
{ switch (dateFilter) { case 'all': return t('all-dates') case 'week': return t('for-a-week') case 'month': return t('for-a-month') case 'older': return t('older-than-a-month') default: return t('today') } })()} callBack={setDateFilter} >
  • {t('all-dates')}
  • {t('today')}
  • {t('for-a-week')}
  • {t('for-a-month')}
  • {t('older-than-a-month')}
  • {fileType === "all" && (
    {t('type-filter')}:
    { switch (typeFilter) { case 'image': return t('images') case 'video': return t('videos') case 'audio': return t('audios') default: return t('all-types') } })() } callBack={setTypeFilter} >
  • {t('all-types')}
  • {t('images-few')}
  • {t('videos')}
  • {t('audios')}
  • )}
    {t('items-per-page')}:
    {[5, 10, 20, 50, 100].map(pageSize => (
  • {t('show')} {pageSize}
  • ))}
    {/* Таблица */}
    {table.getHeaderGroups().map(headerGroup => ( {headerGroup.headers.map(header => ( ))} ))} {table.getRowModel().rows.length > 0 ? ( table.getRowModel().rows.map(row => ( {row.getVisibleCells().map(cell => ( ))} )) ) : ( )}
    {header.isPlaceholder ? null : typeof header.column.columnDef.header === 'function' ? header.column.columnDef.header(header.getContext()) : header.column.columnDef.header as string}
    {typeof cell.column.columnDef.cell === 'function' ? cell.column.columnDef.cell(cell.getContext()) : cell.getValue() as string}
    {t('no-data-for-selected-filters')}
    {/* Пагинация */}
    {t('page')}{' '} {table.getState().pagination.pageIndex + 1} {t('out-of')} {table.getPageCount() ? table.getPageCount() : 1} | {t('shown')} {table.getRowModel().rows.length} {t('out-of')} {filteredData.length} {pluralizeFiles(filteredData.length || 0)}
    {table.getCanPreviousPage() && ( )} {table.getCanPreviousPage() && ( )}
    {Array.from({ length: Math.min(5, table.getPageCount()) }, (_, i) => { const pageIndex = Math.max( 0, Math.min( table.getPageCount() - 5, table.getState().pagination.pageIndex - 2 ) ) + i; if (pageIndex < table.getPageCount()) { return ( ); } return null; })}
    {table.getCanNextPage() && ( )} {table.getCanNextPage() && ( )}
    ); }