From 9d90c81f644c6ca0ab943cb0beba731d4c28cf27 Mon Sep 17 00:00:00 2001 From: smanylov Date: Fri, 20 Mar 2026 19:45:18 +0700 Subject: [PATCH] update tanstack-table --- src/app/actions/fileEntity.ts | 36 +- .../components/tanstak-table/TanstakTable.tsx | 466 +++++++++--------- src/app/styles/pages-styles.scss | 23 + 3 files changed, 290 insertions(+), 235 deletions(-) diff --git a/src/app/actions/fileEntity.ts b/src/app/actions/fileEntity.ts index 3c5f387..310d88a 100644 --- a/src/app/actions/fileEntity.ts +++ b/src/app/actions/fileEntity.ts @@ -4,8 +4,25 @@ import { getSessionData } from '@/app/actions/session'; import { API_BASE_URL } from '@/app/actions/definitions'; -export async function getUserFilesData(page: number, pageSize: number) { +export async function getUserFilesData({ + page, + pageSize, + sortBy, + sortOrder, + type, + date, + query = '' +}: { + page: number; + pageSize: number; + sortBy?: string; + sortOrder?: 'asc' | 'desc' | string; + type?: string; + date?: string; + query?: string; +}) { const token = await getSessionData('token'); + /* console.log('getUserFilesData', { page, pageSize, sortBy, sortOrder, type, date, query }); */ try { const response = await fetch(`${API_BASE_URL}/api/v1/data`, { @@ -14,10 +31,15 @@ export async function getUserFilesData(page: number, pageSize: number) { version: 1, msg_id: 20005, message_body: { - action: 'user_files', + action: 'search_files', + token: token, page: page, page_size: pageSize, - token: token + query: query, + sort_by: sortBy || '', // protectStatus, fileName, fileSize, createdAt, updatedAt, supportId + type: type || '', // image, video, document, audio + date: date || '', // today, week, month + sort_order: sortOrder } }), headers: { @@ -28,17 +50,19 @@ export async function getUserFilesData(page: number, pageSize: number) { if (response.ok) { let parsed = await response.json(); + console.log(parsed); if (parsed.message_code === 0) { return parsed.message_body; } else { - throw parsed; + throw new Error(parsed.message_text || 'API error'); } } else { - throw (`${response.status}`); + throw new Error(`${response.status}`); } } catch (error) { - return error + console.error('getUserFilesData error:', error); + return error; } } diff --git a/src/app/components/tanstak-table/TanstakTable.tsx b/src/app/components/tanstak-table/TanstakTable.tsx index 44d6e13..374fee4 100644 --- a/src/app/components/tanstak-table/TanstakTable.tsx +++ b/src/app/components/tanstak-table/TanstakTable.tsx @@ -10,8 +10,9 @@ import { ColumnDef, SortingState, ColumnFiltersState, + PaginationState } from '@tanstack/react-table'; -import { IconEye, IconDoubleArrowRight, IconArrowRight, IconDoubleArrowLeft, IconArrowLeft, IconArrowUp, IconArrowDown, IconFilter, IconFileDownload, IconShieldExclamation, IconInfo } from '@/app/ui/icons/icons'; +import { IconDoubleArrowRight, IconArrowRight, IconDoubleArrowLeft, IconArrowLeft, IconArrowUp, IconArrowDown, IconFilter, IconFileDownload, IconInfo } from '@/app/ui/icons/icons'; import { useTranslations, useLocale } from 'next-intl'; import DropDownList from '@/app/components/DropDownList'; import { useQuery, useQueryClient } from '@tanstack/react-query'; @@ -27,12 +28,8 @@ 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 { API_BASE_URL } from '@/app/actions/definitions'; -import { getSessionData } from '@/app/actions/session'; - -import { downloadFile } from '@/app/actions/fileEntity' - -type FileType = 'image' | 'video' | 'audio'; +import { downloadFile } from '@/app/actions/fileEntity'; +import { useDebouncedCallback } from 'use-debounce'; export type FileItem = { id: string; @@ -64,6 +61,9 @@ type ApiFile = { type ApiResponse = { files?: ApiFile[]; + total_count: number; + page: number; + page_size: number; }; const cutFileExtension = (fileName: string) => { @@ -73,37 +73,107 @@ const cutFileExtension = (fileName: string) => { } export default function TanstakFilesTable({ fileType }: { fileType: string }) { + + const [sorting, setSorting] = useState([]); + const [columnFilters, setColumnFilters] = useState([]); + const [dateFilter, setDateFilter] = useState('month'); + const [typeFilter, setTypeFilter] = useState(fileType); + const [pagination, setPagination] = useState({ + pageIndex: 0, + pageSize: 10, + }); + const [query, setQuery] = useState(''); + + + const getSortParams = useMemo(() => { + if (sorting.length === 0) { + return { sortBy: '', sortOrder: undefined }; + } + + const sort = sorting[0]; + const sortByMap: Record = { + '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: tableData, + data: apiResponse, isLoading, isError, error, - } = useQuery({ - queryKey: ['userFilesData', 1, 10000], - queryFn: () => getUserFilesData(1, 10000), + refetch + } = useQuery({ + queryKey: [ + 'userFilesData', + pagination.pageIndex, + pagination.pageSize, + getSortParams.sortBy, + getSortParams.sortOrder, + getTypeParam, + getDateParam, + query + ], + queryFn: () => getUserFilesData({ + page: pagination.pageIndex + 1, + pageSize: pagination.pageSize, + sortBy: getSortParams.sortBy, + sortOrder: getSortParams?.sortOrder, + type: getTypeParam, + date: getDateParam, + query: query + }), + 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 }; + } - select: (data: ApiResponse): FileItem[] => { - if (!data?.files) return []; + 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 + })); - return data.files.map((item: ApiFile) => { - const newDate = new Date(item.updatedAt).getTime(); - - return { - id: item.id, - fileName: item.originalFileName, - fileType: item.mimeType.toLocaleLowerCase(), - violations: item.violations, - size: item.fileSize, - uploadDate: newDate, - status: item.status, - monitoring: item.monitoring, - protectStatus: item.protectStatus, - supportId: item.supportId, - _original: item - }; - }); + return { + items, + total_count: data.total_count || data.files.length, // предполагаем, что сервер возвращает total + page: data.page || pagination.pageIndex + 1, + pageSize: data.page_size || pagination.pageSize + }; }, - refetchInterval: 30000 + refetchInterval: 30000, + placeholderData: (previousData) => previousData // для оптимистичных обновлений }); const { @@ -124,15 +194,12 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) { 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 tableData = apiResponse?.items || []; + const totalItems = apiResponse?.total_count || 0; + const totalPages = Math.ceil(totalItems / pagination.pageSize); + + const [openWindow, setOpenWindow] = useState(false); const [openWindowChildren, setOpenWindowChildren] = useState(null); @@ -256,48 +323,6 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) { ), 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 }) => ( @@ -420,12 +445,6 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) { ) }, - /* sortingFn: (rowA, rowB) => { - console.log(rowA.original.monitoring, rowB.original.monitoring) - const monitoringA = rowA.original.monitoring; - const monitoringB = rowA.original.monitoring; - return monitoringA - monitoringB; - }, */ }, { id: 'actions', @@ -439,22 +458,6 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) { cell: ({ row }) => (
- {/* */} - {/* */}
{row.original.protectStatus === 'PROTECTED' && ( @@ -603,7 +606,7 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) { } return result; - }, [tableData, typeFilter, dateFilter]); + }, [query, typeFilter, dateFilter]); useEffect(() => { const currentPageRows = table.getRowModel().rows; @@ -616,33 +619,53 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) { // Создание таблицы const table = useReactTable({ - data: filteredData, + data: tableData, columns, state: { sorting, - columnFilters, - pagination + pagination, }, - autoResetPageIndex: false, + pageCount: totalPages, + manualPagination: true, + manualSorting: true, + manualFiltering: true, onPaginationChange: setPagination, - onSortingChange: setSorting, - onColumnFiltersChange: setColumnFilters, + onSortingChange: (updater) => { + const newSorting = typeof updater === 'function' ? updater(sorting) : updater; + setSorting(newSorting); + setPagination(prev => ({ ...prev, pageIndex: 0 })); + }, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), getPaginationRowModel: getPaginationRowModel(), - getFilteredRowModel: getFilteredRowModel(), - initialState: { - pagination: { - pageSize: 10, - }, - }, }); + const handleDateFilterChange = (newDateFilter: string) => { + setDateFilter(newDateFilter); + setPagination(prev => ({ ...prev, pageIndex: 0 })); + }; + + const handleTypeFilterChange = (newTypeFilter: string) => { + setTypeFilter(newTypeFilter); + setPagination(prev => ({ ...prev, pageIndex: 0 })); + }; + + const handleQueryChange = (newQuery: string) => { + setQuery(newQuery); + 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 (

{ switch (dateFilter) { case 'all': - return t('all-dates') + return t('all-dates'); + case 'today': + return t('today'); case 'week': - return t('for-a-week') + return t('for-a-week'); case 'month': - return t('for-a-month') - case 'older': - return t('older-than-a-month') + return t('for-a-month'); default: - return t('today') + return t('all-dates'); } })()} - callBack={setDateFilter} + callBack={handleDateFilterChange} > -
  • - {t('all-dates')} -
  • -
  • - {t('today')} -
  • -
  • - {t('for-a-week')} -
  • -
  • - {t('for-a-month')} -
  • -
  • - {t('older-than-a-month')} -
  • +
  • {t('all-dates')}
  • +
  • {t('today')}
  • +
  • {t('for-a-week')}
  • +
  • {t('for-a-month')}
  • @@ -710,57 +722,55 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) {
    {t('type-filter')}:
    { - 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={setTypeFilter} + 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} > -
  • - {t('all-types')} -
  • -
  • - {t('images-few')} -
  • -
  • - {t('videos')} -
  • -
  • - {t('audios')} -
  • +
  • {t('all-types')}
  • +
  • {t('images-few')}
  • +
  • {t('videos')}
  • +
  • {t('audios')}
  • {!referralLink && ( -
  • - {t('documents')} -
  • +
  • {t('documents')}
  • )}
    )} +
    +
    {t('search')}:
    + handleQueryChange(e.target.value)} + placeholder={t('search')} + /> +
    -
    +
    {t('items-per-page')}:
    { + table.setPageSize(Number(newSize)); + setPagination(prev => ({ ...prev, pageIndex: 0 })); + }} > - {[5, 10, 20, 50, 100].map(pageSize => ( + {['5', '10', '20', '50', '100'].map(pageSize => (
  • {t('show')} {pageSize}
  • @@ -768,9 +778,14 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) {
    -
    + {/* {isLoading && ( +
    + loading... +
    + )} */} + {/* Таблица */}
    @@ -821,53 +836,50 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) { {t('page')}{' '} - {table.getState().pagination.pageIndex + 1} {t('out-of')} {table.getPageCount() ? table.getPageCount() : 1} + {pagination.pageIndex + 1} {t('out-of')} {totalPages || 1} - | {t('shown')} {table.getRowModel().rows.length} {t('out-of')} {filteredData.length} {pluralizeFiles(filteredData.length || 0)} + | {t('shown')} {tableData.length} {t('out-of')} {totalItems} {pluralizeFiles(totalItems)}
    - {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; + {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 < table.getPageCount()) { + if (pageIndex >= 0 && pageIndex < totalPages) { return ( @@ -877,24 +889,20 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) { })}
    - {table.getCanNextPage() && ( - - )} - {table.getCanNextPage() && ( - - )} + +
    diff --git a/src/app/styles/pages-styles.scss b/src/app/styles/pages-styles.scss index 72effba..994c3d4 100644 --- a/src/app/styles/pages-styles.scss +++ b/src/app/styles/pages-styles.scss @@ -624,6 +624,29 @@ font-weight: 500; margin-bottom: 0.25rem; } + + + .table-filtres-text-filter { + background-color: #fff; + border: 2px solid #e2e8f0; + border-radius: 12px; + justify-content: space-between; + align-items: center; + width: 100%; + padding: 8px 16px; + font-size: 14px; + font-weight: 500; + line-height: 1.25; + transition: background-color .2s ease-in-out; + display: flex; + box-shadow: 0 1px 2px #0000000d; + + &:focus { + outline-offset: 2px; + outline: 2px solid #0000; + box-shadow: 0 0 0 2px #6365f187; + } + } } }