From 333dd5d3c5fda09d7207b61c0aadd8042a35cd19 Mon Sep 17 00:00:00 2001 From: smanylov Date: Tue, 17 Mar 2026 18:41:02 +0700 Subject: [PATCH] continue: add violation panel --- .../[locale]/pages/violations/[id]/page.tsx | 87 ++++-- src/app/styles/pages-styles.scss | 265 +++++++++++++----- .../modal-windows/file-info-modal-window.tsx | 12 +- .../file-page/violation-file-not-found.tsx | 13 + .../file-page/violation-page-file-info.tsx | 60 ++-- .../file-page/violation-page-note.tsx | 6 +- .../file-page/violation-page-title.tsx | 37 +-- .../violation-page-violations-list.tsx | 197 +++++++++---- src/i18n/messages/en.json | 8 +- src/i18n/messages/ru.json | 8 +- 10 files changed, 502 insertions(+), 191 deletions(-) create mode 100644 src/app/ui/violations/file-page/violation-file-not-found.tsx diff --git a/src/app/[locale]/pages/violations/[id]/page.tsx b/src/app/[locale]/pages/violations/[id]/page.tsx index 98fe711..92b7a0d 100644 --- a/src/app/[locale]/pages/violations/[id]/page.tsx +++ b/src/app/[locale]/pages/violations/[id]/page.tsx @@ -9,6 +9,43 @@ import ViolationPageActions from '@/app/ui/violations/file-page/violation-page-a 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'; +import { viewFileInfo } from '@/app/actions/fileEntity'; +import { useTranslations } from 'next-intl'; +import ViolationFileNotFound from '@/app/ui/violations/file-page/violation-file-not-found'; + +type MonitoringStatus = 'NONE' | 'MONITORING_DAILY' | 'MONITORING_WEEKLY' | 'MONITORING_MONTHLY'; +type MimeType = 'image' | 'video' | 'audio' | 'document'; + +export interface FileDetails { + id: string; + userId: number; + originalFileName: string; + storedFileName: string; + filePath: string; + protectedFilePath: string; + fileSize: number; + mimeType: MimeType; + fileExtension: string; + checksum: string; + uploadSessionId: string; + status: string; + createdAt: string; + updatedAt: string; + formattedSize: string; + downloadUrl: string; + existsOnDisk: boolean; + supportId: number | null; + protectStatus: string; + ownerName: string; + ownerEmail: string; + ownerCompany: string | null; + fileName: string; + fileFormat: string | null; + checksCount: number; + fileUploadDate: string; + monitoring: MonitoringStatus; + thumbnailFileUrl: string; +} interface PageProps { params: Promise<{ id: string }>; @@ -19,31 +56,43 @@ export default async function Page({ params, searchParams }: PageProps) { + /* const t = useTranslations('Global'); */ const { id } = await params; const { page } = await searchParams; const currentPage = Number(page) || 1; - const fileViolations = await getFileViolations(id, currentPage); + try { + const fileViolations = await getFileViolations(id, currentPage); + const fileInfo: FileDetails = await viewFileInfo(id); - return ( -
- -
+ return ( +
+
- - - -
-
- - - +
+ + {/* */} + +
+
+ + {/* */} + {/* */} +
-
- ) + ) + } catch (error) { + + return ( + + ) + + } } \ No newline at end of file diff --git a/src/app/styles/pages-styles.scss b/src/app/styles/pages-styles.scss index 41df816..0e00d32 100644 --- a/src/app/styles/pages-styles.scss +++ b/src/app/styles/pages-styles.scss @@ -3593,6 +3593,17 @@ padding: 12px 20px; border-bottom: 1px solid #f1f5f9; width: 100%; + + &.image { + text-align: center; + max-width: 200px; + display: flex; + margin: 0 auto; + + img { + width: 100%; + } + } } .info-label { @@ -4192,7 +4203,7 @@ justify-content: space-between; margin-bottom: 20px; padding-bottom: 15px; - border-bottom: 2px solid #f1f5f9; + border-bottom: 1px solid #e2e8f0; } .info-row { @@ -4300,11 +4311,13 @@ .action-buttons { display: flex; gap: 10px; - flex-wrap: wrap; + flex-direction: column; + /* flex-wrap: wrap; */ .btn-action { flex: 1; - min-width: 150px; + /* min-width: 150px; */ + width: 100%; padding: 12px 20px; border-radius: 12px; font-weight: 600; @@ -4375,84 +4388,183 @@ } } - .sources-list { + .sources-list-wrapper { + display: grid; + grid-template-columns: 1fr 3fr; + gap: 10px; + height: 80vh; + + .sources-list { + display: flex; + flex-direction: column; + gap: 15px; + + .source-card { + /* background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%); + border-radius: 12px; + border: 1px solid #e2e8f0; */ + + background: #fff; + border: 1px solid #f3f4f6; + border-radius: 20px; + box-shadow: 0 4px 20px #0000001a; + + + + transition: all 0.3s ease; + padding: 15px; + height: calc(16vh - 12px); + cursor: pointer; + display: flex; + justify-content: space-between; + /* flex-direction: column; */ + opacity: 0.75; + + &:hover { + transform: translateY(-2px); + box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1); + border-color: #cbd5e1; + } + + &.selected { + opacity: 1; + border: 1px solid #6366f1; + background: linear-gradient(180deg, #ffffff 0%, #e6eaf3 100%); + } + } + + .source-header { + display: flex; + align-items: start; + justify-content: space-between; + margin-bottom: 10px; + gap: 5px; + + &-right { + display: flex; + flex-direction: column; + align-items: end; + } + } + + .source-image { + max-width: 120px; + width: 100%; + } + + .source-url { + color: #6366f1; + font-weight: 600; + font-size: 14px; + text-decoration: none; + word-break: break-all; + flex: 1; + } + + .source-status { + padding: 4px 10px; + border-radius: 6px; + font-size: 11px; + font-weight: 600; + white-space: nowrap; + margin-left: 10px; + + &.new { + background: #fef2f2; + color: #dc2626; + } + } + + .source-meta { + gap: 15px; + font-size: 12px; + color: #64748b; + margin-bottom: 10px; + flex-wrap: wrap; + margin-right: 5px; + } + + .source-actions { + display: flex; + gap: 10px; + flex-wrap: wrap; + margin-top: auto; + } + } + } + + .violation-info { + /* background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%); + border: 1px solid #e2e8f0; + border-radius: 12px; + */ + background: #fff; + border: 1px solid #f3f4f6; + border-radius: 20px; + box-shadow: 0 4px 20px #0000001a; + + padding: 15px; display: flex; flex-direction: column; - gap: 15px; - .source-card { - background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%); - border-radius: 12px; - padding: 15px; - border: 1px solid #e2e8f0; - transition: all 0.3s ease; - - &:hover { - transform: translateY(-2px); - box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1); - border-color: #cbd5e1; - } - } - - .source-header { - display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: 10px; - } - - .source-image { - max-width: 120px; - width: 100%; - } - - .source-url { - color: #6366f1; - font-weight: 600; - font-size: 14px; - text-decoration: none; - word-break: break-all; - flex: 1; - } - - .source-status { - padding: 4px 10px; - border-radius: 6px; - font-size: 11px; - font-weight: 600; - white-space: nowrap; - margin-left: 10px; - - &.new { - background: #fef2f2; - color: #dc2626; - } - } - - .source-meta { - display: flex; - gap: 15px; - font-size: 12px; - color: #64748b; - margin-bottom: 10px; - flex-wrap: wrap; - } - - .source-actions { - display: flex; + &-grid { + display: grid; + grid-template-columns: 3fr 1fr; gap: 10px; - flex-wrap: wrap; - margin-top: 10px; + flex-grow: 1; + padding-bottom: 15px; + } + + &-title { + border-bottom: 1px solid #e2e8f0; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; + padding-bottom: 15px; + display: flex; + } + + &-image { + + margin-bottom: 20px; + /* padding-bottom: 15px; */ + /* border-bottom: 2px solid #e6e8eb; */ + + img { + max-width: 300px; + width: 100%; + + } + } + + &-source { + margin-bottom: 20px; + /* padding-bottom: 15px; */ + /* border-bottom: 2px solid #e6e8eb; */ + + span { + color: #64748b; + margin-right: 5px; + } + + a { + color: #6366f1; + } + } + + &-actions { + border-left: 1px solid #e2e8f0; + padding-left: 15px; } } .pagination { - margin-top: 20px; display: flex; - justify-content: center; + justify-content: start; gap: 4px; + margin-top: 20px; - &__item { + &-item { padding: 8px 12px; border-radius: 8px; text-decoration: none; @@ -4462,11 +4574,15 @@ border: 2px solid #e5e7eb; transition: all 0.2s ease; + &:disabled { + opacity: 0.6; + } + &:hover { border-color: #667eea; } - &--active { + &-active { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: 2px solid #667eea; @@ -4478,11 +4594,15 @@ } } + .violation-page-note { + margin-top: auto; + margin-bottom: 0; + } + .note-form { margin-top: 0; border: none; padding: 0; - background: #f8fafc; } .note-textarea { @@ -4495,6 +4615,7 @@ font-family: inherit; resize: vertical; box-sizing: border-box; + background: #f8fafc; &:focus { outline: none; diff --git a/src/app/ui/modal-windows/file-info-modal-window.tsx b/src/app/ui/modal-windows/file-info-modal-window.tsx index dd3f0fb..3e85a7a 100644 --- a/src/app/ui/modal-windows/file-info-modal-window.tsx +++ b/src/app/ui/modal-windows/file-info-modal-window.tsx @@ -1,8 +1,13 @@ import { convertBytes } from '@/app/lib/convertBytes'; import { formatDate, formatDateTime } from '@/app/lib/formatDate'; import { useTranslations } from 'next-intl'; +import { FileDetails } from '@/app/[locale]/pages/violations/[id]/page'; -export function FileInfoModalWindow({ fileInfo, setWindowClose, setWindowChildren }: any) { +export function FileInfoModalWindow({ fileInfo, setWindowClose, setWindowChildren }: { + fileInfo: FileDetails, + setWindowClose: any, + setWindowChildren: any +}) { const t = useTranslations('Global'); return ( @@ -86,6 +91,11 @@ export function FileInfoModalWindow({ fileInfo, setWindowClose, setWindowChildre {fileInfo.fileExtension ? fileInfo.fileExtension : '-'}
+ {fileInfo.mimeType === 'image' && ( +
+ +
+ )} diff --git a/src/app/ui/violations/file-page/violation-file-not-found.tsx b/src/app/ui/violations/file-page/violation-file-not-found.tsx new file mode 100644 index 0000000..67ea968 --- /dev/null +++ b/src/app/ui/violations/file-page/violation-file-not-found.tsx @@ -0,0 +1,13 @@ +'use client' + +import { useTranslations } from 'next-intl' + +export default function ViolationFileNotFound() { + const t = useTranslations('Global'); + + return ( +
+ {t('file-not-found')} +
+ ) +} \ No newline at end of file diff --git a/src/app/ui/violations/file-page/violation-page-file-info.tsx b/src/app/ui/violations/file-page/violation-page-file-info.tsx index f67f2ea..aef5dbe 100644 --- a/src/app/ui/violations/file-page/violation-page-file-info.tsx +++ b/src/app/ui/violations/file-page/violation-page-file-info.tsx @@ -1,4 +1,14 @@ -export default function ViolationPageFileInfo() { +'use client' + +import { FileDetails } from '@/app/[locale]/pages/violations/[id]/page'; +import { convertBytes } from '@/app/lib/convertBytes'; +import { formatDate } from '@/app/lib/formatDate'; +import { useTranslations } from 'next-intl'; +import { useEffect } from 'react'; + +export default function ViolationPageFileInfo({ fileInfo }: { fileInfo: FileDetails }) { + const t = useTranslations('Global'); + return (

- 📄 Информация о файле + {t('file-information')}

- Тип файла - fileType -
-
- Размер - 0 КБ -
-
- Загружен - 0 -
-
- Водяной знак + + ID + - Применен + {fileInfo ? fileInfo.supportId : '#'}
- Уровень защиты + + {t('file-type')} + - Максимальный + {fileInfo ? fileInfo.mimeType : '#'} + +
+
+ + {t('size')} + + {fileInfo ? convertBytes(fileInfo.fileSize) : '#'} +
+
+ + {t('loaded')} + + + {fileInfo ? formatDate(fileInfo.createdAt) : '#'} + +
+
+ + {t('monitoring')} + + + {fileInfo ? fileInfo.monitoring : '#'}
diff --git a/src/app/ui/violations/file-page/violation-page-note.tsx b/src/app/ui/violations/file-page/violation-page-note.tsx index 4c721c6..e08b4b0 100644 --- a/src/app/ui/violations/file-page/violation-page-note.tsx +++ b/src/app/ui/violations/file-page/violation-page-note.tsx @@ -1,12 +1,12 @@ export default function ViolationPageNote() { return (

- ✏️ Добавить заметку + Добавить заметку

@@ -15,7 +15,7 @@ export default function ViolationPageNote() {
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 2894d9d..b98a1e6 100644 --- a/src/app/ui/violations/file-page/violation-page-title.tsx +++ b/src/app/ui/violations/file-page/violation-page-title.tsx @@ -1,45 +1,28 @@ 'use client' import { useTranslations } from 'next-intl'; -import { useParams } from 'next/navigation' import { IconEyeDashed } from '@/app/ui/icons/icons'; +import { FileDetails } from '@/app/[locale]/pages/violations/[id]/page'; -export default function ViolationPageTitle() { +export default function ViolationPageTitle({ fileInfo }: { fileInfo: FileDetails }) { const t = useTranslations('Global'); - const params = useParams() - const id = params.id return (
- -
-
-

- file id: {id} -

-
- - fileType - - - fileSize - + {fileInfo.thumbnailFileUrl ? ( + {fileInfo.fileName} + ) : ( + + )} - - fileViolationDate - - - - fileViolationType - -
+

+ {t('file-name')}: {fileInfo?.fileName ? fileInfo.fileName : '#'} +

) } \ No newline at end of file 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 f540758..c0ceb53 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 @@ -4,7 +4,9 @@ import { useTranslations } from 'next-intl'; import Link from 'next/link'; import { useRouter, usePathname } from 'next/navigation'; -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; +import { formatDate } from '@/app/lib/formatDate'; +import ViolationPageNote from '@/app/ui/violations/file-page/violation-page-note'; interface ViolationFileDetail { id: number; @@ -39,17 +41,14 @@ export default function ViolationPageViolationsList({ const t = useTranslations('Global'); const router = useRouter(); const pathname = usePathname(); + const [selectedViolation, setSelectedViolation] = useState(null); if (!fileViolations?.violations) { return null; } - useEffect(() => { - console.log(fileViolations); - }, [fileViolations]); - const handlePageChange = (page: number) => { - router.push(`${pathname}?page=${page}`); + router.push(`${pathname}?page=${page}`, { scroll: false }); }; const getPageNumbers = () => { @@ -71,79 +70,179 @@ export default function ViolationPageViolationsList({ return pages; }; + function selectHandler(violation: ViolationFileDetail) { + setSelectedViolation(violation); + } + return (

- Все нарушения этого файла + {t('all-file-violations')}

- Всего: {fileViolations.total_elements} | Страница {fileViolations.current_page} из {fileViolations.total_pages} + {t('total-violations')}: {fileViolations.total_elements} | {t('page')} {fileViolations.current_page} {t('out-of')} {fileViolations.total_pages}
-
- {fileViolations.violations.map(item => { - return ( -
-
- +
+
+ {fileViolations.violations.map(item => { + return ( +
{ + selectHandler(item); + }} + > + {/*
+
+ +
+ +
*/} +
+
+ +
+
+ + {t('source')}: + + + {item.host} + +
+
+ +
+ + {item.status} + + +
+ + {t('open')} + +
+
+ +
-
- - {item.page_title} - - - {item.status} + ); + })} +
+ +
+
+
+

+ {selectedViolation?.page_title} +

+
+ {selectedViolation?.page_title} +
+
+ + + {t('source')}: -
-
- {item.host} -
-
- {t('open')} + {selectedViolation?.page_url}
-
- ); - })} -
+
+ + {t('date')} {selectedViolation?.created_date ? formatDate(selectedViolation?.created_date) : '#'} + +
+
+ {selectedViolation?.status} +
+
+ +
+
+ + + + + +
+
+
+ +
+
{fileViolations.total_pages > 1 && (
- {fileViolations.has_previous && ( - - )} + {getPageNumbers().map(pageNum => ( ))} - {fileViolations.has_next && ( - - )} +
)}
diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index a193f4f..1c554b0 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -309,7 +309,13 @@ "violations-detected": "Violations detected", "content-verification-status": "Content verification status", "violation-data-updated": "Violation data updated", - "open": "Open" + "open": "Open", + "file-type": "File type", + "file-not-found": "File not found", + "all-file-violations" : "All file violations", + "previous" : "Previous", + "next": "Next", + "source": "Source" }, "Login-register-form": { "and": "and", diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index f85a8c7..5e3a985 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -309,7 +309,13 @@ "violations-detected": "Обнаруженные нарушения", "content-verification-status": "Статус проверки контента", "violation-data-updated": "Данные нарушений обновлены", - "open": "Открыть" + "open": "Открыть", + "file-type": "Тип файла", + "file-not-found": "Файл не найден", + "all-file-violations": "Все нарушения файла", + "previous" : "Предыдущая", + "next": "Следующая", + "source": "Источник" }, "Login-register-form": { "and": "и",