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 ? (
+

+ ) : (
+
+ )}
-
- 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')}
+
+
+
+
+
-
+
+
+
+
+
+ {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": "и",