diff --git a/package.json b/package.json index d6c179f..48efe4b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "no-copy-frontend", - "version": "0.61.0", + "version": "0.62.0", "private": true, "scripts": { "dev": "next dev -p 2999", diff --git a/src/app/[locale]/pages/violations/[id]/page.tsx b/src/app/[locale]/pages/violations/[id]/page.tsx index a1be5f4..98fe711 100644 --- a/src/app/[locale]/pages/violations/[id]/page.tsx +++ b/src/app/[locale]/pages/violations/[id]/page.tsx @@ -1,6 +1,6 @@ -'use client' +// app/files/[id]/page.tsx +'use server' -import { useParams } from 'next/navigation' import ViolationPageTitle from '@/app/ui/violations/file-page/violation-page-title'; import ViolationPageViolationInfo from '@/app/ui/violations/file-page/violation-page-violation-info'; import ViolationPpageFileStatistic from '@/app/ui/violations/file-page/violation-page-file-statistic'; @@ -8,23 +8,34 @@ import ViolationPageFileInfo from '@/app/ui/violations/file-page/violation-page- import ViolationPageActions from '@/app/ui/violations/file-page/violation-page-actions'; import ViolationPageViolationsList from '@/app/ui/violations/file-page/violation-page-violations-list'; import ViolationPageNote from '@/app/ui/violations/file-page/violation-page-note'; +import { getFileViolations } from '@/app/actions/violationActions'; -/* violation-details */ -export default function Page() { - const params = useParams() - const id = params.id +interface PageProps { + params: Promise<{ id: string }>; + searchParams: Promise<{ page?: string }>; // Добавляем searchParams +} + +export default async function Page({ + params, + searchParams +}: PageProps) { + const { id } = await params; + const { page } = await searchParams; + const currentPage = Number(page) || 1; + + const fileViolations = await getFileViolations(id, currentPage); return ( -
+
-
+
- +
@@ -33,7 +44,6 @@ export default function Page() {
-
) } \ No newline at end of file diff --git a/src/app/actions/violationActions.ts b/src/app/actions/violationActions.ts index e1780e8..9337843 100644 --- a/src/app/actions/violationActions.ts +++ b/src/app/actions/violationActions.ts @@ -80,4 +80,108 @@ export async function startGlobalMonitoring(monitoringType: 'monitoring' | 'all_ } catch (error) { return null } +} + +export async function getViolationFilesArray() { + const token = await getSessionData('token'); + + try { + + const response = await fetch(`${API_BASE_URL}/api/v1/global-search/violation-summary`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'Authorization': `Bearer ${token}`, + } + }); + + if (response.ok) { + let parsed = await response.json(); + if (parsed.length) { + return parsed; + } else { + throw parsed; + } + } else { + throw (`${response.status}`); + } + + } catch (error) { + return [] + } +} + +/* статистика нурешений */ +export async function fetchViolationStats() { + const token = await getSessionData('token'); + + try { + const response = await fetch(`${API_BASE_URL}/api/v1/data`, { + method: 'POST', + body: JSON.stringify({ + version: 1, + msg_id: 30010, + message_body: { + token: token + } + }), + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + } + }); + + if (response.ok) { + let parsed = await response.json(); + if (parsed) { + return parsed; + } else { + throw null; + } + } else { + throw (`${response.status}`); + } + } catch (error) { + return null + } +} + +export async function getFileViolations(fileId: string, page: number) { + console.log('getFileViolations'); + console.log(page); + + try { + const response = await fetch(`${API_BASE_URL}/api/v1/data`, { + method: 'POST', + body: JSON.stringify({ + version: 1, + msg_id: 30009, + message_body: { + file_id: fileId, + page: page, + size: 5, + sort_direction: "desc" + } + }), + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + } + }); + + if (response.ok) { + let parsed = await response.json(); + if (parsed.message_body) { + return parsed.message_body; + } else { + throw null; + } + } else { + throw (`${response.status}`); + } + + } catch (error) { + return null + } } \ No newline at end of file diff --git a/src/app/styles/pages-styles.scss b/src/app/styles/pages-styles.scss index eeb2c4f..41df816 100644 --- a/src/app/styles/pages-styles.scss +++ b/src/app/styles/pages-styles.scss @@ -789,15 +789,15 @@ } td { - padding: 15px 0; + padding: 15px 16px; white-space: nowrap; &:first-child { - padding-left: 15px; + /* padding-left: 15px; */ } &:last-child { - padding-right: 15px; + /* padding-right: 15px; */ } &:has(.table-item-id) { @@ -862,7 +862,8 @@ gap: 5px; } - button { + button, + a { padding: 4px 12px; font-size: 14px; line-height: 20px; @@ -4400,6 +4401,11 @@ margin-bottom: 10px; } + .source-image { + max-width: 120px; + width: 100%; + } + .source-url { color: #6366f1; font-weight: 600; diff --git a/src/app/ui/violations/file-page/violation-page-title.tsx b/src/app/ui/violations/file-page/violation-page-title.tsx index 0c1bcf5..2894d9d 100644 --- a/src/app/ui/violations/file-page/violation-page-title.tsx +++ b/src/app/ui/violations/file-page/violation-page-title.tsx @@ -1,3 +1,5 @@ +'use client' + import { useTranslations } from 'next-intl'; import { useParams } from 'next/navigation' import { IconEyeDashed } from '@/app/ui/icons/icons'; diff --git a/src/app/ui/violations/file-page/violation-page-violations-list.tsx b/src/app/ui/violations/file-page/violation-page-violations-list.tsx index 4d6eb61..68ee6ed 100644 --- a/src/app/ui/violations/file-page/violation-page-violations-list.tsx +++ b/src/app/ui/violations/file-page/violation-page-violations-list.tsx @@ -1,52 +1,149 @@ -export default function ViolationPageViolationsList() { +// app/ui/violations/file-page/violation-page-violations-list.tsx +'use client' + +import { useTranslations } from 'next-intl'; +import { useRouter, usePathname } from 'next/navigation'; +import { useEffect } from 'react'; + +interface ViolationFileDetail { + id: number; + url: string; + page_url: string; + page_title: string; + host: string; + status: string; + created_date: string; + file_id: string; +} + +interface ViolationFile { + violations: ViolationFileDetail[], + total_elements: number, + total_pages: number, + current_page: number, + page_size: number, + has_next: boolean, + has_previous: boolean +} + +export default function ViolationPageViolationsList({ + fileViolations, + currentPage, + fileId +}: { + fileViolations: ViolationFile, + currentPage: number, + fileId: string +}) { + const t = useTranslations('Global'); + const router = useRouter(); + const pathname = usePathname(); + + if (!fileViolations?.violations) { + return null; + } + + useEffect(() => { + console.log(fileViolations); + }, [fileViolations]) + + const handlePageChange = (page: number) => { + router.push(`${pathname}?page=${page}`); + }; + + const getPageNumbers = () => { + const pages = []; + const maxVisible = 5; + const total = fileViolations.total_pages; + + let start = Math.max(1, currentPage - Math.floor(maxVisible / 2)); + let end = Math.min(total, start + maxVisible - 1); + + if (end - start + 1 < maxVisible) { + start = Math.max(1, end - maxVisible + 1); + } + + for (let i = start; i <= end; i++) { + pages.push(i); + } + + return pages; + }; + return ( -
+

Все нарушения этого файла

- Всего: 0 | Страница 0 из 0 + Всего: {fileViolations.total_elements} | Страница {fileViolations.current_page} из {fileViolations.total_pages}
- {/* это нужно будет сделать отдельным компонентом */} -
-
-
- - fileName - - - Новое - -
-
- 0 - Неавторизованное использование - 0% -
- -
+
+ {fileViolations.violations.map(item => { + return ( +
+
+ +
+
+ + {item.page_title} + + + {item.status} + +
+
+ {item.host} +
+ +
+ ); + })}
-
- 1 - 2 - 3 - Следующая -
+ {fileViolations.total_pages > 1 && ( +
+ {fileViolations.has_previous && ( + + )} + + {getPageNumbers().map(pageNum => ( + + ))} + + {fileViolations.has_next && ( + + )} +
+ )}
- ) + ); } \ No newline at end of file diff --git a/src/app/ui/violations/violations-check-all-section.tsx b/src/app/ui/violations/violations-check-all-section.tsx index 99db6ad..cc98c27 100644 --- a/src/app/ui/violations/violations-check-all-section.tsx +++ b/src/app/ui/violations/violations-check-all-section.tsx @@ -3,7 +3,7 @@ import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useTranslations } from 'next-intl'; import { getViolationSearchStatus, startGlobalMonitoring } from '@/app/actions/violationActions'; -import { useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import ModalWindow from '@/app/components/ModalWindow'; import { toast } from 'sonner'; @@ -40,9 +40,6 @@ export default function ViolationsCheckAllSection() { return getViolationSearchStatus(); }, select: (data: violationSearchStatus) => { - console.log('violationSearchStatus'); - console.log(data); - if (data) { return data; } else { @@ -62,6 +59,18 @@ export default function ViolationsCheckAllSection() { const [isProcessing, setIsProcessing] = useState(false); const [openWindow, setOpenWindow] = useState(false); const t = useTranslations('Global'); + const previousStatusRef = useRef(''); + + useEffect(() => { + const currentStatus = violationSearchStatus?.status; + const previousStatus = previousStatusRef.current; + + if (currentStatus === 'task-not-found' && previousStatus === 'PROCESSING') { + queryClient.invalidateQueries({ queryKey: ['violationData'] }); + toast.success(t('violation-data-updated')); + } + previousStatusRef.current = currentStatus; + }, [violationSearchStatus?.status, queryClient]); async function openModalWindow() { await queryClient.invalidateQueries({ queryKey: ['violationSearchStatus'] }); @@ -135,9 +144,9 @@ export default function ViolationsCheckAllSection() {
Поиск нарушений во всех ваших изображениях. Проверка только точных совпадений (95%+).
- {violationSearchStatus?.status && ( + {(violationSearchStatus?.status !== 'task-not-found') && (
- {violationSearchStatus?.status} + {t('content-verification-status')}: {violationSearchStatus?.status}
)}
diff --git a/src/app/ui/violations/violations-table.tsx b/src/app/ui/violations/violations-table.tsx index da2b555..1787225 100644 --- a/src/app/ui/violations/violations-table.tsx +++ b/src/app/ui/violations/violations-table.tsx @@ -3,11 +3,27 @@ import { useMemo, useState } 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 } from '@/app/ui/icons/icons'; +import { IconArrowUp, IconArrowDown, IconFilter, IconDoubleArrowRight, IconArrowRight, IconDoubleArrowLeft, IconArrowLeft, IconEye } from '@/app/ui/icons/icons'; import DropDownList from '@/app/components/DropDownList'; import { useQuery } from '@tanstack/react-query'; -import { getUserFilesData } from '@/app/actions/fileEntity'; +import { getViolationFilesArray } from '@/app/actions/violationActions'; +import { useViewport } from '@/app/hooks/useViewport'; +import { formatDate, formatDateTime } from '@/app/lib/formatDate'; +import Link from 'next/link'; +interface ViolationFile { + createdAt: string; + fileId: string; + fileName: string; + fileSize: number; + latestViolationDate: string; + mimeType: string; + status: string; + supportId: number; + violationCount: number; +} + +interface ViolationDataResponse extends Array { } export default function ViolationsTable() { const { @@ -15,42 +31,65 @@ export default function ViolationsTable() { isLoading, isError, error, - } = useQuery({ - queryKey: ['violationData', 1, 10000], - queryFn: () => getUserFilesData(1, 10000), + } = useQuery({ + queryKey: ['violationData'], + queryFn: () => getViolationFilesArray(), select: (data) => { - if (!data?.files) return []; - - return data.files.map((item: any) => { - /* const newDate = new Date(item.updatedAt).getTime(); */ - - return { - id: item.id, - }; - }); + return data; }, - refetchInterval: 30000 + /* refetchInterval: 30000 */ }); const t = useTranslations('Global'); // Состояния const [sorting, setSorting] = useState([]); const [columnFilters, setColumnFilters] = useState([]); - const [dateFilter, setDateFilter] = useState('all'); const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10, }); - const columns = useMemo[]>( + const viewport = useViewport(); + const getMaxLengthByWidth = (): number => { + if (viewport.device === 'MOBILE') return 26; + if (viewport.device === 'TABLET') return 32; + if (viewport.device === 'DESKTOP') return 84; + 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 */; + } + + const columns = useMemo[]>( () => [ { - accessorKey: 'file', + accessorKey: 'supportId', header: ({ column }) => (
- Файл + ID -
- ), - cell: ({ row }) => ( -
- {/* {row.original.amount} */} - violationUrl -
- ), - }, - { - accessorKey: 'statistic', - header: ({ column }) => ( -
- - Статистика - - -
- ), - cell: ({ row }) => ( -
- Статистика -
- ), - }, - { - accessorKey: 'DMCA', - header: ({ column }) => ( -
- - DMCA жалоба + {t('file')} +
+ ), + cell: ({ row }) => ( +
+ {row.original.violationCount} +
+ ), + }, + { + accessorKey: 'date', + header: ({ column }) => ( +
+ + {t('date')} + + +
+ ), + cell: ({ row }) => ( +
+ {formatDate(row.original.latestViolationDate)} +
+ {formatDateTime(row.original.latestViolationDate)} +
+ ), }, { accessorKey: 'actions', header: ({ column }) => (
- actions + {t('actions')}
), cell: ({ row }) => { return ( -
- actions +
+
+ {/* */} + + + +
) } @@ -209,58 +270,13 @@ export default function ViolationsTable() { ) const filteredData = useMemo(() => { - let result = violationData?.payments; + let result = violationData; if (!result) { return []; } - // Фильтр по дате - 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: any) => { - if (item.createdAt) { - return new Date(item.createdAt).getTime() >= todayStart - } else { - return 0 - } - }); - break; - case 'week': - const weekAgo = now - sevenDays; - result = result.filter((item: any) => { - if (item.createdAt) { - return new Date(item.createdAt).getTime() >= weekAgo; - } - }); - break; - case 'month': - const monthAgo = now - thirtyDays; - result = result.filter((item: any) => { - if (item.createdAt) { - return new Date(item.createdAt).getTime() >= monthAgo; - } - }); - break; - case 'older': - const monthAgo2 = now - thirtyDays; - result = result.filter((item: any) => { - if (item.createdAt) { - return new Date(item.createdAt).getTime() < monthAgo2; - } - }); - break; - } - } - return result - }, [violationData?.payments, dateFilter]) + }, [violationData]) const table = useReactTable({ data: filteredData, @@ -296,7 +312,7 @@ export default function ViolationsTable() {

- Обнаруженные нарушения + {t('violations-detected')}

{/* Фильтры */} diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 8d54a57..a193f4f 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -305,7 +305,11 @@ "file-verification-started-successfully": "File verification started successfully.", "an-error-occurred-while-starting-file-verification": "An error occurred while starting file verification.", "file-file-name-already-exists": "The file {fileName} already exists on the account.", - "file-file-name-does-not-exist": "The file {fileName} does not exist on the account." + "file-file-name-does-not-exist": "The file {fileName} does not exist on the account.", + "violations-detected": "Violations detected", + "content-verification-status": "Content verification status", + "violation-data-updated": "Violation data updated", + "open": "Open" }, "Login-register-form": { "and": "and", diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index 35525cd..f85a8c7 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -305,7 +305,11 @@ "file-verification-started-successfully": "Проверка файлов успешно запущена.", "an-error-occurred-while-starting-file-verification": "При запуске проверки файлов произошла ошибка.", "file-file-name-already-exists": "Файл {fileName} уже есть на аккаунте.", - "file-file-name-does-not-exist": "Файла {fileName} на аккаунте нет." + "file-file-name-does-not-exist": "Файла {fileName} на аккаунте нет.", + "violations-detected": "Обнаруженные нарушения", + "content-verification-status": "Статус проверки контента", + "violation-data-updated": "Данные нарушений обновлены", + "open": "Открыть" }, "Login-register-form": { "and": "и",