diff --git a/src/app/[locale]/pages/dashboard/page.tsx b/src/app/[locale]/pages/dashboard/page.tsx index ea2a835..9cf3bbe 100644 --- a/src/app/[locale]/pages/dashboard/page.tsx +++ b/src/app/[locale]/pages/dashboard/page.tsx @@ -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 DahboardGeographySection from '@/app/ui/dashboard/new/dashboard-geography-section'; 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'; export const metadata: Metadata = { diff --git a/src/app/actions/violationActions.ts b/src/app/actions/violationActions.ts index ffc0f22..5488ce0 100644 --- a/src/app/actions/violationActions.ts +++ b/src/app/actions/violationActions.ts @@ -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'); + console.log('getViolationFilesArray'); + const { page, size, start_date, end_date, file_name } = props; try { + const body: Record = { + token: token + }; - const response = await fetch(`${API_BASE_URL}/api/v1/global-search/violation-summary`, { - method: 'GET', + if (page !== undefined) body.page = page; + 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: { 'Content-Type': 'application/json', 'Accept': 'application/json', - 'Authorization': `Bearer ${token}`, - } + }, + body: JSON.stringify(body), }); if (response.ok) { let parsed = await response.json(); - if (parsed.length) { + if (parsed.content.length) { return parsed; } else { throw parsed; @@ -310,6 +327,7 @@ export async function createComplaint( formData: FormData ): Promise { const email = await getSessionData('email'); + const token = await getSessionData('token'); const violationId = formData.get('violationId') as string || ''; const textArea = formData.get('note') as string || ''; @@ -345,6 +363,7 @@ export async function createComplaint( version: 1, msg_id: 30013, message_body: { + token: token, action: 'create', violation_id: violationId, text: textArea, diff --git a/src/app/components/tanstak-table/TanstakTable.tsx b/src/app/components/tanstak-table/TanstakTable.tsx index 694f0d0..72bec21 100644 --- a/src/app/components/tanstak-table/TanstakTable.tsx +++ b/src/app/components/tanstak-table/TanstakTable.tsx @@ -912,6 +912,21 @@ export default function TanstakFilesTable({ fileType, showFileLink }: { fileType ))} + {isLoading && ( + + +
+
+
+
+ + + )} + {table.getRowModel().rows.length > 0 ? ( table.getRowModel().rows.map(row => ( @@ -926,9 +941,11 @@ export default function TanstakFilesTable({ fileType, showFileLink }: { fileType )) ) : ( - - {t('no-data-for-selected-filters')} - + {!isLoading && ( + + {t('no-data-for-selected-filters')} + + )} )} diff --git a/src/app/ui/dashboard/new/dashboard-user-violations.tsx b/src/app/ui/dashboard/new/dashboard-user-violations.tsx index b2f9931..96bd17c 100644 --- a/src/app/ui/dashboard/new/dashboard-user-violations.tsx +++ b/src/app/ui/dashboard/new/dashboard-user-violations.tsx @@ -11,7 +11,7 @@ import Link from 'next/link'; import { getViolationFilesArray } from '@/app/actions/violationActions'; import { IconEye } from '@/app/ui/icons/icons'; -interface ViolationFile { +interface FileViolation { createdAt: string; fileId: string; fileName: string; @@ -23,20 +23,26 @@ interface ViolationFile { violationCount: number; } +interface ViolationData { + content: FileViolation[]; +} + export default function DashboardUserViolations() { const { data: violationData, isLoading, isError, error, - } = useQuery({ - queryKey: ['violationData'], - queryFn: () => getViolationFilesArray(), + } = useQuery({ + queryKey: ['violationData', { page: 0, size: 5 }], + queryFn: () => getViolationFilesArray({ + page: 0, + size: 5, + }), select: (data) => { - return data?.slice(0, 5) || []; - }, - /* refetchInterval: 30000 */ + return data; + } }); const t = useTranslations('Global'); @@ -76,8 +82,8 @@ export default function DashboardUserViolations() { )} - {violationData?.length ? ( - violationData?.map((file) => { + {violationData?.content.length ? ( + violationData?.content.map((file) => { return (
({ - queryKey: ['violationData'], - queryFn: () => getViolationFilesArray(), - - select: (data) => { - return data; - }, - /* refetchInterval: 30000 */ - }); - const paginationRef = useRef(null); - const t = useTranslations('Global'); - - useEffect(() => { - console.log(violationData); - }, [violationData]) - - return ( -
-
-

{t('violation')}

- - Смотреть все - -
-
- {t('are-no-violations')} -
-
- - {/* {violationData?.map(item => { - return ( - -
- -
-
- ) - })} */} -{/* -
-
- Preview -
НАРУШЕНИЕ
-
ОБРАБОТАНО
-
- -
-
-
- 🖼️banner111.png
-
- Фото -
-
- -
-
-
- 🌍 Регион -
-
- 🇺🇸 - США -
-
- -
-
📅 Обнаружено
-
- 17.10.2025
-
- -
-
🌐 Домен
-
- m.apkpure.com
-
- -
-
📊 Категория
-
- Единичное -
-
- -
-
📁 Тип
-
- Изображение
-
- -
-
⚡ Статус
-
- ✅ Обработано
-
-
- -
- - -
-
-
-
*/} -
-
-
-
-
-
-
- ) -} \ No newline at end of file diff --git a/src/app/ui/violations/violations-table.tsx b/src/app/ui/violations/violations-table.tsx index 2bc84a1..4ea8deb 100644 --- a/src/app/ui/violations/violations-table.tsx +++ b/src/app/ui/violations/violations-table.tsx @@ -1,6 +1,6 @@ '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 { useTranslations } from 'next-intl'; 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 { formatDate, formatDateTime } from '@/app/lib/formatDate'; import Link from 'next/link'; +import DropDownList from '@/app/components/DropDownList'; +import { useDebounce } from 'use-debounce'; -interface ViolationFile { +interface FileViolation { createdAt: string; fileId: string; fileName: string; @@ -22,32 +24,103 @@ interface ViolationFile { violationCount: number; } +interface ViolationData { + content: FileViolation[]; + totalPages: number; + totalElements: number; + size: number; + number: number; +} + export default function ViolationsTable() { - const { - data: violationData, - isLoading, - isError, - error, - } = useQuery({ - queryKey: ['violationData'], - queryFn: () => getViolationFilesArray(), - - select: (data) => { - return data; - }, - /* refetchInterval: 30000 */ - }); const t = useTranslations('Global'); + const viewport = useViewport(); - // Состояния - const [sorting, setSorting] = useState([]); - const [columnFilters, setColumnFilters] = useState([]); + const [fileNameFilter, setFileNameFilter] = useState(''); + const [dateFilter, setDateFilter] = useState('all'); const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10, }); + const [sorting, setSorting] = useState([]); + 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({ + 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 => { if (viewport.device === 'MOBILE') return 26; if (viewport.device === 'TABLET') return 32; @@ -73,115 +146,83 @@ export default function ViolationsTable() { const maxNameLength = MAX_FILE_NAME_LENGTH - extension.length - 3; if (maxNameLength <= 0) { - return '...' /* + extension */; + return '...'; } - return nameWithoutExtension.substring(0, maxNameLength) + '...' /* + extension */; - } + return nameWithoutExtension.substring(0, maxNameLength) + '...'; + }; - const columns = useMemo[]>( + const columns = useMemo[]>( () => [ { accessorKey: 'supportId', header: ({ column }) => (
- - ID - + ID
), - cell: ({ row }) => { - return ( -
-
- {row.original.supportId} -
-
- ) - } + cell: ({ row }) => ( +
+ {row.original.supportId} +
+ ), }, { accessorKey: 'fileName', header: ({ column }) => (
- - {t('file')} - + {t('file')}
), - cell: ({ row }) => { - return ( -
-
- {cutFileName(row.original.fileName)} -
-
- ) - } + cell: ({ row }) => ( +
+ {cutFileName(row.original.fileName)} +
+ ), }, { accessorKey: 'violationCount', header: ({ column }) => (
- - {t('matches')} - + {t('matches')}
), @@ -195,26 +236,18 @@ export default function ViolationsTable() { accessorKey: 'latestViolationDate', header: ({ column }) => (
- - {t('date')} - + {t('date')}
), @@ -228,93 +261,74 @@ export default function ViolationsTable() { }, { accessorKey: 'actions', - header: ({ column }) => ( + header: () => (
- - {t('actions')} - + {t('actions')}
), - cell: ({ row }) => { - return ( -
-
( +
+
+ - {/* */} - - - -
+ +
- ) - } +
+ ), }, ], - [] - ) - - const filteredData = useMemo(() => { - let result = violationData; - if (!result) { - return []; - } - - return result - }, [violationData]) + [t, viewport] + ); const table = useReactTable({ - data: filteredData, + data: violationData?.content || [], columns, state: { sorting, - columnFilters, - pagination + pagination, }, + pageCount: violationData?.totalPages || -1, autoResetPageIndex: false, + manualPagination: true, // Пагинация с бека + manualSorting: false, // сортировка локально так как эндпоинт не поддерживает сортировку. + manualFiltering: true, // Фильтрация с бека onPaginationChange: setPagination, onSortingChange: setSorting, - onColumnFiltersChange: setColumnFilters, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), getPaginationRowModel: getPaginationRowModel(), - getFilteredRowModel: getFilteredRowModel(), - initialState: { - pagination: { - pageSize: 10, - }, - }, }); + const handleFileNameChange = (e: React.ChangeEvent) => { + setFileNameFilter(e.target.value); + setPagination(prev => ({ ...prev, pageIndex: 0 })); + }; + + const handleDateFilterChange = (value: string) => { + setDateFilter(value); + setPagination(prev => ({ ...prev, pageIndex: 0 })); + }; + + if (isError) { + return
Error: {error?.message || t('error')}
; + } + return ( -
+
-
-

+
+

{t('matches-detected')}

{/* Фильтры */} - {/*
+
+ {/* Фильтр по дате */}
{t('date-filter')}:
{ switch (dateFilter) { case 'all': - return t('all-dates') + return t('all-dates'); case 'week': - return t('for-a-week') + return t('for-a-week'); case 'month': - return t('for-a-month') + return t('for-a-month'); case 'older': - return t('older-than-a-month') + return t('older-than-a-month'); default: - return t('today') + return t('today'); } })()} - 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')}
  • +
  • {t('older-than-a-month')}
  • + {/* Фильтр по имени файла */} +
    +
    {t('file-name')}:
    + +
    -
    +
    {t('items-per-page')}:
    { + table.setPageSize(value); + setPagination(prev => ({ ...prev, pageSize: value, pageIndex: 0 })); + }} > {[5, 10, 20, 50, 100].map(pageSize => (
  • @@ -373,8 +389,7 @@ export default function ViolationsTable() {
  • - -
    */} +
    {/* Таблица */}
    @@ -383,20 +398,32 @@ export default function ViolationsTable() { {table.getHeaderGroups().map(headerGroup => ( {headerGroup.headers.map(header => ( - + {header.isPlaceholder ? null : typeof header.column.columnDef.header === 'function' ? header.column.columnDef.header(header.getContext()) - : header.column.columnDef.header as string} + : (header.column.columnDef.header as string)} ))} ))} - + + {isLoading && ( + + +
    +
    +
    +
    + + + )} {table.getRowModel().rows.length > 0 ? ( table.getRowModel().rows.map(row => ( @@ -404,16 +431,18 @@ export default function ViolationsTable() { {typeof cell.column.columnDef.cell === 'function' ? cell.column.columnDef.cell(cell.getContext()) - : cell.getValue() as string} + : (cell.getValue() as string)} ))} )) ) : ( - - {t('no-data-for-selected-filters')} - + {!isLoading && ( + + {t('no-data-for-selected-filters')} + + )} )} @@ -426,52 +455,51 @@ export default function ViolationsTable() { {t('page')}{' '} - {table.getState().pagination.pageIndex + 1} {t('out-of')} {table.getPageCount() ? table.getPageCount() : 1} + {table.getState().pagination.pageIndex + 1} {t('out-of')} {violationData?.totalPages || 1} - | {t('shown')} {table.getRowModel().rows.length} {t('out-of')} {filteredData.length} + | {t('shown')} {violationData?.content?.length || 0} {t('out-of')} {violationData?.totalElements || 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; + {Array.from({ length: Math.min(5, violationData?.totalPages || 0) }, (_, i) => { + const totalPages = violationData?.totalPages || 0; + const currentPage = table.getState().pagination.pageIndex; + let pageIndex = 0; - 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 (
    - {table.getCanNextPage() && ( - - )} - {table.getCanNextPage() && ( - - )} + +

    - ) + ); } \ No newline at end of file