rework violation table and violation actions

This commit is contained in:
smanylov
2026-04-29 15:15:04 +07:00
parent c0fe6cce48
commit c274cb134d
6 changed files with 341 additions and 449 deletions
@@ -10,7 +10,6 @@ import DashboardFilesInfo from '@/app/ui/dashboard/new/dashboard-files-info';
import DahboardLimitsSection from '@/app/ui/dashboard/new/dashborad-limits-section'; import DahboardLimitsSection from '@/app/ui/dashboard/new/dashborad-limits-section';
import DahboardGeographySection from '@/app/ui/dashboard/new/dashboard-geography-section'; import DahboardGeographySection from '@/app/ui/dashboard/new/dashboard-geography-section';
import DashboardLastCheck from '@/app/ui/dashboard/new/dashboard-last-check'; import DashboardLastCheck from '@/app/ui/dashboard/new/dashboard-last-check';
import ViolationsCarousel from '@/app/ui/dashboard/new/violations-carousel';
import DashboardUserViolations from '@/app/ui/dashboard/new/dashboard-user-violations'; import DashboardUserViolations from '@/app/ui/dashboard/new/dashboard-user-violations';
export const metadata: Metadata = { export const metadata: Metadata = {
+25 -6
View File
@@ -80,24 +80,41 @@ export async function startGlobalMonitoring(monitoringType: 'monitoring' | 'all_
} }
} }
export async function getViolationFilesArray() { export async function getViolationFilesArray(props: {
page?: number,
size?: number,
start_date?: string,
end_date?: string,
file_name?: string
}) {
const token = await getSessionData('token'); const token = await getSessionData('token');
console.log('getViolationFilesArray');
const { page, size, start_date, end_date, file_name } = props;
try { try {
const body: Record<string, any> = {
token: token
};
const response = await fetch(`${API_BASE_URL}/api/v1/global-search/violation-summary`, { if (page !== undefined) body.page = page;
method: 'GET', if (size !== undefined) body.size = size;
if (start_date !== undefined) body.start_date = start_date;
if (end_date !== undefined) body.end_date = end_date;
if (file_name !== undefined) body.file_name = file_name;
const response = await fetch(`${API_BASE_URL}/api/v1/global-search/violation-summary-with-files`, {
method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Accept': 'application/json', 'Accept': 'application/json',
'Authorization': `Bearer ${token}`, },
} body: JSON.stringify(body),
}); });
if (response.ok) { if (response.ok) {
let parsed = await response.json(); let parsed = await response.json();
if (parsed.length) { if (parsed.content.length) {
return parsed; return parsed;
} else { } else {
throw parsed; throw parsed;
@@ -310,6 +327,7 @@ export async function createComplaint(
formData: FormData formData: FormData
): Promise<complainBody> { ): Promise<complainBody> {
const email = await getSessionData('email'); const email = await getSessionData('email');
const token = await getSessionData('token');
const violationId = formData.get('violationId') as string || ''; const violationId = formData.get('violationId') as string || '';
const textArea = formData.get('note') as string || ''; const textArea = formData.get('note') as string || '';
@@ -345,6 +363,7 @@ export async function createComplaint(
version: 1, version: 1,
msg_id: 30013, msg_id: 30013,
message_body: { message_body: {
token: token,
action: 'create', action: 'create',
violation_id: violationId, violation_id: violationId,
text: textArea, text: textArea,
@@ -912,6 +912,21 @@ export default function TanstakFilesTable({ fileType, showFileLink }: { fileType
))} ))}
</thead> </thead>
<tbody className={`tanstak-table-body ${isFetching ? 'loading' : ''}`}> <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.length > 0 ? (
table.getRowModel().rows.map(row => ( table.getRowModel().rows.map(row => (
<tr key={row.id}> <tr key={row.id}>
@@ -926,9 +941,11 @@ export default function TanstakFilesTable({ fileType, showFileLink }: { fileType
)) ))
) : ( ) : (
<tr> <tr>
{!isLoading && (
<td colSpan={columns.length} className="text-center"> <td colSpan={columns.length} className="text-center">
{t('no-data-for-selected-filters')} {t('no-data-for-selected-filters')}
</td> </td>
)}
</tr> </tr>
)} )}
</tbody> </tbody>
@@ -11,7 +11,7 @@ import Link from 'next/link';
import { getViolationFilesArray } from '@/app/actions/violationActions'; import { getViolationFilesArray } from '@/app/actions/violationActions';
import { IconEye } from '@/app/ui/icons/icons'; import { IconEye } from '@/app/ui/icons/icons';
interface ViolationFile { interface FileViolation {
createdAt: string; createdAt: string;
fileId: string; fileId: string;
fileName: string; fileName: string;
@@ -23,20 +23,26 @@ interface ViolationFile {
violationCount: number; violationCount: number;
} }
interface ViolationData {
content: FileViolation[];
}
export default function DashboardUserViolations() { export default function DashboardUserViolations() {
const { const {
data: violationData, data: violationData,
isLoading, isLoading,
isError, isError,
error, error,
} = useQuery<ViolationFile[]>({ } = useQuery<ViolationData>({
queryKey: ['violationData'], queryKey: ['violationData', { page: 0, size: 5 }],
queryFn: () => getViolationFilesArray(), queryFn: () => getViolationFilesArray({
page: 0,
size: 5,
}),
select: (data) => { select: (data) => {
return data?.slice(0, 5) || []; return data;
}, }
/* refetchInterval: 30000 */
}); });
const t = useTranslations('Global'); const t = useTranslations('Global');
@@ -76,8 +82,8 @@ export default function DashboardUserViolations() {
</div> </div>
)} )}
{violationData?.length ? ( {violationData?.content.length ? (
violationData?.map((file) => { violationData?.content.map((file) => {
return ( return (
<div <div
key={file.fileId} key={file.fileId}
@@ -1,173 +0,0 @@
'use client'
import { Swiper, SwiperSlide } from 'swiper/react';
import { Pagination, Autoplay } from 'swiper/modules';
import Image from 'next/image';
import 'swiper/css';
import 'swiper/css/pagination';
import testImage from '@/app/src/image-preview.png'
import { useEffect, useRef } from 'react';
import { useTranslations } from 'next-intl';
import Link from 'next/link';
import { useQuery } from '@tanstack/react-query';
import { getViolationFilesArray } from '@/app/actions/violationActions';
interface ViolationFile {
createdAt: string;
fileId: string;
fileName: string;
fileSize: number;
latestViolationDate: string;
mimeType: string;
status: string;
supportId: number;
violationCount: number;
}
export default function ViolationsCarousel() {
const {
data: violationData,
isLoading,
isError,
error,
} = useQuery<ViolationFile[]>({
queryKey: ['violationData'],
queryFn: () => getViolationFilesArray(),
select: (data) => {
return data;
},
/* refetchInterval: 30000 */
});
const paginationRef = useRef(null);
const t = useTranslations('Global');
useEffect(() => {
console.log(violationData);
}, [violationData])
return (
<div className="content-section">
<div className="section-header">
<h3 className="section-title">{t('violation')}</h3>
<Link
href="violations"
className="view-all-link"
>
Смотреть все
</Link>
</div>
<div>
{t('are-no-violations')}
</div>
<div className="violations-carousel">
<Swiper
spaceBetween={50}
slidesPerView={1}
modules={[Pagination, Autoplay]}
pagination={{
clickable: true,
el: '.custon-swiper-pagination'
}}
loop={true}
autoplay={{
delay: 3000,
disableOnInteraction: false,
}}
>
{/* {violationData?.map(item => {
return (
<SwiperSlide>
<div
className="violation-slide"
>
</div>
</SwiperSlide>
)
})} */}
{/* <SwiperSlide>
<div className="violation-slide">
<div className="violation-image-container">
<Image src={testImage} alt="Preview" className="violation-image" />
<div className="violation-badge">НАРУШЕНИЕ</div>
<div className="violation-status-badge">ОБРАБОТАНО</div>
</div>
<div className="violation-content">
<div className="violation-title-row">
<div className="violation-filename">
🖼️banner111.png</div>
<div className="violation-type-badge photo">
Фото
</div>
</div>
<div className="violation-info-grid">
<div className="violation-info-item">
<div className="violation-info-label">
🌍 Регион
</div>
<div className="violation-info-value">
<span className="region-flag">🇺🇸</span>
<span>США</span>
</div>
</div>
<div className="violation-info-item">
<div className="violation-info-label">📅 Обнаружено</div>
<div className="violation-info-value">
17.10.2025</div>
</div>
<div className="violation-info-item">
<div className="violation-info-label">🌐 Домен</div>
<div className="violation-info-value">
m.apkpure.com </div>
</div>
<div className="violation-info-item">
<div className="violation-info-label">📊 Категория</div>
<div className="violation-info-value">
Единичное
</div>
</div>
<div className="violation-info-item">
<div className="violation-info-label">📁 Тип</div>
<div className="violation-info-value">
Изображение</div>
</div>
<div className="violation-info-item">
<div className="violation-info-label">⚡ Статус</div>
<div className="violation-info-value">
✅ Обработано</div>
</div>
</div>
<div className="violation-actions-row">
<button className="btn-visit-site">
🔗 Перейти на сайт
</button>
<button className="violation-action">
🚨 Открыть дело
</button>
</div>
</div>
</div>
</SwiperSlide> */}
</Swiper>
<div
className="flex justify-center mt-6"
>
<div
ref={paginationRef}
className="flex justify-center gap-2 custon-swiper-pagination"
>
</div>
</div>
</div>
</div>
)
}
+221 -197
View File
@@ -1,6 +1,6 @@
'use client' 'use client'
import { useMemo, useState } from 'react'; import { useMemo, useState, useEffect } from 'react';
import { useReactTable, getCoreRowModel, getSortedRowModel, getPaginationRowModel, getFilteredRowModel, ColumnDef, SortingState, ColumnFiltersState } from '@tanstack/react-table'; import { useReactTable, getCoreRowModel, getSortedRowModel, getPaginationRowModel, getFilteredRowModel, ColumnDef, SortingState, ColumnFiltersState } from '@tanstack/react-table';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { IconArrowUp, IconArrowDown, IconFilter, IconDoubleArrowRight, IconArrowRight, IconDoubleArrowLeft, IconArrowLeft, IconEye } from '@/app/ui/icons/icons'; import { IconArrowUp, IconArrowDown, IconFilter, IconDoubleArrowRight, IconArrowRight, IconDoubleArrowLeft, IconArrowLeft, IconEye } from '@/app/ui/icons/icons';
@@ -9,8 +9,10 @@ import { getViolationFilesArray } from '@/app/actions/violationActions';
import { useViewport } from '@/app/hooks/useViewport'; import { useViewport } from '@/app/hooks/useViewport';
import { formatDate, formatDateTime } from '@/app/lib/formatDate'; import { formatDate, formatDateTime } from '@/app/lib/formatDate';
import Link from 'next/link'; import Link from 'next/link';
import DropDownList from '@/app/components/DropDownList';
import { useDebounce } from 'use-debounce';
interface ViolationFile { interface FileViolation {
createdAt: string; createdAt: string;
fileId: string; fileId: string;
fileName: string; fileName: string;
@@ -22,32 +24,103 @@ interface ViolationFile {
violationCount: number; violationCount: number;
} }
interface ViolationData {
content: FileViolation[];
totalPages: number;
totalElements: number;
size: number;
number: number;
}
export default function ViolationsTable() { export default function ViolationsTable() {
const {
data: violationData,
isLoading,
isError,
error,
} = useQuery<ViolationFile[]>({
queryKey: ['violationData'],
queryFn: () => getViolationFilesArray(),
select: (data) => {
return data;
},
/* refetchInterval: 30000 */
});
const t = useTranslations('Global'); const t = useTranslations('Global');
const viewport = useViewport();
// Состояния const [fileNameFilter, setFileNameFilter] = useState<string>('');
const [sorting, setSorting] = useState<SortingState>([]); const [dateFilter, setDateFilter] = useState<string>('all');
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
const [pagination, setPagination] = useState({ const [pagination, setPagination] = useState({
pageIndex: 0, pageIndex: 0,
pageSize: 10, pageSize: 10,
}); });
const [sorting, setSorting] = useState<SortingState>([]);
const [debouncedFileName] = useDebounce(fileNameFilter, 500);
const getDateRange = (filter: string): { start_date?: string; end_date?: string } => {
const now = new Date();
const end_date = formatDateTimeForAPI(now);
switch (filter) {
case 'today': {
const start_date = formatDateTimeForAPI(new Date(now.setHours(0, 0, 0, 0)));
return { start_date, end_date };
}
case 'week': {
const weekAgo = new Date(now);
weekAgo.setDate(weekAgo.getDate() - 7);
weekAgo.setHours(0, 0, 0, 0);
return { start_date: formatDateTimeForAPI(weekAgo), end_date };
}
case 'month': {
const monthAgo = new Date(now);
monthAgo.setMonth(monthAgo.getMonth() - 1);
monthAgo.setHours(0, 0, 0, 0);
return { start_date: formatDateTimeForAPI(monthAgo), end_date };
}
case 'older': {
const monthAgo = new Date(now);
monthAgo.setMonth(monthAgo.getMonth() - 1);
monthAgo.setHours(0, 0, 0, 0);
return { end_date: formatDateTimeForAPI(monthAgo) };
}
default:
return {};
}
};
const formatDateTimeForAPI = (date: Date): string => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
};
const dateRange = getDateRange(dateFilter);
const {
data: violationData,
isLoading,
isFetching,
isError,
error,
refetch,
} = useQuery<ViolationData>({
queryKey: ['violationData', pagination.pageIndex, pagination.pageSize, debouncedFileName, dateFilter],
queryFn: () => getViolationFilesArray({
page: pagination.pageIndex,
size: pagination.pageSize,
file_name: debouncedFileName || undefined,
start_date: dateRange.start_date,
end_date: dateRange.end_date,
}),
select: (data) => {
console.log(data);
return data;
},
placeholderData: (previousData) => previousData,
});
useEffect(() => {
if (violationData) {
setPagination(prev => ({
...prev,
pageIndex: violationData.number || 0,
}));
}
}, [violationData]);
const viewport = useViewport();
const getMaxLengthByWidth = (): number => { const getMaxLengthByWidth = (): number => {
if (viewport.device === 'MOBILE') return 26; if (viewport.device === 'MOBILE') return 26;
if (viewport.device === 'TABLET') return 32; if (viewport.device === 'TABLET') return 32;
@@ -73,115 +146,83 @@ export default function ViolationsTable() {
const maxNameLength = MAX_FILE_NAME_LENGTH - extension.length - 3; const maxNameLength = MAX_FILE_NAME_LENGTH - extension.length - 3;
if (maxNameLength <= 0) { if (maxNameLength <= 0) {
return '...' /* + extension */; return '...';
} }
return nameWithoutExtension.substring(0, maxNameLength) + '...' /* + extension */; return nameWithoutExtension.substring(0, maxNameLength) + '...';
} };
const columns = useMemo<ColumnDef<ViolationFile>[]>( const columns = useMemo<ColumnDef<FileViolation>[]>(
() => [ () => [
{ {
accessorKey: 'supportId', accessorKey: 'supportId',
header: ({ column }) => ( header: ({ column }) => (
<div className="column"> <div className="column">
<span> <span>ID</span>
ID
</span>
<button <button
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')} onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
className="sort-button" className="sort-button"
> >
{ {column.getIsSorted() === 'asc' ? (
column.getIsSorted() === 'asc' ?
<span>
<IconArrowUp /> <IconArrowUp />
</span> ) : column.getIsSorted() === 'desc' ? (
: column.getIsSorted() === 'desc' ?
<span>
<IconArrowDown /> <IconArrowDown />
</span> ) : (
: <span>
<IconFilter /> <IconFilter />
</span> )}
}
</button> </button>
</div> </div>
), ),
cell: ({ row }) => { cell: ({ row }) => (
return (
<div>
<div className="text-center table-item table-item-id"> <div className="text-center table-item table-item-id">
{row.original.supportId} {row.original.supportId}
</div> </div>
</div> ),
)
}
}, },
{ {
accessorKey: 'fileName', accessorKey: 'fileName',
header: ({ column }) => ( header: ({ column }) => (
<div className="column start"> <div className="column start">
<span> <span>{t('file')}</span>
{t('file')}
</span>
<button <button
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')} onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
className="sort-button" className="sort-button"
> >
{ {column.getIsSorted() === 'asc' ? (
column.getIsSorted() === 'asc' ?
<span>
<IconArrowUp /> <IconArrowUp />
</span> ) : column.getIsSorted() === 'desc' ? (
: column.getIsSorted() === 'desc' ?
<span>
<IconArrowDown /> <IconArrowDown />
</span> ) : (
: <span>
<IconFilter /> <IconFilter />
</span> )}
}
</button> </button>
</div> </div>
), ),
cell: ({ row }) => { cell: ({ row }) => (
return (
<div>
<div <div
className="flex items-center space-x-2 table-item table-item-file-name" className="flex items-center space-x-2 table-item table-item-file-name"
title={row.original.fileName} title={row.original.fileName}
> >
{cutFileName(row.original.fileName)} {cutFileName(row.original.fileName)}
</div> </div>
</div> ),
)
}
}, },
{ {
accessorKey: 'violationCount', accessorKey: 'violationCount',
header: ({ column }) => ( header: ({ column }) => (
<div className="column"> <div className="column">
<span> <span>{t('matches')}</span>
{t('matches')}
</span>
<button <button
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')} onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
className="sort-button" className="sort-button"
> >
{ {column.getIsSorted() === 'asc' ? (
column.getIsSorted() === 'asc' ?
<span>
<IconArrowUp /> <IconArrowUp />
</span> ) : column.getIsSorted() === 'desc' ? (
: column.getIsSorted() === 'desc' ?
<span>
<IconArrowDown /> <IconArrowDown />
</span> ) : (
: <span>
<IconFilter /> <IconFilter />
</span> )}
}
</button> </button>
</div> </div>
), ),
@@ -195,26 +236,18 @@ export default function ViolationsTable() {
accessorKey: 'latestViolationDate', accessorKey: 'latestViolationDate',
header: ({ column }) => ( header: ({ column }) => (
<div className="column"> <div className="column">
<span> <span>{t('date')}</span>
{t('date')}
</span>
<button <button
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')} onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
className="sort-button" className="sort-button"
> >
{ {column.getIsSorted() === 'asc' ? (
column.getIsSorted() === 'asc' ?
<span>
<IconArrowUp /> <IconArrowUp />
</span> ) : column.getIsSorted() === 'desc' ? (
: column.getIsSorted() === 'desc' ?
<span>
<IconArrowDown /> <IconArrowDown />
</span> ) : (
: <span>
<IconFilter /> <IconFilter />
</span> )}
}
</button> </button>
</div> </div>
), ),
@@ -228,28 +261,14 @@ export default function ViolationsTable() {
}, },
{ {
accessorKey: 'actions', accessorKey: 'actions',
header: ({ column }) => ( header: () => (
<div className="column"> <div className="column">
<span> <span>{t('actions')}</span>
{t('actions')}
</span>
</div> </div>
), ),
cell: ({ row }) => { cell: ({ row }) => (
return (
<div className="actions"> <div className="actions">
<div <div className="actions-group">
className="actions-group"
>
{/* <button
onClick={() => {
console.log(row.original.fileId);
}}
className="bg-violet-500 hover:bg-violet-600"
title={t('view')}
>
<IconEye />
</button> */}
<Link <Link
href={`/pages/file/${row.original.fileId}`} href={`/pages/file/${row.original.fileId}`}
className="bg-violet-500 hover:bg-violet-600" className="bg-violet-500 hover:bg-violet-600"
@@ -259,62 +278,57 @@ export default function ViolationsTable() {
</Link> </Link>
</div> </div>
</div> </div>
) ),
}
}, },
], ],
[] [t, viewport]
) );
const filteredData = useMemo(() => {
let result = violationData;
if (!result) {
return [];
}
return result
}, [violationData])
const table = useReactTable({ const table = useReactTable({
data: filteredData, data: violationData?.content || [],
columns, columns,
state: { state: {
sorting, sorting,
columnFilters, pagination,
pagination
}, },
pageCount: violationData?.totalPages || -1,
autoResetPageIndex: false, autoResetPageIndex: false,
manualPagination: true, // Пагинация с бека
manualSorting: false, // сортировка локально так как эндпоинт не поддерживает сортировку.
manualFiltering: true, // Фильтрация с бека
onPaginationChange: setPagination, onPaginationChange: setPagination,
onSortingChange: setSorting, onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(), getSortedRowModel: getSortedRowModel(),
getPaginationRowModel: getPaginationRowModel(), getPaginationRowModel: getPaginationRowModel(),
getFilteredRowModel: getFilteredRowModel(),
initialState: {
pagination: {
pageSize: 10,
},
},
}); });
const handleFileNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setFileNameFilter(e.target.value);
setPagination(prev => ({ ...prev, pageIndex: 0 }));
};
const handleDateFilterChange = (value: string) => {
setDateFilter(value);
setPagination(prev => ({ ...prev, pageIndex: 0 }));
};
if (isError) {
return <div className="block-wrapper">Error: {error?.message || t('error')}</div>;
}
return ( return (
<div <div className="block-wrapper">
className="block-wrapper"
>
<div className="tab-content"> <div className="tab-content">
<div <div className="tanstak-table-wrapper">
className="tanstak-table-wrapper" <h3 className="tanstak-table-title">
>
<h3
className="tanstak-table-title"
>
{t('matches-detected')} {t('matches-detected')}
</h3> </h3>
{/* Фильтры */} {/* Фильтры */}
{/* <div className="tanstak-table-filtres"> <div className="tanstak-table-filtres">
<div className="table-filtres-wrapper"> <div className="table-filtres-wrapper">
{/* Фильтр по дате */}
<div className="table-filtres-item"> <div className="table-filtres-item">
<div className="table-filtres-label">{t('date-filter')}:</div> <div className="table-filtres-label">{t('date-filter')}:</div>
<DropDownList <DropDownList
@@ -322,48 +336,50 @@ export default function ViolationsTable() {
translatedValue={(() => { translatedValue={(() => {
switch (dateFilter) { switch (dateFilter) {
case 'all': case 'all':
return t('all-dates') return t('all-dates');
case 'week': case 'week':
return t('for-a-week') return t('for-a-week');
case 'month': case 'month':
return t('for-a-month') return t('for-a-month');
case 'older': case 'older':
return t('older-than-a-month') return t('older-than-a-month');
default: default:
return t('today') return t('today');
} }
})()} })()}
callBack={setDateFilter} callBack={handleDateFilterChange}
> >
<li value="all"> <li value="all">{t('all-dates')}</li>
{t('all-dates')} <li value="today">{t('today')}</li>
</li> <li value="week">{t('for-a-week')}</li>
<li value="today"> <li value="month">{t('for-a-month')}</li>
{t('today')} <li value="older">{t('older-than-a-month')}</li>
</li>
<li value="week">
{t('for-a-week')}
</li>
<li value="month">
{t('for-a-month')}
</li>
<li value="older">
{t('older-than-a-month')}
</li>
</DropDownList> </DropDownList>
</div> </div>
{/* Фильтр по имени файла */}
<div className="table-filtres-item">
<div className="table-filtres-label">{t('file-name')}:</div>
<input
type="text"
value={fileNameFilter}
onChange={handleFileNameChange}
placeholder={t('search')}
className="table-filtres-text-filter"
/>
</div>
</div> </div>
<div <div className="table-filtres-wrapper">
className="table-filtres-wrapper"
>
<div className="table-filtres-item"> <div className="table-filtres-item">
<div className="table-filtres-label">{t('items-per-page')}:</div> <div className="table-filtres-label">{t('items-per-page')}:</div>
<DropDownList <DropDownList
value={table.getState().pagination.pageSize} value={table.getState().pagination.pageSize}
//@ts-ignore сделал так потому что для table.setPageSize нужна цифра //@ts-ignore
callBack={table.setPageSize} callBack={(value: number) => {
table.setPageSize(value);
setPagination(prev => ({ ...prev, pageSize: value, pageIndex: 0 }));
}}
> >
{[5, 10, 20, 50, 100].map(pageSize => ( {[5, 10, 20, 50, 100].map(pageSize => (
<li key={pageSize} value={pageSize}> <li key={pageSize} value={pageSize}>
@@ -373,8 +389,7 @@ export default function ViolationsTable() {
</DropDownList> </DropDownList>
</div> </div>
</div> </div>
</div>
</div> */}
{/* Таблица */} {/* Таблица */}
<div className="tanstak-table-block"> <div className="tanstak-table-block">
@@ -383,20 +398,32 @@ export default function ViolationsTable() {
{table.getHeaderGroups().map(headerGroup => ( {table.getHeaderGroups().map(headerGroup => (
<tr key={headerGroup.id}> <tr key={headerGroup.id}>
{headerGroup.headers.map(header => ( {headerGroup.headers.map(header => (
<th <th key={header.id}>
key={header.id}
>
{header.isPlaceholder {header.isPlaceholder
? null ? null
: typeof header.column.columnDef.header === 'function' : typeof header.column.columnDef.header === 'function'
? header.column.columnDef.header(header.getContext()) ? header.column.columnDef.header(header.getContext())
: header.column.columnDef.header as string} : (header.column.columnDef.header as string)}
</th> </th>
))} ))}
</tr> </tr>
))} ))}
</thead> </thead>
<tbody className="tanstak-table-body"> <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.length > 0 ? (
table.getRowModel().rows.map(row => ( table.getRowModel().rows.map(row => (
<tr key={row.id}> <tr key={row.id}>
@@ -404,16 +431,18 @@ export default function ViolationsTable() {
<td key={cell.id}> <td key={cell.id}>
{typeof cell.column.columnDef.cell === 'function' {typeof cell.column.columnDef.cell === 'function'
? cell.column.columnDef.cell(cell.getContext()) ? cell.column.columnDef.cell(cell.getContext())
: cell.getValue() as string} : (cell.getValue() as string)}
</td> </td>
))} ))}
</tr> </tr>
)) ))
) : ( ) : (
<tr> <tr>
{!isLoading && (
<td colSpan={columns.length} className="text-center"> <td colSpan={columns.length} className="text-center">
{t('no-data-for-selected-filters')} {t('no-data-for-selected-filters')}
</td> </td>
)}
</tr> </tr>
)} )}
</tbody> </tbody>
@@ -426,16 +455,15 @@ export default function ViolationsTable() {
<span className="pagination-info-pages"> <span className="pagination-info-pages">
{t('page')}{' '} {t('page')}{' '}
<strong> <strong>
{table.getState().pagination.pageIndex + 1} {t('out-of')} {table.getPageCount() ? table.getPageCount() : 1} {table.getState().pagination.pageIndex + 1} {t('out-of')} {violationData?.totalPages || 1}
</strong> </strong>
</span> </span>
<span className="pagination-info-files"> <span className="pagination-info-files">
| {t('shown')} {table.getRowModel().rows.length} {t('out-of')} {filteredData.length} | {t('shown')} {violationData?.content?.length || 0} {t('out-of')} {violationData?.totalElements || 0}
</span> </span>
</div> </div>
<div className="pagination-controls"> <div className="pagination-controls">
{table.getCanPreviousPage() && (
<button <button
className="arrow" className="arrow"
onClick={() => table.firstPage()} onClick={() => table.firstPage()}
@@ -443,8 +471,6 @@ export default function ViolationsTable() {
> >
<IconDoubleArrowLeft /> <IconDoubleArrowLeft />
</button> </button>
)}
{table.getCanPreviousPage() && (
<button <button
className="arrow" className="arrow"
onClick={() => table.previousPage()} onClick={() => table.previousPage()}
@@ -452,26 +478,28 @@ export default function ViolationsTable() {
> >
<IconArrowLeft /> <IconArrowLeft />
</button> </button>
)}
<div className="pagination-controls-pages"> <div className="pagination-controls-pages">
{Array.from({ length: Math.min(5, table.getPageCount()) }, (_, i) => { {Array.from({ length: Math.min(5, violationData?.totalPages || 0) }, (_, i) => {
const pageIndex = Math.max( const totalPages = violationData?.totalPages || 0;
0, const currentPage = table.getState().pagination.pageIndex;
Math.min( let pageIndex = 0;
table.getPageCount() - 5,
table.getState().pagination.pageIndex - 2
)
) + i;
if (pageIndex < table.getPageCount()) { if (totalPages <= 5) {
pageIndex = i;
} else if (currentPage <= 2) {
pageIndex = i;
} else if (currentPage >= totalPages - 3) {
pageIndex = totalPages - 5 + i;
} else {
pageIndex = currentPage - 2 + i;
}
if (pageIndex < totalPages) {
return ( return (
<button <button
key={pageIndex} key={pageIndex}
className={`${table.getState().pagination.pageIndex === pageIndex className={table.getState().pagination.pageIndex === pageIndex ? 'current' : 'other'}
? 'current'
: 'other'
}`}
onClick={() => table.setPageIndex(pageIndex)} onClick={() => table.setPageIndex(pageIndex)}
> >
{pageIndex + 1} {pageIndex + 1}
@@ -482,7 +510,6 @@ export default function ViolationsTable() {
})} })}
</div> </div>
{table.getCanNextPage() && (
<button <button
className="arrow" className="arrow"
onClick={() => table.nextPage()} onClick={() => table.nextPage()}
@@ -490,8 +517,6 @@ export default function ViolationsTable() {
> >
<IconArrowRight /> <IconArrowRight />
</button> </button>
)}
{table.getCanNextPage() && (
<button <button
className="arrow" className="arrow"
onClick={() => table.lastPage()} onClick={() => table.lastPage()}
@@ -499,11 +524,10 @@ export default function ViolationsTable() {
> >
<IconDoubleArrowRight /> <IconDoubleArrowRight />
</button> </button>
)}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
) );
} }